github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/storage/bucket/s3/config.go (about)

     1  package s3
     2  
     3  import (
     4  	"encoding/json"
     5  	"flag"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/grafana/dskit/flagext"
    11  	"github.com/minio/minio-go/v7/pkg/encrypt"
    12  	"github.com/pkg/errors"
    13  	"github.com/thanos-io/thanos/pkg/objstore/s3"
    14  
    15  	bucket_http "github.com/grafana/loki/pkg/storage/bucket/http"
    16  	"github.com/grafana/loki/pkg/util"
    17  )
    18  
    19  const (
    20  	SignatureVersionV4 = "v4"
    21  	SignatureVersionV2 = "v2"
    22  
    23  	// SSEKMS config type constant to configure S3 server side encryption using KMS
    24  	// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html
    25  	SSEKMS = "SSE-KMS"
    26  
    27  	// SSES3 config type constant to configure S3 server side encryption with AES-256
    28  	// https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingServerSideEncryption.html
    29  	SSES3 = "SSE-S3"
    30  )
    31  
    32  var (
    33  	supportedSignatureVersions     = []string{SignatureVersionV4, SignatureVersionV2}
    34  	supportedSSETypes              = []string{SSEKMS, SSES3}
    35  	errUnsupportedSignatureVersion = errors.New("unsupported signature version")
    36  	errUnsupportedSSEType          = errors.New("unsupported S3 SSE type")
    37  	errInvalidSSEContext           = errors.New("invalid S3 SSE encryption context")
    38  )
    39  
    40  // HTTPConfig stores the http.Transport configuration for the s3 minio client.
    41  type HTTPConfig struct {
    42  	bucket_http.Config `yaml:",inline"`
    43  
    44  	// Allow upstream callers to inject a round tripper
    45  	Transport http.RoundTripper `yaml:"-"`
    46  }
    47  
    48  // RegisterFlagsWithPrefix registers the flags for s3 storage with the provided prefix
    49  func (cfg *HTTPConfig) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
    50  	cfg.Config.RegisterFlagsWithPrefix(prefix+"s3.", f)
    51  }
    52  
    53  // Config holds the config options for an S3 backend
    54  type Config struct {
    55  	Endpoint         string         `yaml:"endpoint"`
    56  	Region           string         `yaml:"region"`
    57  	BucketName       string         `yaml:"bucket_name"`
    58  	SecretAccessKey  flagext.Secret `yaml:"secret_access_key"`
    59  	AccessKeyID      string         `yaml:"access_key_id"`
    60  	Insecure         bool           `yaml:"insecure"`
    61  	SignatureVersion string         `yaml:"signature_version"`
    62  
    63  	SSE  SSEConfig  `yaml:"sse"`
    64  	HTTP HTTPConfig `yaml:"http"`
    65  }
    66  
    67  // RegisterFlags registers the flags for s3 storage with the provided prefix
    68  func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
    69  	cfg.RegisterFlagsWithPrefix("", f)
    70  }
    71  
    72  // RegisterFlagsWithPrefix registers the flags for s3 storage with the provided prefix
    73  func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
    74  	f.StringVar(&cfg.AccessKeyID, prefix+"s3.access-key-id", "", "S3 access key ID")
    75  	f.Var(&cfg.SecretAccessKey, prefix+"s3.secret-access-key", "S3 secret access key")
    76  	f.StringVar(&cfg.BucketName, prefix+"s3.bucket-name", "", "S3 bucket name")
    77  	f.StringVar(&cfg.Region, prefix+"s3.region", "", "S3 region. If unset, the client will issue a S3 GetBucketLocation API call to autodetect it.")
    78  	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.")
    79  	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.")
    80  	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, ", ")))
    81  	cfg.SSE.RegisterFlagsWithPrefix(prefix+"s3.sse.", f)
    82  	cfg.HTTP.RegisterFlagsWithPrefix(prefix, f)
    83  }
    84  
    85  // Validate config and returns error on failure
    86  func (cfg *Config) Validate() error {
    87  	if !util.StringsContain(supportedSignatureVersions, cfg.SignatureVersion) {
    88  		return errUnsupportedSignatureVersion
    89  	}
    90  
    91  	return cfg.SSE.Validate()
    92  }
    93  
    94  // SSEConfig configures S3 server side encryption
    95  // struct that is going to receive user input (through config file or CLI)
    96  type SSEConfig struct {
    97  	Type                 string `yaml:"type"`
    98  	KMSKeyID             string `yaml:"kms_key_id"`
    99  	KMSEncryptionContext string `yaml:"kms_encryption_context"`
   100  }
   101  
   102  func (cfg *SSEConfig) RegisterFlags(f *flag.FlagSet) {
   103  	cfg.RegisterFlagsWithPrefix("", f)
   104  }
   105  
   106  // RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet
   107  func (cfg *SSEConfig) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
   108  	f.StringVar(&cfg.Type, prefix+"type", "", fmt.Sprintf("Enable AWS Server Side Encryption. Supported values: %s.", strings.Join(supportedSSETypes, ", ")))
   109  	f.StringVar(&cfg.KMSKeyID, prefix+"kms-key-id", "", "KMS Key ID used to encrypt objects in S3")
   110  	f.StringVar(&cfg.KMSEncryptionContext, prefix+"kms-encryption-context", "", "KMS Encryption Context used for object encryption. It expects JSON formatted string.")
   111  }
   112  
   113  func (cfg *SSEConfig) Validate() error {
   114  	if cfg.Type != "" && !util.StringsContain(supportedSSETypes, cfg.Type) {
   115  		return errUnsupportedSSEType
   116  	}
   117  
   118  	if _, err := parseKMSEncryptionContext(cfg.KMSEncryptionContext); err != nil {
   119  		return errInvalidSSEContext
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // BuildThanosConfig builds the SSE config expected by the Thanos client.
   126  func (cfg *SSEConfig) BuildThanosConfig() (s3.SSEConfig, error) {
   127  	switch cfg.Type {
   128  	case "":
   129  		return s3.SSEConfig{}, nil
   130  	case SSEKMS:
   131  		encryptionCtx, err := parseKMSEncryptionContext(cfg.KMSEncryptionContext)
   132  		if err != nil {
   133  			return s3.SSEConfig{}, err
   134  		}
   135  
   136  		return s3.SSEConfig{
   137  			Type:                 s3.SSEKMS,
   138  			KMSKeyID:             cfg.KMSKeyID,
   139  			KMSEncryptionContext: encryptionCtx,
   140  		}, nil
   141  	case SSES3:
   142  		return s3.SSEConfig{
   143  			Type: s3.SSES3,
   144  		}, nil
   145  	default:
   146  		return s3.SSEConfig{}, errUnsupportedSSEType
   147  	}
   148  }
   149  
   150  // BuildMinioConfig builds the SSE config expected by the Minio client.
   151  func (cfg *SSEConfig) BuildMinioConfig() (encrypt.ServerSide, error) {
   152  	switch cfg.Type {
   153  	case "":
   154  		return nil, nil
   155  	case SSEKMS:
   156  		encryptionCtx, err := parseKMSEncryptionContext(cfg.KMSEncryptionContext)
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  
   161  		if encryptionCtx == nil {
   162  			// To overcome a limitation in Minio which checks interface{} == nil.
   163  			return encrypt.NewSSEKMS(cfg.KMSKeyID, nil)
   164  		}
   165  		return encrypt.NewSSEKMS(cfg.KMSKeyID, encryptionCtx)
   166  	case SSES3:
   167  		return encrypt.NewSSE(), nil
   168  	default:
   169  		return nil, errUnsupportedSSEType
   170  	}
   171  }
   172  
   173  func parseKMSEncryptionContext(data string) (map[string]string, error) {
   174  	if data == "" {
   175  		return nil, nil
   176  	}
   177  
   178  	decoded := map[string]string{}
   179  	err := errors.Wrap(json.Unmarshal([]byte(data), &decoded), "unable to parse KMS encryption context")
   180  	return decoded, err
   181  }