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

feat(operator): Extend Azure secret validation #12007

Merged
merged 4 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
1 change: 1 addition & 0 deletions operator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Main

- [12007](https://github.com/grafana/loki/pull/12007) **xperimental**: Extend Azure secret validation
- [11964](https://github.com/grafana/loki/pull/11964) **xperimental**: Provide Azure region for managed credentials using environment variable
- [11920](https://github.com/grafana/loki/pull/11920) **xperimental**: Refactor handling of credentials in managed-auth mode
- [11869](https://github.com/grafana/loki/pull/11869) **periklis**: Add support for running with Google Workload Identity
Expand Down
33 changes: 30 additions & 3 deletions operator/internal/handlers/internal/storage/secrets.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package storage

import (
"bytes"
"context"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"sort"

corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -33,9 +36,18 @@ var (
errAzureNoCredentials = errors.New("azure storage secret does contain neither account_key or client_id")
errAzureMixedCredentials = errors.New("azure storage secret can not contain both account_key and client_id")
errAzureManagedIdentityNoOverride = errors.New("when in managed mode, storage secret can not contain credentials")
errAzureInvalidEnvironment = errors.New("azure environment invalid (valid values: AzureGlobal, AzureChinaCloud, AzureGermanCloud, AzureUSGovernment)")
errAzureInvalidAccountKey = errors.New("azure account key is not valid base64")

errGCPParseCredentialsFile = errors.New("gcp storage secret cannot be parsed from JSON content")
errGCPWrongCredentialSourceFile = errors.New("credential source in secret needs to point to token file")

azureValidEnvironments = map[string]bool{
"AzureGlobal": true,
"AzureChinaCloud": true,
"AzureGermanCloud": true,
"AzureUSGovernment": true,
}
)

const gcpAccountTypeExternal = "external_account"
Expand Down Expand Up @@ -159,11 +171,15 @@ func hashSecretData(s *corev1.Secret) (string, error) {

func extractAzureConfigSecret(s *corev1.Secret, fg configv1.FeatureGates) (*storage.AzureStorageConfig, error) {
// Extract and validate mandatory fields
env := s.Data[storage.KeyAzureEnvironmentName]
if len(env) == 0 {
env := string(s.Data[storage.KeyAzureEnvironmentName])
if env == "" {
return nil, fmt.Errorf("%w: %s", errSecretMissingField, storage.KeyAzureEnvironmentName)
}

if !azureValidEnvironments[env] {
return nil, fmt.Errorf("%w: %s", errAzureInvalidEnvironment, env)
}

accountName := s.Data[storage.KeyAzureStorageAccountName]
if len(accountName) == 0 {
return nil, fmt.Errorf("%w: %s", errSecretMissingField, storage.KeyAzureStorageAccountName)
Expand All @@ -188,7 +204,7 @@ func extractAzureConfigSecret(s *corev1.Secret, fg configv1.FeatureGates) (*stor
}

return &storage.AzureStorageConfig{
Env: string(env),
Env: env,
Container: string(container),
EndpointSuffix: string(endpointSuffix),
Audience: string(audience),
Expand Down Expand Up @@ -219,6 +235,10 @@ func validateAzureCredentials(s *corev1.Secret, fg configv1.FeatureGates) (workl
}

if len(accountKey) > 0 {
if err := validateBase64(accountKey); err != nil {
return false, errAzureInvalidAccountKey
}

// have both account_name and account_key -> no workload identity federation
return false, nil
}
Expand All @@ -235,6 +255,13 @@ func validateAzureCredentials(s *corev1.Secret, fg configv1.FeatureGates) (workl
return true, nil
}

func validateBase64(data []byte) error {
buf := bytes.NewBuffer(data)
reader := base64.NewDecoder(base64.StdEncoding, buf)
_, err := io.ReadAll(reader)
return err
}

func extractGCSConfigSecret(s *corev1.Secret) (*storage.GCSStorageConfig, error) {
// Extract and validate mandatory fields
bucket := s.Data[storage.KeyGCPStorageBucketName]
Expand Down
39 changes: 24 additions & 15 deletions operator/internal/handlers/internal/storage/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,20 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{},
wantError: "missing secret field: environment",
},
{
name: "invalid environment",
secret: &corev1.Secret{
Data: map[string][]byte{
"environment": []byte("invalid-environment"),
},
},
wantError: "azure environment invalid (valid values: AzureGlobal, AzureChinaCloud, AzureGermanCloud, AzureUSGovernment): invalid-environment",
},
{
name: "missing account_name",
secret: &corev1.Secret{
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
},
},
wantError: "missing secret field: account_name",
Expand All @@ -97,7 +106,7 @@ func TestAzureExtract(t *testing.T) {
name: "missing container",
secret: &corev1.Secret{
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"account_name": []byte("id"),
},
},
Expand All @@ -108,7 +117,7 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"container": []byte("this,that"),
"account_name": []byte("id"),
},
Expand All @@ -120,7 +129,7 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"container": []byte("this,that"),
"account_name": []byte("test-account-name"),
"account_key": []byte("test-account-key"),
Expand All @@ -134,7 +143,7 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"container": []byte("this,that"),
"account_name": []byte("test-account-name"),
"client_id": []byte("test-client-id"),
Expand All @@ -147,7 +156,7 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"container": []byte("this,that"),
"account_name": []byte("test-account-name"),
"client_id": []byte("test-client-id"),
Expand All @@ -161,7 +170,7 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"account_name": []byte("test-account-name"),
"container": []byte("this,that"),
"region": []byte("test-region"),
Expand All @@ -184,10 +193,10 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"container": []byte("this,that"),
"account_name": []byte("id"),
"account_key": []byte("secret"),
"account_key": []byte("dGVzdC1hY2NvdW50LWtleQ=="), // test-account-key
"audience": []byte("test-audience"),
},
},
Expand All @@ -198,10 +207,10 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"container": []byte("this,that"),
"account_name": []byte("id"),
"account_key": []byte("secret"),
"account_key": []byte("dGVzdC1hY2NvdW50LWtleQ=="), // test-account-key
},
},
wantCredentialMode: lokiv1.CredentialModeStatic,
Expand All @@ -211,7 +220,7 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"container": []byte("this,that"),
"account_name": []byte("test-account-name"),
"client_id": []byte("test-client-id"),
Expand All @@ -227,7 +236,7 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"account_name": []byte("test-account-name"),
"container": []byte("this,that"),
"region": []byte("test-region"),
Expand Down Expand Up @@ -256,10 +265,10 @@ func TestAzureExtract(t *testing.T) {
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "test"},
Data: map[string][]byte{
"environment": []byte("here"),
"environment": []byte("AzureGlobal"),
"container": []byte("this,that"),
"account_name": []byte("id"),
"account_key": []byte("secret"),
"account_key": []byte("dGVzdC1hY2NvdW50LWtleQ=="), // test-account-key
"endpoint_suffix": []byte("suffix"),
},
},
Expand Down
Loading