fleet/server/datastore/s3/s3.go
Victor Lyuboslavsky 68b7cf9141
Added signed URLs (#25197)
For #24869 

This subtask contains code to sign the CloudFront software installer and
bootstrap package URL using AWS SDK URL signer.
It works with the current bootstrap package delivery. For software
installers, fleetd will need to be modified to take advantage of this
URL in a future subtask (which will also include updated API contributor
docs).

My article on signed URLs, for context:
https://victoronsoftware.com/posts/cloudfront-signed-urls/

# Checklist for submitter

- [x] Added/updated automated tests
- [x] Manual QA for all new/changed functionality
2025-01-09 12:56:54 -06:00

98 lines
2.6 KiB
Go

package s3
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"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/fleetdm/fleet/v4/server/config"
)
const awsRegionHint = "us-east-1"
type s3store struct {
s3client *s3.S3
bucket string
prefix string
cloudFrontConfig *config.S3CloudFrontConfig
}
// newS3store initializes an S3 Datastore
func newS3store(config config.S3ConfigInternal) (*s3store, error) {
conf := &aws.Config{}
// Use default auth provire if no static credentials were provided
if config.AccessKeyID != "" && config.SecretAccessKey != "" {
conf.Credentials = credentials.NewStaticCredentials(
config.AccessKeyID,
config.SecretAccessKey,
"",
)
}
if config.EndpointURL != "" {
conf.Endpoint = &config.EndpointURL
}
conf.DisableSSL = &config.DisableSSL
conf.S3ForcePathStyle = &config.ForceS3PathStyle
sess, err := session.NewSession(conf)
if err != nil {
return nil, fmt.Errorf("create S3 client: %w", err)
}
// Assume role if configured
if config.StsAssumeRoleArn != "" {
creds := stscreds.NewCredentials(sess, config.StsAssumeRoleArn, func(provider *stscreds.AssumeRoleProvider) {
if config.StsExternalID != "" {
provider.ExternalID = &config.StsExternalID
}
})
conf.Credentials = creds
sess, err = session.NewSession(conf)
if err != nil {
return nil, fmt.Errorf("create S3 client: %w", err)
}
}
if len(config.Region) == 0 {
region, err := s3manager.GetBucketRegion(context.TODO(), sess, config.Bucket, awsRegionHint)
if err != nil {
return nil, fmt.Errorf("create S3 client: %w", err)
}
config.Region = region
}
return &s3store{
s3client: s3.New(sess, &aws.Config{Region: &config.Region}),
bucket: config.Bucket,
prefix: config.Prefix,
cloudFrontConfig: config.CloudFrontConfig,
}, nil
}
// CreateTestBucket creates a bucket with the provided name and a default
// bucket config. Only recommended for local testing.
func (s *s3store) CreateTestBucket(name string) error {
_, err := s.s3client.CreateBucket(&s3.CreateBucketInput{
Bucket: &name,
CreateBucketConfiguration: &s3.CreateBucketConfiguration{},
})
// Don't error if the bucket already exists
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case s3.ErrCodeBucketAlreadyExists, s3.ErrCodeBucketAlreadyOwnedByYou:
return nil
}
}
return err
}