grafana/pkg/components/imguploader/s3uploader.go
2022-04-15 07:48:44 -07:00

145 lines
4.0 KiB
Go

package imguploader
import (
"context"
"fmt"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/util"
)
type S3Uploader struct {
endpoint string
region string
bucket string
path string
acl string
secretKey string
accessKey string
pathStyleAccess bool
log log.Logger
}
func NewS3Uploader(endpoint, region, bucket, path, acl, accessKey, secretKey string, pathStyleAccess bool) *S3Uploader {
return &S3Uploader{
endpoint: endpoint,
region: region,
bucket: bucket,
path: path,
acl: acl,
accessKey: accessKey,
secretKey: secretKey,
pathStyleAccess: pathStyleAccess,
log: log.New("s3uploader"),
}
}
func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string, error) {
sess, err := session.NewSession()
if err != nil {
return "", err
}
creds := credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.StaticProvider{Value: credentials.Value{
AccessKeyID: u.accessKey,
SecretAccessKey: u.secretKey,
}},
&credentials.EnvProvider{},
webIdentityProvider(sess),
remoteCredProvider(sess),
})
cfg := &aws.Config{
Region: aws.String(u.region),
Endpoint: aws.String(u.endpoint),
S3ForcePathStyle: aws.Bool(u.pathStyleAccess),
Credentials: creds,
}
rand, err := util.GetRandomString(20)
if err != nil {
return "", err
}
key := u.path + rand + pngExt
u.log.Debug("Uploading image to s3.", "bucket", u.bucket, "path", key)
// We can ignore the gosec G304 warning on this one because `imageDiskPath` comes
// from alert notifiers and is only used to upload images generated by alerting.
// nolint:gosec
file, err := os.Open(imageDiskPath)
if err != nil {
return "", err
}
defer func() {
if err := file.Close(); err != nil {
u.log.Warn("Failed to close file", "path", imageDiskPath, "err", err)
}
}()
sess, err = session.NewSession(cfg)
if err != nil {
return "", err
}
uploader := s3manager.NewUploader(sess)
result, err := uploader.UploadWithContext(ctx, &s3manager.UploadInput{
Bucket: aws.String(u.bucket),
Key: aws.String(key),
ACL: aws.String(u.acl),
Body: file,
ContentType: aws.String("image/png"),
})
if err != nil {
return "", err
}
return result.Location, nil
}
func webIdentityProvider(sess client.ConfigProvider) credentials.Provider {
svc := sts.New(sess)
roleARN := os.Getenv("AWS_ROLE_ARN")
tokenFilepath := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE")
roleSessionName := os.Getenv("AWS_ROLE_SESSION_NAME")
// nolint:staticcheck
return stscreds.NewWebIdentityRoleProvider(svc, roleARN, roleSessionName, tokenFilepath)
}
func remoteCredProvider(sess *session.Session) credentials.Provider {
ecsCredURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
if len(ecsCredURI) > 0 {
return ecsCredProvider(sess, ecsCredURI)
}
return ec2RoleProvider(sess)
}
func ecsCredProvider(sess *session.Session, uri string) credentials.Provider {
const host = `169.254.170.2`
d := defaults.Get()
return endpointcreds.NewProviderClient(
*d.Config,
d.Handlers,
fmt.Sprintf("http://%s%s", host, uri),
func(p *endpointcreds.Provider) { p.ExpiryWindow = 5 * time.Minute })
}
func ec2RoleProvider(sess client.ConfigProvider) credentials.Provider {
return &ec2rolecreds.EC2RoleProvider{Client: ec2metadata.New(sess), ExpiryWindow: 5 * time.Minute}
}