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 }