Skip to content

Commit

Permalink
S3: add support for s3 session token (grafana#8474)
Browse files Browse the repository at this point in the history
We want to automate the access account rotation. This is that easy at moment with service accounts in MinIO. Our currently life time of tokens is 3 months with MinIO STS works this fine.

Signed-off-by: Jan Jansen <jan.jansen@gdata.de>
  • Loading branch information
farodin91 authored Feb 28, 2023
1 parent 6bf782b commit 84c5e20
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* [8259](https://github.com/grafana/loki/pull/8259) **mar4uk**: Extract push.proto from the logproto package to the separate module.
* [7906](https://github.com/grafana/loki/pull/7906) **kavirajk**: Add API endpoint that formats LogQL expressions and support new `fmt` subcommand in `logcli` to format LogQL query.
* [6675](https://github.com/grafana/loki/pull/6675) **btaani**: Add logfmt expression parser for selective extraction of labels from logfmt formatted logs
* [8474](https://github.com/grafana/loki/pull/8474) **farodin91**: Add support for short-lived S3 session tokens

##### Fixes

Expand Down
8 changes: 8 additions & 0 deletions docs/sources/configuration/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3658,6 +3658,10 @@ dynamodb:
# CLI flag: -s3.secret-access-key
[secret_access_key: <string> | default = ""]

# AWS Session Token
# CLI flag: -s3.session-token
[session_token: <string> | default = ""]

# Disable https on s3 connection.
# CLI flag: -s3.insecure
[insecure: <boolean> | default = false]
Expand Down Expand Up @@ -3928,6 +3932,10 @@ The `s3_storage_config` block configures the connection to Amazon S3 object stor
# CLI flag: -<prefix>.storage.s3.secret-access-key
[secret_access_key: <string> | default = ""]

# AWS Session Token
# CLI flag: -<prefix>.storage.s3.session-token
[session_token: <string> | default = ""]

# Disable https on s3 connection.
# CLI flag: -<prefix>.storage.s3.insecure
[insecure: <boolean> | default = false]
Expand Down
62 changes: 61 additions & 1 deletion pkg/loki/config_wrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ memberlist:
assert.ErrorIs(t, err, ErrTooManyStorageConfigs)
})

t.Run("when common s3 storage config is provided, ruler and storage config are defaulted to use it", func(t *testing.T) {
t.Run("when common s3 storage config is provided (with empty session token), ruler and storage config are defaulted to use it", func(t *testing.T) {
s3Config := `common:
storage:
s3:
Expand Down Expand Up @@ -271,6 +271,66 @@ memberlist:
assert.Equal(t, "us-east1", actual.Region)
assert.Equal(t, "abc123", actual.AccessKeyID)
assert.Equal(t, "def789", actual.SecretAccessKey.String())
assert.Equal(t, "", actual.SessionToken.String())
assert.Equal(t, true, actual.Insecure)
assert.Equal(t, false, actual.SSEEncryption)
assert.Equal(t, 5*time.Minute, actual.HTTPConfig.ResponseHeaderTimeout)
assert.Equal(t, false, actual.HTTPConfig.InsecureSkipVerify)

assert.Equal(t, aws.SignatureVersionV4, actual.SignatureVersion,
"signature version should equal default value")
assert.Equal(t, 90*time.Second, actual.HTTPConfig.IdleConnTimeout,
"idle connection timeout should equal default value")
}

// should remain empty
assert.EqualValues(t, defaults.Ruler.StoreConfig.Azure, config.Ruler.StoreConfig.Azure)
assert.EqualValues(t, defaults.Ruler.StoreConfig.GCS, config.Ruler.StoreConfig.GCS)
assert.EqualValues(t, defaults.Ruler.StoreConfig.Swift, config.Ruler.StoreConfig.Swift)
assert.EqualValues(t, defaults.Ruler.StoreConfig.Local, config.Ruler.StoreConfig.Local)
assert.EqualValues(t, defaults.Ruler.StoreConfig.BOS, config.Ruler.StoreConfig.BOS)
// should remain empty
assert.EqualValues(t, defaults.StorageConfig.AzureStorageConfig, config.StorageConfig.AzureStorageConfig)
assert.EqualValues(t, defaults.StorageConfig.GCSConfig, config.StorageConfig.GCSConfig)
assert.EqualValues(t, defaults.StorageConfig.Swift, config.StorageConfig.Swift)
assert.EqualValues(t, defaults.StorageConfig.FSConfig, config.StorageConfig.FSConfig)
assert.EqualValues(t, defaults.StorageConfig.BOSStorageConfig, config.StorageConfig.BOSStorageConfig)
})

t.Run("when common s3 storage config is provided (with session token), ruler and storage config are defaulted to use it", func(t *testing.T) {
s3Config := `common:
storage:
s3:
s3: s3://foo-bucket/example
endpoint: s3://foo-bucket
region: us-east1
access_key_id: abc123
secret_access_key: def789
session_token: 456abc
insecure: true
http_config:
response_header_timeout: 5m`

config, defaults := testContext(s3Config, nil)

expected, err := url.Parse("s3://foo-bucket/example")
require.NoError(t, err)

assert.Equal(t, "s3", config.Ruler.StoreConfig.Type)

for _, actual := range []aws.S3Config{
config.Ruler.StoreConfig.S3,
config.StorageConfig.AWSStorageConfig.S3Config,
} {
require.NotNil(t, actual.S3.URL)
assert.Equal(t, *expected, *actual.S3.URL)

assert.Equal(t, false, actual.S3ForcePathStyle)
assert.Equal(t, "s3://foo-bucket", actual.Endpoint)
assert.Equal(t, "us-east1", actual.Region)
assert.Equal(t, "abc123", actual.AccessKeyID)
assert.Equal(t, "def789", actual.SecretAccessKey.String())
assert.Equal(t, "456abc", actual.SessionToken.String())
assert.Equal(t, true, actual.Insecure)
assert.Equal(t, false, actual.SSEEncryption)
assert.Equal(t, 5*time.Minute, actual.HTTPConfig.ResponseHeaderTimeout)
Expand Down
1 change: 1 addition & 0 deletions pkg/storage/bucket/s3/bucket_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func newS3Config(cfg Config) (s3.Config, error) {
Region: cfg.Region,
AccessKey: cfg.AccessKeyID,
SecretKey: cfg.SecretAccessKey.String(),
SessionToken: cfg.SessionToken.String(),
Insecure: cfg.Insecure,
SSEConfig: sseCfg,
PutUserMetadata: map[string]string{awsStorageClassHeader: cfg.StorageClass},
Expand Down
2 changes: 2 additions & 0 deletions pkg/storage/bucket/s3/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type Config struct {
Region string `yaml:"region"`
BucketName string `yaml:"bucket_name"`
SecretAccessKey flagext.Secret `yaml:"secret_access_key"`
SessionToken flagext.Secret `yaml:"session_token"`
AccessKeyID string `yaml:"access_key_id"`
Insecure bool `yaml:"insecure"`
SignatureVersion string `yaml:"signature_version"`
Expand All @@ -76,6 +77,7 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
f.StringVar(&cfg.AccessKeyID, prefix+"s3.access-key-id", "", "S3 access key ID")
f.Var(&cfg.SecretAccessKey, prefix+"s3.secret-access-key", "S3 secret access key")
f.Var(&cfg.SessionToken, prefix+"s3.session-token", "S3 session token")
f.StringVar(&cfg.BucketName, prefix+"s3.bucket-name", "", "S3 bucket name")
f.StringVar(&cfg.Region, prefix+"s3.region", "", "S3 region. If unset, the client will issue a S3 GetBucketLocation API call to autodetect it.")
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.")
Expand Down
4 changes: 3 additions & 1 deletion pkg/storage/chunk/client/aws/s3_storage_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ type S3Config struct {
Region string `yaml:"region"`
AccessKeyID string `yaml:"access_key_id"`
SecretAccessKey flagext.Secret `yaml:"secret_access_key"`
SessionToken flagext.Secret `yaml:"session_token"`
Insecure bool `yaml:"insecure"`
SSEEncryption bool `yaml:"sse_encryption"`
HTTPConfig HTTPConfig `yaml:"http_config"`
Expand Down Expand Up @@ -108,6 +109,7 @@ func (cfg *S3Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
f.StringVar(&cfg.Region, prefix+"s3.region", "", "AWS region to use.")
f.StringVar(&cfg.AccessKeyID, prefix+"s3.access-key-id", "", "AWS Access Key ID")
f.Var(&cfg.SecretAccessKey, prefix+"s3.secret-access-key", "AWS Secret Access Key")
f.Var(&cfg.SessionToken, prefix+"s3.session-token", "AWS Session Token")
f.BoolVar(&cfg.Insecure, prefix+"s3.insecure", false, "Disable https on s3 connection.")

// TODO Remove in Cortex 1.10.0
Expand Down Expand Up @@ -248,7 +250,7 @@ func buildS3Client(cfg S3Config, hedgingCfg hedging.Config, hedging bool) (*s3.S
}

if cfg.AccessKeyID != "" && cfg.SecretAccessKey.String() != "" {
creds := credentials.NewStaticCredentials(cfg.AccessKeyID, cfg.SecretAccessKey.String(), "")
creds := credentials.NewStaticCredentials(cfg.AccessKeyID, cfg.SecretAccessKey.String(), cfg.SessionToken.String())
s3Config = s3Config.WithCredentials(creds)
}

Expand Down
17 changes: 17 additions & 0 deletions pkg/storage/chunk/client/aws/s3_storage_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,5 +176,22 @@ secret_access_key: secret access key

require.Equal(t, underTest.AccessKeyID, "access key id")
require.Equal(t, underTest.SecretAccessKey.String(), "secret access key")
require.Equal(t, underTest.SessionToken.String(), "")

}

func Test_ConfigParsesCredentialsInlineWithSessionToken(t *testing.T) {
var underTest = S3Config{}
yamlCfg := `
access_key_id: access key id
secret_access_key: secret access key
session_token: session token
`
err := yaml.Unmarshal([]byte(yamlCfg), &underTest)
require.NoError(t, err)

require.Equal(t, underTest.AccessKeyID, "access key id")
require.Equal(t, underTest.SecretAccessKey.String(), "secret access key")
require.Equal(t, underTest.SessionToken.String(), "session token")

}

0 comments on commit 84c5e20

Please sign in to comment.