github.com/grafana/pyroscope@v1.18.0/pkg/objstore/providers/s3/config.go (about)

     1  // SPDX-License-Identifier: AGPL-3.0-only
     2  // Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/storage/bucket/s3/config.go
     3  // Provenance-includes-license: Apache-2.0
     4  // Provenance-includes-copyright: The Cortex Authors.
     5  
     6  package s3
     7  
     8  import (
     9  	"encoding/json"
    10  	"flag"
    11  	"fmt"
    12  	"net/http"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/grafana/dskit/flagext"
    17  	"github.com/minio/minio-go/v7/pkg/encrypt"
    18  	"github.com/pkg/errors"
    19  	"github.com/samber/lo"
    20  	"github.com/thanos-io/objstore/providers/s3"
    21  )
    22  
    23  const (
    24  	SignatureVersionV4 = "v4"
    25  	SignatureVersionV2 = "v2"
    26  
    27  	// SSEKMS config type constant to configure S3 server side encryption using KMS
    28  	// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html
    29  	SSEKMS = "SSE-KMS"
    30  
    31  	// SSES3 config type constant to configure S3 server side encryption with AES-256
    32  	// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html
    33  	SSES3 = "SSE-S3"
    34  
    35  	// https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#path-style-access
    36  	PathStyleLookup = "path-style"
    37  
    38  	// https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access
    39  	VirtualHostedStyleLookup = "virtual-hosted-style"
    40  
    41  	AutoLookup = "auto"
    42  )
    43  
    44  var (
    45  	supportedSignatureVersions     = []string{SignatureVersionV4, SignatureVersionV2}
    46  	supportedSSETypes              = []string{SSEKMS, SSES3}
    47  	supportedBucketLookupTypes     = []string{PathStyleLookup, VirtualHostedStyleLookup, AutoLookup}
    48  	errUnsupportedSignatureVersion = errors.New("unsupported signature version")
    49  	errUnsupportedSSEType          = errors.New("unsupported S3 SSE type")
    50  	errInvalidSSEContext           = errors.New("invalid S3 SSE encryption context")
    51  	errBucketLookupConfigConflict  = errors.New("cannot use s3.force-path-style = true and s3.bucket-lookup-type = virtual-hosted-style at the same time")
    52  	errUnsupportedBucketLookupType = errors.New("invalid S3 bucket lookup type")
    53  )
    54  
    55  // HTTPConfig stores the http.Transport configuration for the s3 minio client.
    56  type HTTPConfig struct {
    57  	IdleConnTimeout       time.Duration `yaml:"idle_conn_timeout" category:"advanced"`
    58  	ResponseHeaderTimeout time.Duration `yaml:"response_header_timeout" category:"advanced"`
    59  	InsecureSkipVerify    bool          `yaml:"insecure_skip_verify" category:"advanced"`
    60  	TLSHandshakeTimeout   time.Duration `yaml:"tls_handshake_timeout" category:"advanced"`
    61  	ExpectContinueTimeout time.Duration `yaml:"expect_continue_timeout" category:"advanced"`
    62  	MaxIdleConns          int           `yaml:"max_idle_connections" category:"advanced"`
    63  	MaxIdleConnsPerHost   int           `yaml:"max_idle_connections_per_host" category:"advanced"`
    64  	MaxConnsPerHost       int           `yaml:"max_connections_per_host" category:"advanced"`
    65  
    66  	// Allow upstream callers to inject a round tripper
    67  	Transport http.RoundTripper `yaml:"-"`
    68  }
    69  
    70  // RegisterFlagsWithPrefix registers the flags for s3 storage with the provided prefix
    71  func (cfg *HTTPConfig) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
    72  	f.DurationVar(&cfg.IdleConnTimeout, prefix+"s3.http.idle-conn-timeout", 90*time.Second, "The time an idle connection will remain idle before closing.")
    73  	f.DurationVar(&cfg.ResponseHeaderTimeout, prefix+"s3.http.response-header-timeout", 2*time.Minute, "The amount of time the client will wait for a servers response headers.")
    74  	f.BoolVar(&cfg.InsecureSkipVerify, prefix+"s3.http.insecure-skip-verify", false, "If the client connects to S3 via HTTPS and this option is enabled, the client will accept any certificate and hostname.")
    75  	f.DurationVar(&cfg.TLSHandshakeTimeout, prefix+"s3.tls-handshake-timeout", 10*time.Second, "Maximum time to wait for a TLS handshake. 0 means no limit.")
    76  	f.DurationVar(&cfg.ExpectContinueTimeout, prefix+"s3.expect-continue-timeout", 1*time.Second, "The time to wait for a server's first response headers after fully writing the request headers if the request has an Expect header. 0 to send the request body immediately.")
    77  	f.IntVar(&cfg.MaxIdleConns, prefix+"s3.max-idle-connections", 0, "Maximum number of idle (keep-alive) connections across all hosts. 0 means no limit.")
    78  	f.IntVar(&cfg.MaxIdleConnsPerHost, prefix+"s3.max-idle-connections-per-host", 100, "Maximum number of idle (keep-alive) connections to keep per-host. If 0, a built-in default value is used.")
    79  	f.IntVar(&cfg.MaxConnsPerHost, prefix+"s3.max-connections-per-host", 0, "Maximum number of connections per host. 0 means no limit.")
    80  }
    81  
    82  // Config holds the config options for an S3 backend
    83  type Config struct {
    84  	Endpoint         string         `yaml:"endpoint"`
    85  	Region           string         `yaml:"region"`
    86  	BucketName       string         `yaml:"bucket_name"`
    87  	SecretAccessKey  flagext.Secret `yaml:"secret_access_key"`
    88  	AccessKeyID      string         `yaml:"access_key_id"`
    89  	Insecure         bool           `yaml:"insecure" category:"advanced"`
    90  	SignatureVersion string         `yaml:"signature_version" category:"advanced"`
    91  	ForcePathStyle   bool           `yaml:"force_path_style" category:"advanced"`
    92  	BucketLookupType string         `yaml:"bucket_lookup_type" category:"advanced"`
    93  
    94  	SSE  SSEConfig  `yaml:"sse"`
    95  	HTTP HTTPConfig `yaml:"http"`
    96  }
    97  
    98  // RegisterFlags registers the flags for s3 storage with the provided prefix
    99  func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
   100  	cfg.RegisterFlagsWithPrefix("", f)
   101  }
   102  
   103  // RegisterFlagsWithPrefix registers the flags for s3 storage with the provided prefix
   104  func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
   105  	f.StringVar(&cfg.AccessKeyID, prefix+"s3.access-key-id", "", "S3 access key ID")
   106  	f.Var(&cfg.SecretAccessKey, prefix+"s3.secret-access-key", "S3 secret access key")
   107  	f.StringVar(&cfg.BucketName, prefix+"s3.bucket-name", "", "S3 bucket name")
   108  	f.StringVar(&cfg.Region, prefix+"s3.region", "", "S3 region. If unset, the client will issue a S3 GetBucketLocation API call to autodetect it.")
   109  	f.StringVar(&cfg.Endpoint, prefix+"s3.endpoint", "", "The S3 bucket endpoint. It could be an AWS S3 endpoint listed at https://docs.aws.amazon.com/general/latest/gr/s3.html or the address of an S3-compatible service in hostname:port format.")
   110  	f.BoolVar(&cfg.Insecure, prefix+"s3.insecure", false, "If enabled, use http:// for the S3 endpoint instead of https://. This could be useful in local dev/test environments while using an S3-compatible backend storage, like Minio.")
   111  	f.BoolVar(&cfg.ForcePathStyle, prefix+"s3.force-path-style", false, "Deprecated, use s3.bucket-lookup-type instead. Set this to `true` to force the bucket lookup to be using path-style.")
   112  	f.StringVar(&cfg.BucketLookupType, prefix+"s3.bucket-lookup-type", AutoLookup, fmt.Sprintf("S3 bucket lookup style, use one of: %v", supportedBucketLookupTypes))
   113  	f.StringVar(&cfg.SignatureVersion, prefix+"s3.signature-version", SignatureVersionV4, fmt.Sprintf("The signature version to use for authenticating against S3. Supported values are: %s.", strings.Join(supportedSignatureVersions, ", ")))
   114  	cfg.SSE.RegisterFlagsWithPrefix(prefix+"s3.sse.", f)
   115  	cfg.HTTP.RegisterFlagsWithPrefix(prefix, f)
   116  }
   117  
   118  // Validate config and returns error on failure
   119  func (cfg *Config) Validate() error {
   120  	if !lo.Contains(supportedSignatureVersions, cfg.SignatureVersion) {
   121  		return errUnsupportedSignatureVersion
   122  	}
   123  
   124  	if cfg.ForcePathStyle && cfg.BucketLookupType != AutoLookup && cfg.BucketLookupType != PathStyleLookup {
   125  		return errBucketLookupConfigConflict
   126  	}
   127  
   128  	if !lo.Contains(supportedBucketLookupTypes, cfg.BucketLookupType) {
   129  		return errUnsupportedBucketLookupType
   130  	}
   131  
   132  	if err := cfg.SSE.Validate(); err != nil {
   133  		return err
   134  	}
   135  
   136  	return nil
   137  }
   138  
   139  // SSEConfig configures S3 server side encryption
   140  // struct that is going to receive user input (through config file or CLI)
   141  type SSEConfig struct {
   142  	Type                 string `yaml:"type"`
   143  	KMSKeyID             string `yaml:"kms_key_id"`
   144  	KMSEncryptionContext string `yaml:"kms_encryption_context"`
   145  }
   146  
   147  func (cfg *SSEConfig) RegisterFlags(f *flag.FlagSet) {
   148  	cfg.RegisterFlagsWithPrefix("", f)
   149  }
   150  
   151  // RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet
   152  func (cfg *SSEConfig) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
   153  	f.StringVar(&cfg.Type, prefix+"type", "", fmt.Sprintf("Enable AWS Server Side Encryption. Supported values: %s.", strings.Join(supportedSSETypes, ", ")))
   154  	f.StringVar(&cfg.KMSKeyID, prefix+"kms-key-id", "", "KMS Key ID used to encrypt objects in S3")
   155  	f.StringVar(&cfg.KMSEncryptionContext, prefix+"kms-encryption-context", "", "KMS Encryption Context used for object encryption. It expects JSON formatted string.")
   156  }
   157  
   158  func (cfg *SSEConfig) Validate() error {
   159  	if cfg.Type != "" && !lo.Contains(supportedSSETypes, cfg.Type) {
   160  		return errUnsupportedSSEType
   161  	}
   162  
   163  	if _, err := parseKMSEncryptionContext(cfg.KMSEncryptionContext); err != nil {
   164  		return errInvalidSSEContext
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  // BuildThanosConfig builds the SSE config expected by the Thanos client.
   171  func (cfg *SSEConfig) BuildThanosConfig() (s3.SSEConfig, error) {
   172  	switch cfg.Type {
   173  	case "":
   174  		return s3.SSEConfig{}, nil
   175  	case SSEKMS:
   176  		encryptionCtx, err := parseKMSEncryptionContext(cfg.KMSEncryptionContext)
   177  		if err != nil {
   178  			return s3.SSEConfig{}, err
   179  		}
   180  
   181  		return s3.SSEConfig{
   182  			Type:                 s3.SSEKMS,
   183  			KMSKeyID:             cfg.KMSKeyID,
   184  			KMSEncryptionContext: encryptionCtx,
   185  		}, nil
   186  	case SSES3:
   187  		return s3.SSEConfig{
   188  			Type: s3.SSES3,
   189  		}, nil
   190  	default:
   191  		return s3.SSEConfig{}, errUnsupportedSSEType
   192  	}
   193  }
   194  
   195  // BuildMinioConfig builds the SSE config expected by the Minio client.
   196  func (cfg *SSEConfig) BuildMinioConfig() (encrypt.ServerSide, error) {
   197  	switch cfg.Type {
   198  	case "":
   199  		return nil, nil
   200  	case SSEKMS:
   201  		encryptionCtx, err := parseKMSEncryptionContext(cfg.KMSEncryptionContext)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  
   206  		if encryptionCtx == nil {
   207  			// To overcome a limitation in Minio which checks interface{} == nil.
   208  			return encrypt.NewSSEKMS(cfg.KMSKeyID, nil)
   209  		}
   210  		return encrypt.NewSSEKMS(cfg.KMSKeyID, encryptionCtx)
   211  	case SSES3:
   212  		return encrypt.NewSSE(), nil
   213  	default:
   214  		return nil, errUnsupportedSSEType
   215  	}
   216  }
   217  
   218  func parseKMSEncryptionContext(data string) (map[string]string, error) {
   219  	if data == "" {
   220  		return nil, nil
   221  	}
   222  
   223  	decoded := map[string]string{}
   224  	err := errors.Wrap(json.Unmarshal([]byte(data), &decoded), "unable to parse KMS encryption context")
   225  	return decoded, err
   226  }