Skip to content

Commit

Permalink
Merge pull request #38 from buildkite/keithduncan/fix-cross-region
Browse files Browse the repository at this point in the history
Use current region to find bucket region
  • Loading branch information
keithduncan authored Jul 9, 2021
2 parents 67c4e22 + 5daa8ac commit 71e04a4
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 23 deletions.
9 changes: 1 addition & 8 deletions s3secrets-helper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ const (
envPipeline = "BUILDKITE_PIPELINE_SLUG"
envRepo = "BUILDKITE_REPO"
envCredHelper = "BUILDKITE_PLUGIN_S3_SECRETS_CREDHELPER"

envDefaultRegion = "AWS_DEFAULT_REGION"
defaultRegion = "us-east-1"
)

func main() {
Expand All @@ -42,11 +39,7 @@ func mainWithError(log *log.Logger) error {
return fmt.Errorf("%s or %s required", envPrefix, envPipeline)
}

region := os.Getenv(envDefaultRegion)
if region == "" {
region = defaultRegion
}
client, err := s3.New(region)
client, err := s3.New(log, bucket)
if err != nil {
return err
}
Expand Down
52 changes: 45 additions & 7 deletions s3secrets-helper/s3/s3.go
Original file line number Diff line number Diff line change
@@ -1,38 +1,76 @@
package s3

import (
"log"
"io/ioutil"
"os"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/buildkite/elastic-ci-stack-s3-secrets-hooks/s3secrets-helper/v2/sentinel"
)

const envDefaultRegion = "AWS_DEFAULT_REGION"

type Client struct {
s3 *s3.S3
bucket string
}

func New(region string) (*Client, error) {
sess, err := session.NewSession(&aws.Config{
Region: &region,
func New(log *log.Logger, bucket string) (*Client, error) {
sess, err := session.NewSession()
if err != nil {
return nil, err
}

currentRegion := os.Getenv(envDefaultRegion)
// Discover our executing region using the IMDS
if currentRegion == "" {
idms := ec2metadata.New(sess)
currentRegion, _ = idms.Region()
}
// Fall back to us-east-1 :(
if currentRegion == "" {
currentRegion = "us-east-1"
}

log.Printf("Discovered current region as %q\n", currentRegion)

// Using the current region (or a guess) find where the bucket lives
bucketRegion, err := s3manager.GetBucketRegion(aws.BackgroundContext(), sess, bucket, currentRegion)
if err != nil {
return nil, err
}

log.Printf("Discovered bucket region as %q\n", bucketRegion)

sess, err = session.NewSession(&aws.Config{
Region: &bucketRegion,
})
if err != nil {
return nil, err
}
return &Client{
s3: s3.New(sess),
bucket: bucket,
}, nil
}

func (c *Client) Bucket() (string) {
return c.bucket
}

// Get downloads an object from S3.
// Intended for small files; object is fully read into memory.
// sentinel.ErrNotFound and sentinel.ErrForbidden are returned for those cases.
// Other errors are returned verbatim.
func (c *Client) Get(bucket, key string) ([]byte, error) {
func (c *Client) Get(key string) ([]byte, error) {
out, err := c.s3.GetObject(&s3.GetObjectInput{
Bucket: &bucket,
Bucket: &c.bucket,
Key: &key,
})
if err != nil {
Expand All @@ -59,8 +97,8 @@ func (c *Client) Get(bucket, key string) ([]byte, error) {
// 200 OK returns true without error.
// 404 Not Found and 403 Forbidden return false without error.
// Other errors result in false with an error.
func (c *Client) BucketExists(bucket string) (bool, error) {
if _, err := c.s3.HeadBucket(&s3.HeadBucketInput{Bucket: &bucket}); err != nil {
func (c *Client) BucketExists() (bool, error) {
if _, err := c.s3.HeadBucket(&s3.HeadBucketInput{Bucket: &c.bucket}); err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
// https://github.com/aws/aws-sdk-go/issues/2593#issuecomment-491436818
Expand Down
17 changes: 9 additions & 8 deletions s3secrets-helper/secrets/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (

// Client represents interaction with AWS S3
type Client interface {
Get(bucket, key string) ([]byte, error)
BucketExists(bucket string) (bool, error)
Bucket() (string)
Get(key string) ([]byte, error)
BucketExists() (bool, error)
}

// Agent represents interaction with an ssh-agent process
Expand Down Expand Up @@ -56,12 +57,12 @@ type Config struct {
// functionality; secrets are downloaded from S3, and loaded into ssh-agent
// etc.
func Run(conf Config) error {
bucket := conf.Bucket
bucket := conf.Client.Bucket()
log := conf.Logger

log.Printf("~~~ Downloading secrets from :s3: %s", bucket)

if ok, err := conf.Client.BucketExists(bucket); !ok {
if ok, err := conf.Client.BucketExists(); !ok {
if err != nil {
log.Printf("+++ :warning: Bucket %q not found: %v", bucket, err)
} else {
Expand Down Expand Up @@ -102,7 +103,7 @@ func getSSHKeys(conf Config, results chan<- getResult) {
for _, k := range keys {
conf.Logger.Printf("- %s", k)
}
go GetAll(conf.Client, conf.Bucket, keys, results)
go GetAll(conf.Client, conf.Client.Bucket(), keys, results)
}

func getEnvs(conf Config, results chan<- getResult) {
Expand All @@ -116,7 +117,7 @@ func getEnvs(conf Config, results chan<- getResult) {
for _, k := range keys {
conf.Logger.Printf("- %s", k)
}
go GetAll(conf.Client, conf.Bucket, keys, results)
go GetAll(conf.Client, conf.Client.Bucket(), keys, results)
}

func getGitCredentials(conf Config, results chan<- getResult) {
Expand All @@ -128,7 +129,7 @@ func getGitCredentials(conf Config, results chan<- getResult) {
for _, k := range keys {
conf.Logger.Printf("- %s", k)
}
go GetAll(conf.Client, conf.Bucket, keys, results)
go GetAll(conf.Client, conf.Client.Bucket(), keys, results)
}

func handleSSHKeys(conf Config, results <-chan getResult) error {
Expand Down Expand Up @@ -241,7 +242,7 @@ func GetAll(c Client, bucket string, keys []string, results chan<- getResult) {
// goroutine immediately fetches from S3, then waits for its turn to send
// to the results channel; concurrent fetch, ordered results.
go func(k string, link <-chan chan<- getResult, nextLink chan<- chan<- getResult) {
data, err := c.Get(bucket, k)
data, err := c.Get(k)
results := <-link // wait for results channel from previous goroutine
results <- getResult{bucket: bucket, key: k, data: data, err: err}
nextLink <- results // send results channel to the next goroutine
Expand Down

0 comments on commit 71e04a4

Please sign in to comment.