Skip to content

Commit c4dace5

Browse files
feat(operator): allow users to configure virtual-host-style for S3 secrets (#17363)
Signed-off-by: Joao Marcal <jmarcal@redhat.com> Co-authored-by: Robert Jacob <rojacob@redhat.com>
1 parent 0b2bae8 commit c4dace5

4 files changed

Lines changed: 97 additions & 17 deletions

File tree

‎operator/docs/lokistack/object_storage.md‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ weight: 100
1313
toc: true
1414
---
1515

16-
Loki Operator supports [AWS S3](https://aws.amazon.com/), [Azure](https://azure.microsoft.com), [GCS](https://cloud.google.com/), [Minio](https://min.io/), [OpenShift Data Foundation](https://www.redhat.com/en/technologies/cloud-computing/openshift-data-foundation) and [Swift](https://docs.openstack.org/swift/latest/) for LokiStack object storage.
16+
Loki Operator supports [AWS S3](https://aws.amazon.com/), [Azure](https://azure.microsoft.com), [GCS](https://cloud.google.com/), [Minio](https://min.io/), [OpenShift Data Foundation](https://www.redhat.com/en/technologies/cloud-computing/openshift-data-foundation) and [Swift](https://docs.openstack.org/swift/latest/) for LokiStack object storage.
1717

1818
_Note_: Upon setting up LokiStack for any object storage provider, you should configure a logging collector that references the LokiStack in order to view the logs.
1919

@@ -46,11 +46,12 @@ _Note_: Upon setting up LokiStack for any object storage provider, you should co
4646
--from-literal=endpoint="<AWS_BUCKET_ENDPOINT>" \
4747
--from-literal=access_key_id="<AWS_ACCESS_KEY_ID>" \
4848
--from-literal=access_key_secret="<AWS_ACCESS_KEY_SECRET>" \
49+
--from-literal=region="<AWS_REGION_YOUR_BUCKET_LIVES_IN>" \
4950
--from-literal=sse_type="SSE-KMS" \
5051
--from-literal=sse_kms_key_id="<AWS_SSE_KMS_KEY_ID>" \
5152
--from-literal=sse_kms_encryption_context="<OPTIONAL_AWS_SSE_KMS_ENCRYPTION_CONTEXT_JSON>"
5253
```
53-
54+
5455
See also official docs on [AWS KMS Key ID](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#key-id) and [AWS KMS Encryption Context](https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#encrypt_context) (**Note:** Only content without newlines allowed, because it is exposed via environment variable to the containers).
5556

5657
or with `SSE-S3` encryption
@@ -61,9 +62,28 @@ _Note_: Upon setting up LokiStack for any object storage provider, you should co
6162
--from-literal=endpoint="<AWS_BUCKET_ENDPOINT>" \
6263
--from-literal=access_key_id="<AWS_ACCESS_KEY_ID>" \
6364
--from-literal=access_key_secret="<AWS_ACCESS_KEY_SECRET>" \
65+
--from-literal=region="<AWS_REGION_YOUR_BUCKET_LIVES_IN>" \
6466
--from-literal=sse_type="SSE-S3"
6567
```
6668

69+
Additionally, you can control the S3 URL style access behavior with `force_path_style` parameter:
70+
71+
```console
72+
kubectl create secret generic lokistack-dev-s3 \
73+
--from-literal=bucketnames="<BUCKET_NAME>" \
74+
--from-literal=endpoint="<AWS_BUCKET_ENDPOINT>" \
75+
--from-literal=access_key_id="<AWS_ACCESS_KEY_ID>" \
76+
--from-literal=access_key_secret="<AWS_ACCESS_KEY_SECRET>" \
77+
--from-literal=region="<AWS_REGION_YOUR_BUCKET_LIVES_IN>" \
78+
--from-literal=force_path_style="true"
79+
```
80+
81+
By default:
82+
* AWS endpoints (ending with `.amazonaws.com`) use virtual hosted style (`force_path_style=false`)
83+
* Non-AWS endpoints use path style (`force_path_style=true`)
84+
85+
Set `force_path_style` to `false` if you need to use virtual-hosted style with non-AWS S3 compatible services.
86+
6787
where `lokistack-dev-s3` is the secret name.
6888

6989
* Create an instance of [LokiStack](../hack/lokistack_dev.yaml) by referencing the secret name and type as `s3`:
@@ -130,7 +150,7 @@ _Note_: Upon setting up LokiStack for any object storage provider, you should co
130150
--from-literal=bucketname="<BUCKET_NAME>" \
131151
--from-file=key.json="<PATH/TO/KEY.JSON>"
132152
```
133-
153+
134154
where `lokistack-dev-gcs` is the secret name, `<BUCKET_NAME>` is the name of bucket created in requirements step and `<PATH/TO/KEY.JSON>` is the file path where the `key.json` was copied to.
135155

136156
* Create an instance of [LokiStack](../hack/lokistack_dev.yaml) by referencing the secret name and type as `gcs`:

‎operator/internal/handlers/internal/storage/secrets.go‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var (
4343
errS3EndpointNoURL = errors.New("endpoint for S3 must be an HTTP or HTTPS URL")
4444
errS3EndpointUnsupportedScheme = errors.New("scheme of S3 endpoint URL is unsupported")
4545
errS3EndpointAWSInvalid = errors.New("endpoint for AWS S3 must include correct region")
46+
errS3ForcePathStyleInvalid = errors.New(`forcepathstyle must be "true" or "false"`)
4647

4748
errGCPParseCredentialsFile = errors.New("gcp storage secret cannot be parsed from JSON content")
4849
errGCPWrongCredentialSourceFile = errors.New("credential source in secret needs to point to token file")
@@ -410,10 +411,23 @@ func extractS3ConfigSecret(s *corev1.Secret, credentialMode lokiv1.CredentialMod
410411
roleArn = s.Data[storage.KeyAWSRoleArn]
411412
audience = s.Data[storage.KeyAWSAudience]
412413
// Optional fields
413-
region = s.Data[storage.KeyAWSRegion]
414-
forcePathStyle = !strings.HasSuffix(string(endpoint), awsEndpointSuffix)
414+
region = s.Data[storage.KeyAWSRegion]
415415
)
416416

417+
// Determine if we should use path style URLs for S3
418+
// default to false for non-AWS endpoints
419+
forcePathStyle := !strings.HasSuffix(string(endpoint), awsEndpointSuffix)
420+
// Check if the user has specified force_path_style
421+
if configForcePathStyle, ok := s.Data[storage.KeyAWSForcePathStyle]; ok {
422+
strForcePathStyle := string(configForcePathStyle)
423+
switch strForcePathStyle {
424+
case "true", "false":
425+
forcePathStyle = strForcePathStyle == "true"
426+
default:
427+
return nil, fmt.Errorf("%w: %s", errS3ForcePathStyleInvalid, strForcePathStyle)
428+
}
429+
}
430+
417431
sseCfg, err := extractS3SSEConfig(s.Data)
418432
if err != nil {
419433
return nil, err

‎operator/internal/handlers/internal/storage/secrets_test.go‎

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -654,14 +654,15 @@ func TestS3Extract(t *testing.T) {
654654
}
655655
}
656656

657-
func TestS3Extract_S3ForcePathStyle(t *testing.T) {
657+
func TestS3Extract_ForcePathStyle(t *testing.T) {
658658
tt := []struct {
659659
desc string
660660
secret *corev1.Secret
661661
wantOptions *storage.S3StorageConfig
662+
wantError string
662663
}{
663664
{
664-
desc: "aws s3 endpoint",
665+
desc: "aws endpoint without forcepathstyle specified (default behavior)",
665666
secret: &corev1.Secret{
666667
ObjectMeta: metav1.ObjectMeta{Name: "test"},
667668
Data: map[string][]byte{
@@ -673,38 +674,81 @@ func TestS3Extract_S3ForcePathStyle(t *testing.T) {
673674
},
674675
},
675676
wantOptions: &storage.S3StorageConfig{
676-
Endpoint: "https://s3.region.amazonaws.com",
677-
Region: "region",
678-
Buckets: "this,that",
677+
Endpoint: "https://s3.region.amazonaws.com",
678+
Region: "region",
679+
Buckets: "this,that",
680+
ForcePathStyle: false, // defaults to virtual style for AWS endpoints
679681
},
680682
},
681683
{
682-
desc: "non-aws s3 endpoint",
684+
desc: "non-aws s3 endpoint without forcepathstyle specified (default behavior)",
683685
secret: &corev1.Secret{
684686
ObjectMeta: metav1.ObjectMeta{Name: "test"},
685687
Data: map[string][]byte{
686-
"endpoint": []byte("https://test.default.svc.cluster.local:9000"),
688+
"endpoint": []byte("http://minio:9000"),
689+
"region": []byte(""),
690+
"bucketnames": []byte("this,that"),
691+
"access_key_id": []byte("id"),
692+
"access_key_secret": []byte("secret"),
693+
},
694+
},
695+
wantOptions: &storage.S3StorageConfig{
696+
Endpoint: "http://minio:9000",
697+
Region: "",
698+
Buckets: "this,that",
699+
ForcePathStyle: true, // defaults to path style for non-AWS endpoints
700+
},
701+
},
702+
{
703+
desc: "aws s3 endpoint with forcepathstyle=true",
704+
secret: &corev1.Secret{
705+
ObjectMeta: metav1.ObjectMeta{Name: "test"},
706+
Data: map[string][]byte{
707+
"endpoint": []byte("https://s3.region.amazonaws.com"),
687708
"region": []byte("region"),
688709
"bucketnames": []byte("this,that"),
689710
"access_key_id": []byte("id"),
690711
"access_key_secret": []byte("secret"),
712+
"forcepathstyle": []byte("true"),
691713
},
692714
},
693715
wantOptions: &storage.S3StorageConfig{
694-
Endpoint: "https://test.default.svc.cluster.local:9000",
716+
Endpoint: "https://s3.region.amazonaws.com",
695717
Region: "region",
696718
Buckets: "this,that",
697719
ForcePathStyle: true,
698720
},
699721
},
722+
{
723+
desc: "invalid forcepathstyle value",
724+
secret: &corev1.Secret{
725+
ObjectMeta: metav1.ObjectMeta{Name: "test"},
726+
Data: map[string][]byte{
727+
"endpoint": []byte("https://s3.region.amazonaws.com"),
728+
"region": []byte("region"),
729+
"bucketnames": []byte("this,that"),
730+
"access_key_id": []byte("id"),
731+
"access_key_secret": []byte("secret"),
732+
"forcepathstyle": []byte("yes"),
733+
},
734+
},
735+
wantError: `forcepathstyle must be "true" or "false": yes`,
736+
},
700737
}
701738

702739
for _, tc := range tt {
703740
t.Run(tc.desc, func(t *testing.T) {
704-
t.Parallel()
705-
options, err := extractS3ConfigSecret(tc.secret, lokiv1.CredentialModeStatic)
706-
require.NoError(t, err)
707-
require.Equal(t, tc.wantOptions, options)
741+
got, err := extractS3ConfigSecret(tc.secret, lokiv1.CredentialModeStatic)
742+
if tc.wantError == "" {
743+
require.NoError(t, err)
744+
745+
require.Equal(t, tc.wantOptions.ForcePathStyle, got.ForcePathStyle)
746+
require.Equal(t, tc.wantOptions.Endpoint, got.Endpoint)
747+
require.Equal(t, tc.wantOptions.Region, got.Region)
748+
require.Equal(t, tc.wantOptions.Buckets, got.Buckets)
749+
} else {
750+
require.EqualError(t, err, tc.wantError)
751+
}
708752
})
709753
}
710754
}

‎operator/internal/manifests/storage/var.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ const (
5959
KeyAWSEndpoint = "endpoint"
6060
// KeyAWSRegion is the secret data key for the AWS region.
6161
KeyAWSRegion = "region"
62+
// KeyAWSForcePathStyle is the secret data key for controlling S3 force the requests to use path-style addressing
63+
KeyAWSForcePathStyle = "forcepathstyle"
6264
// KeyAWSSSEType is the secret data key for the AWS server-side encryption type.
6365
KeyAWSSSEType = "sse_type"
6466
// KeyAWSSseKmsEncryptionContext is the secret data key for the AWS SSE KMS encryption context.

0 commit comments

Comments
 (0)