Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

operator: Fix storing authentication credentials in the Loki ConfigMap #11357

Merged
merged 22 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b0a39b7
Add support for S3 credentials env vars
periklis Dec 1, 2023
fda0348
Add support for Azure credentials env vars
periklis Dec 1, 2023
49a694d
Add support for AlibabaCloud credentials env vars
periklis Dec 1, 2023
a59e5bf
Add support for OpenStack Swift credentials env vars
periklis Dec 1, 2023
2233f16
Remove reading credentials from operator memory
periklis Dec 1, 2023
19d0ba8
Merge branch 'main' into operator-hide-sensitive-vars
periklis Dec 1, 2023
c61e075
Replace strings with consts for all objstore secret access keys
periklis Dec 1, 2023
fccb7bd
Fix azure env var values
periklis Dec 1, 2023
aef8afa
Merge branch 'main' into operator-hide-sensitive-vars
periklis Dec 1, 2023
72ecf6d
Add changelog entry
periklis Dec 1, 2023
551546e
Code cleanup
periklis Dec 1, 2023
6737754
Merge branch 'main' into operator-hide-sensitive-vars
periklis Dec 7, 2023
e1608e0
Apply code review suggestions
periklis Dec 7, 2023
16c6016
Expose AWS SSE KMS encryption context only per env-var
periklis Dec 7, 2023
73ffd7c
Update operator/CHANGELOG.md
periklis Dec 11, 2023
d4223f1
Restart pods on object storage secret contents change
periklis Dec 11, 2023
3fc8f5e
Improve hash content concatenation
periklis Dec 11, 2023
4e9e752
Add warning about no-newlines allowed for KMS encryption context
periklis Dec 11, 2023
8659a52
Merge branch 'main' into operator-hide-sensitive-vars
periklis Dec 11, 2023
625b6f4
Rename SecretHash to SecretSHA1
periklis Dec 11, 2023
f6857f6
Refactor secret hash calculation
xperimental Dec 11, 2023
302f428
Merge branch 'main' into operator-hide-sensitive-vars
periklis Dec 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion operator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## Main

- [11357](https://github.com/grafana/loki/pull/11357) **perklis**: Fix storing authentication credentials in the Loki ConfigMap
- [11357](https://github.com/grafana/loki/pull/11357) **periklis**: Fix storing authentication credentials in the Loki ConfigMap
- [11393](https://github.com/grafana/loki/pull/11393) **periklis**: Add infra annotations for OpenShift based deployments
- [11094](https://github.com/grafana/loki/pull/11094) **periklis**: Add support for blocking queries per tenant
- [11288](https://github.com/grafana/loki/pull/11288) **periklis**: Fix custom CA for object-store in ruler component
Expand Down
2 changes: 1 addition & 1 deletion operator/docs/lokistack/object_storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ _Note_: Upon setting up LokiStack for any object storage provider, you should co
--from-literal=sse_kms_encryption_context="<OPTIONAL_AWS_SSE_KMS_ENCRYPTION_CONTEXT_JSON>"
```

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).
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).

or with `SSE-S3` encryption

Expand Down
141 changes: 102 additions & 39 deletions operator/internal/handlers/internal/storage/secrets.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
package storage

import (
"bytes"
"crypto/sha1"
"fmt"

"github.com/ViaQ/logerr/v2/kverrors"
corev1 "k8s.io/api/core/v1"

lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/manifests/storage"
)

var hashSeparator = []byte(",")

// ExtractSecret reads a k8s secret into a manifest object storage struct if valid.
func ExtractSecret(s *corev1.Secret, secretType lokiv1.ObjectStorageSecretType) (*storage.Options, error) {
var err error
Expand All @@ -18,15 +24,15 @@ func ExtractSecret(s *corev1.Secret, secretType lokiv1.ObjectStorageSecretType)

switch secretType {
case lokiv1.ObjectStorageSecretAzure:
storageOpts.Azure, err = extractAzureConfigSecret(s)
storageOpts.Azure, storageOpts.SecretSHA1, err = extractAzureConfigSecret(s)
case lokiv1.ObjectStorageSecretGCS:
storageOpts.GCS, err = extractGCSConfigSecret(s)
storageOpts.GCS, storageOpts.SecretSHA1, err = extractGCSConfigSecret(s)
case lokiv1.ObjectStorageSecretS3:
storageOpts.S3, err = extractS3ConfigSecret(s)
storageOpts.S3, storageOpts.SecretSHA1, err = extractS3ConfigSecret(s)
case lokiv1.ObjectStorageSecretSwift:
storageOpts.Swift, err = extractSwiftConfigSecret(s)
storageOpts.Swift, storageOpts.SecretSHA1, err = extractSwiftConfigSecret(s)
case lokiv1.ObjectStorageSecretAlibabaCloud:
storageOpts.AlibabaCloud, err = extractAlibabaCloudConfigSecret(s)
storageOpts.AlibabaCloud, storageOpts.SecretSHA1, err = extractAlibabaCloudConfigSecret(s)
default:
return nil, kverrors.New("unknown secret type", "type", secretType)
}
Expand All @@ -37,86 +43,118 @@ func ExtractSecret(s *corev1.Secret, secretType lokiv1.ObjectStorageSecretType)
return &storageOpts, nil
}

func extractAzureConfigSecret(s *corev1.Secret) (*storage.AzureStorageConfig, error) {
func extractAzureConfigSecret(s *corev1.Secret) (*storage.AzureStorageConfig, string, error) {
// Extract and validate mandatory fields
env := s.Data[storage.KeyAzureEnvironmentName]
if len(env) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAzureEnvironmentName)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAzureEnvironmentName)
}
container := s.Data[storage.KeyAzureStorageContainerName]
if len(container) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAzureStorageContainerName)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAzureStorageContainerName)
}
name := s.Data[storage.KeyAzureStorageAccountName]
if len(name) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAzureStorageAccountName)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAzureStorageAccountName)
}
key := s.Data[storage.KeyAzureStorageAccountKey]
if len(key) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAzureStorageAccountKey)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAzureStorageAccountKey)
}

// Extract and validate optional fields
endpointSuffix := s.Data[storage.KeyAzureStorageEndpointSuffix]

contents := bytes.Join([][]byte{env, container, name, key, endpointSuffix}, hashSeparator)

h := sha1.New()
_, err := h.Write([]byte(contents))
if err != nil {
return nil, "", kverrors.Wrap(err, "failed hashing azure secret contents")
}
sha1C := fmt.Sprintf("%x", h.Sum(nil))
periklis marked this conversation as resolved.
Show resolved Hide resolved

return &storage.AzureStorageConfig{
Env: string(env),
Container: string(container),
EndpointSuffix: string(endpointSuffix),
}, nil
}, sha1C, nil
}

func extractGCSConfigSecret(s *corev1.Secret) (*storage.GCSStorageConfig, error) {
func extractGCSConfigSecret(s *corev1.Secret) (*storage.GCSStorageConfig, string, error) {
// Extract and validate mandatory fields
bucket := s.Data[storage.KeyGCPStorageBucketName]
if len(bucket) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyGCPStorageBucketName)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyGCPStorageBucketName)
}

// Check if google authentication credentials is provided
keyJSON := s.Data[storage.KeyGCPServiceAccountKeyFilename]
if len(keyJSON) == 0 {
return nil, kverrors.New("missing google authentication credentials", "field", storage.KeyGCPServiceAccountKeyFilename)
return nil, "", kverrors.New("missing google authentication credentials", "field", storage.KeyGCPServiceAccountKeyFilename)
}

contents := bytes.Join([][]byte{bucket, keyJSON}, hashSeparator)

h := sha1.New()
_, err := h.Write([]byte(contents))
if err != nil {
return nil, "", kverrors.Wrap(err, "failed hashing gcs secret contents")
}
sha1C := fmt.Sprintf("%x", h.Sum(nil))

return &storage.GCSStorageConfig{
Bucket: string(bucket),
}, nil
}, sha1C, nil
}

func extractS3ConfigSecret(s *corev1.Secret) (*storage.S3StorageConfig, error) {
func extractS3ConfigSecret(s *corev1.Secret) (*storage.S3StorageConfig, string, error) {
// Extract and validate mandatory fields
endpoint := s.Data[storage.KeyAWSEndpoint]
if len(endpoint) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAWSEndpoint)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAWSEndpoint)
}
buckets := s.Data[storage.KeyAWSBucketNames]
if len(buckets) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAWSBucketNames)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAWSBucketNames)
}
id := s.Data[storage.KeyAWSAccessKeyID]
if len(id) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAWSAccessKeyID)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAWSAccessKeyID)
}
secret := s.Data[storage.KeyAWSAccessKeySecret]
if len(secret) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAWSAccessKeySecret)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAWSAccessKeySecret)
}

// Extract and validate optional fields
periklis marked this conversation as resolved.
Show resolved Hide resolved
region := s.Data[storage.KeyAWSRegion]

sseCfg, err := extractS3SSEConfig(s.Data)
if err != nil {
return nil, err
return nil, "", err
}

contents := bytes.Join([][]byte{
endpoint, buckets, id, secret, region,
[]byte(sseCfg.Type),
[]byte(sseCfg.KMSKeyID),
[]byte(sseCfg.KMSEncryptionContext),
}, hashSeparator)

h := sha1.New()
_, err = h.Write(contents)
if err != nil {
return nil, "", kverrors.Wrap(err, "failed hashing s3 secret contents")
}
sha1C := fmt.Sprintf("%x", h.Sum(nil))

return &storage.S3StorageConfig{
Endpoint: string(endpoint),
Buckets: string(buckets),
Region: string(region),
SSE: sseCfg,
}, nil
}, sha1C, nil
}

func extractS3SSEConfig(d map[string][]byte) (storage.S3SSEConfig, error) {
Expand Down Expand Up @@ -148,43 +186,43 @@ func extractS3SSEConfig(d map[string][]byte) (storage.S3SSEConfig, error) {
}, nil
}

func extractSwiftConfigSecret(s *corev1.Secret) (*storage.SwiftStorageConfig, error) {
func extractSwiftConfigSecret(s *corev1.Secret) (*storage.SwiftStorageConfig, string, error) {
// Extract and validate mandatory fields
url := s.Data[storage.KeySwiftAuthURL]
if len(url) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftAuthURL)
return nil, "", kverrors.New("missing secret field", "field", storage.KeySwiftAuthURL)
}
username := s.Data[storage.KeySwiftUsername]
if len(username) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftUsername)
return nil, "", kverrors.New("missing secret field", "field", storage.KeySwiftUsername)
}
userDomainName := s.Data[storage.KeySwiftUserDomainName]
if len(userDomainName) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftUserDomainName)
return nil, "", kverrors.New("missing secret field", "field", storage.KeySwiftUserDomainName)
}
userDomainID := s.Data[storage.KeySwiftUserDomainID]
if len(userDomainID) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftUserDomainID)
return nil, "", kverrors.New("missing secret field", "field", storage.KeySwiftUserDomainID)
}
userID := s.Data[storage.KeySwiftUserID]
if len(userID) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftUserID)
return nil, "", kverrors.New("missing secret field", "field", storage.KeySwiftUserID)
}
password := s.Data[storage.KeySwiftPassword]
if len(password) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftPassword)
return nil, "", kverrors.New("missing secret field", "field", storage.KeySwiftPassword)
}
domainID := s.Data[storage.KeySwiftDomainID]
if len(domainID) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftDomainID)
return nil, "", kverrors.New("missing secret field", "field", storage.KeySwiftDomainID)
}
domainName := s.Data[storage.KeySwiftDomainName]
if len(domainName) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftDomainName)
return nil, "", kverrors.New("missing secret field", "field", storage.KeySwiftDomainName)
}
containerName := s.Data[storage.KeySwiftContainerName]
if len(containerName) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeySwiftContainerName)
return nil, "", kverrors.New("missing secret field", "field", storage.KeySwiftContainerName)
}

// Extract and validate optional fields
Expand All @@ -194,6 +232,22 @@ func extractSwiftConfigSecret(s *corev1.Secret) (*storage.SwiftStorageConfig, er
projectDomainName := s.Data[storage.KeySwiftProjectDomainName]
region := s.Data[storage.KeySwiftRegion]

contents := bytes.Join(
[][]byte{
url, username, userDomainName, userDomainID,
userID, password, domainID, domainName, containerName,
projectID, projectName, projectDomainID, projectDomainName, region,
},
hashSeparator,
)

h := sha1.New()
_, err := h.Write(contents)
if err != nil {
return nil, "", kverrors.Wrap(err, "failed hashing swift secret contents")
}
sha1C := fmt.Sprintf("%x", h.Sum(nil))

return &storage.SwiftStorageConfig{
AuthURL: string(url),
UserDomainName: string(userDomainName),
Expand All @@ -207,30 +261,39 @@ func extractSwiftConfigSecret(s *corev1.Secret) (*storage.SwiftStorageConfig, er
ProjectDomainName: string(projectDomainName),
Region: string(region),
Container: string(containerName),
}, nil
}, sha1C, nil
}

func extractAlibabaCloudConfigSecret(s *corev1.Secret) (*storage.AlibabaCloudStorageConfig, error) {
func extractAlibabaCloudConfigSecret(s *corev1.Secret) (*storage.AlibabaCloudStorageConfig, string, error) {
// Extract and validate mandatory fields
endpoint := s.Data[storage.KeyAlibabaCloudEndpoint]
if len(endpoint) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudEndpoint)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudEndpoint)
}
bucket := s.Data[storage.KeyAlibabaCloudBucket]
if len(bucket) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudBucket)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudBucket)
}
id := s.Data[storage.KeyAlibabaCloudAccessKeyID]
if len(id) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudAccessKeyID)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudAccessKeyID)
}
secret := s.Data[storage.KeyAlibabaCloudSecretAccessKey]
if len(secret) == 0 {
return nil, kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudSecretAccessKey)
return nil, "", kverrors.New("missing secret field", "field", storage.KeyAlibabaCloudSecretAccessKey)
}

contents := bytes.Join([][]byte{endpoint, bucket, id, secret}, hashSeparator)

h := sha1.New()
_, err := h.Write(contents)
if err != nil {
return nil, "", kverrors.Wrap(err, "failed hashing alibabacloud secret contents")
}
sha1C := fmt.Sprintf("%x", h.Sum(nil))

return &storage.AlibabaCloudStorageConfig{
Endpoint: string(endpoint),
Bucket: string(bucket),
}, nil
}, sha1C, nil
}
Loading
Loading