Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
16 changes: 8 additions & 8 deletions pkg/operator/encryption/kms/pluginlifecycle/sidecar.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ type sidecarProvider interface {

// newSidecarProvider creates a provider-specific sidecarProvider for the given keyID and plugin configuration,
// wiring in credentials via the credentialResolver.
func newSidecarProvider(keyID string, udsPath string, pluginConfig configv1.KMSPluginConfig, creds *credentialResolver) (sidecarProvider, error) {
func newSidecarProvider(keyID string, udsPath string, pluginConfig configv1.KMSPluginConfig, creds *credentialResolver, unsupportedConfig []byte) (sidecarProvider, error) {
switch pluginConfig.Type {
case configv1.VaultKMSProvider:
return newVaultSidecarProvider("vault-kms-plugin", keyID, udsPath, pluginConfig.Vault, creds)
return newVaultSidecarProvider("vault-kms-plugin", keyID, udsPath, pluginConfig.Vault, creds, unsupportedConfig)
default:
return nil, fmt.Errorf("unsupported KMS plugin configuration")
}
Expand All @@ -57,11 +57,11 @@ func newSidecarProvider(keyID string, udsPath string, pluginConfig configv1.KMSP
//
// It is a no-op when the KMSEncryption feature gate is not enabled or the encryption-config secret does not exist.
// The secretClient should be uncached to avoid injecting sidecars based on a stale encryption configuration.
func AddKMSPluginSidecarToStaticPodSpec(ctx context.Context, podSpec *corev1.PodSpec, containerName string, encryptionConfigNamespace string, encryptionConfigSecretName string, secretClient corev1client.SecretsGetter, featureGateAccessor featuregates.FeatureGateAccess) error {
func AddKMSPluginSidecarToStaticPodSpec(ctx context.Context, podSpec *corev1.PodSpec, containerName string, encryptionConfigNamespace string, encryptionConfigSecretName string, secretClient corev1client.SecretsGetter, featureGateAccessor featuregates.FeatureGateAccess, unsupportedConfig []byte) error {
// The static pod revision controller copies secret data to disk under resourcesDir/secrets/<secretName>/.
credentialsDir := filepath.Join(resourcesDir, "secrets", encryptionConfigSecretName)

sidecarNames, err := addKMSPluginSidecars(ctx, podSpec, containerName, encryptionConfigNamespace, encryptionConfigSecretName, secretClient, featureGateAccessor, credentialsDir)
sidecarNames, err := addKMSPluginSidecars(ctx, podSpec, containerName, encryptionConfigNamespace, encryptionConfigSecretName, secretClient, featureGateAccessor, credentialsDir, unsupportedConfig)
if err != nil {
return err
}
Expand Down Expand Up @@ -89,8 +89,8 @@ func AddKMSPluginSidecarToStaticPodSpec(ctx context.Context, podSpec *corev1.Pod
//
// It is a no-op when the KMSEncryption feature gate is not enabled or the encryption-config secret does not exist.
// The secretClient should be uncached to avoid injecting sidecars based on a stale encryption configuration.
func AddKMSPluginSidecarToPodSpec(ctx context.Context, podSpec *corev1.PodSpec, containerName string, encryptionConfigNamespace string, encryptionConfigSecretName string, secretClient corev1client.SecretsGetter, featureGateAccessor featuregates.FeatureGateAccess) error {
sidecarNames, err := addKMSPluginSidecars(ctx, podSpec, containerName, encryptionConfigNamespace, encryptionConfigSecretName, secretClient, featureGateAccessor, credentialsMountPath)
func AddKMSPluginSidecarToPodSpec(ctx context.Context, podSpec *corev1.PodSpec, containerName string, encryptionConfigNamespace string, encryptionConfigSecretName string, secretClient corev1client.SecretsGetter, featureGateAccessor featuregates.FeatureGateAccess, unsupportedConfig []byte) error {
sidecarNames, err := addKMSPluginSidecars(ctx, podSpec, containerName, encryptionConfigNamespace, encryptionConfigSecretName, secretClient, featureGateAccessor, credentialsMountPath, unsupportedConfig)
if err != nil {
return err
}
Expand Down Expand Up @@ -118,7 +118,7 @@ func AddKMSPluginSidecarToPodSpec(ctx context.Context, podSpec *corev1.PodSpec,

// addKMSPluginSidecars contains the shared logic for discovering KMS plugins and injecting sidecar containers.
// It returns the names of the sidecar containers that were injected, so callers can add deployment-mode-specific volume mounts.
func addKMSPluginSidecars(ctx context.Context, podSpec *corev1.PodSpec, containerName string, encryptionConfigNamespace string, encryptionConfigSecretName string, secretClient corev1client.SecretsGetter, featureGateAccessor featuregates.FeatureGateAccess, credentialsDir string) ([]string, error) {
func addKMSPluginSidecars(ctx context.Context, podSpec *corev1.PodSpec, containerName string, encryptionConfigNamespace string, encryptionConfigSecretName string, secretClient corev1client.SecretsGetter, featureGateAccessor featuregates.FeatureGateAccess, credentialsDir string, unsupportedConfig []byte) ([]string, error) {
if podSpec == nil {
return nil, fmt.Errorf("pod spec cannot be nil")
}
Expand Down Expand Up @@ -183,7 +183,7 @@ func addKMSPluginSidecars(ctx context.Context, podSpec *corev1.PodSpec, containe
keyID: keyID,
}

provider, err := newSidecarProvider(keyID, udsPath, pluginConfig, creds)
provider, err := newSidecarProvider(keyID, udsPath, pluginConfig, creds, unsupportedConfig)
if err != nil {
return nil, fmt.Errorf("failed to create a sidecar provider for keyID %s: %w", keyID, err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/operator/encryption/kms/pluginlifecycle/sidecar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ func TestAddKMSPluginSidecarToPodSpec(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := AddKMSPluginSidecarToPodSpec(context.Background(), tt.actualPodSpec, "kube-apiserver", "openshift-kube-apiserver", "encryption-config", tt.secretClient, tt.featureGateAccessor)
err := AddKMSPluginSidecarToPodSpec(context.Background(), tt.actualPodSpec, "kube-apiserver", "openshift-kube-apiserver", "encryption-config", tt.secretClient, tt.featureGateAccessor, nil)
if tt.wantErr != "" {
require.ErrorContains(t, err, tt.wantErr)
return
Expand All @@ -529,7 +529,7 @@ func TestAddKMSPluginSidecarToPodSpecIdempotency(t *testing.T) {

call := func() {
t.Helper()
err := AddKMSPluginSidecarToPodSpec(context.Background(), podSpec, "kube-apiserver", "openshift-kube-apiserver", "encryption-config", sc, fga)
err := AddKMSPluginSidecarToPodSpec(context.Background(), podSpec, "kube-apiserver", "openshift-kube-apiserver", "encryption-config", sc, fga, nil)
require.NoError(t, err)
}

Expand Down
38 changes: 38 additions & 0 deletions pkg/operator/encryption/kms/pluginlifecycle/unsupported_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package pluginlifecycle

import (
"encoding/json"

"k8s.io/klog/v2"

kyaml "k8s.io/apimachinery/pkg/util/yaml"
)

type unsupportedKMSConfig struct {
Encryption struct {
KMS struct {
Vault struct {
LogLevel string `json:"logLevel"`
} `json:"vault"`
} `json:"kms"`
} `json:"encryption"`
}

func parseUnsupportedKMSConfig(raw []byte) (unsupportedKMSConfig, error) {
if len(raw) == 0 {
return unsupportedKMSConfig{}, nil
}

jsonRaw, err := kyaml.ToJSON(raw)
if err != nil {
klog.Warning(err)
return unsupportedKMSConfig{}, err
}

config := unsupportedKMSConfig{}
if err := json.Unmarshal(jsonRaw, &config); err != nil {
return unsupportedKMSConfig{}, nil
}
Comment on lines +33 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Log JSON unmarshaling errors before swallowing them.

Line 34 silently ignores JSON unmarshaling errors and returns an empty config with no error. While the test shows this lenient behavior is intentional, the error should be logged (like the YAML conversion error at line 28) so users know when their unsupported config is malformed and being ignored.

As per coding guidelines, Go code should never ignore error returns. Even when errors are intentionally not propagated, they should be logged for observability.

📋 Proposed fix to add logging
 	config := unsupportedKMSConfig{}
 	if err := json.Unmarshal(jsonRaw, &config); err != nil {
+		klog.V(4).Infof("failed to unmarshal unsupported KMS config, ignoring: %v", err)
 		return unsupportedKMSConfig{}, nil
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/operator/encryption/kms/pluginlifecycle/unsupported_config.go` around
lines 33 - 35, The json.Unmarshal error is being swallowed in the
unsupportedKMSConfig decoding path; update the block that calls
json.Unmarshal(jsonRaw, &config) to log the error (using the same logging
mechanism used for the YAML conversion error) before returning
unsupportedKMSConfig{}, nil so malformed JSON is observable; reference the
json.Unmarshal call, the jsonRaw variable, and the unsupportedKMSConfig return
to locate and fix the code, ensuring the log message includes context that the
unsupported KMS config JSON was invalid.

Source: Coding guidelines


return config, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package pluginlifecycle

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestParseUnsupportedKMSConfig(t *testing.T) {
tests := []struct {
name string
raw []byte
expectedLogLevel string
expectError bool
}{
{
name: "nil input returns empty config",
raw: nil,
expectedLogLevel: "",
},
{
name: "empty input returns empty config",
raw: []byte{},
expectedLogLevel: "",
},
{
name: "JSON with log level",
raw: []byte(`{"encryption":{"kms":{"vault":{"logLevel":"debug-extended"}}}}`),
expectedLogLevel: "debug-extended",
},
{
name: "YAML with log level",
raw: []byte("encryption:\n kms:\n vault:\n logLevel: trace\n"),
expectedLogLevel: "trace",
},
{
name: "unrelated fields are ignored",
raw: []byte(`{"encryption":{"reason":"test"}}`),
expectedLogLevel: "",
},
{
name: "malformed JSON is handled gracefully",
raw: []byte(`{not json`),
expectedLogLevel: "",
},
{
name: "unparsable input returns error",
raw: []byte{0x00, 0x01, 0x02},
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config, err := parseUnsupportedKMSConfig(tt.raw)
if tt.expectError {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tt.expectedLogLevel, config.Encryption.KMS.Vault.LogLevel)
})
}
}
12 changes: 11 additions & 1 deletion pkg/operator/encryption/kms/pluginlifecycle/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// newVaultSidecarProvider creates a Vault sidecar provider from the given KMS plugin data.
// It assumes the input data has been already been validated.
func newVaultSidecarProvider(name, keyID, udsPath string, vaultConfig configv1.VaultKMSPluginConfig, creds *credentialResolver) (*vault, error) {
func newVaultSidecarProvider(name, keyID, udsPath string, vaultConfig configv1.VaultKMSPluginConfig, creds *credentialResolver, unsupportedConfig []byte) (*vault, error) {
secretName := vaultConfig.Authentication.AppRole.Secret.Name
if secretName == "" {
return nil, fmt.Errorf("vault AppRole authentication secret name cannot be empty")
Expand All @@ -35,13 +35,19 @@ func newVaultSidecarProvider(name, keyID, udsPath string, vaultConfig configv1.V
return nil, fmt.Errorf("secret ID path cannot be empty")
}

kmsConfig, err := parseUnsupportedKMSConfig(unsupportedConfig)
if err != nil {
return nil, err
}

return &vault{
name: name,
keyID: keyID,
udsPath: udsPath,
config: vaultConfig,
roleID: roleID,
secretIDPath: secretIDPath,
logLevel: kmsConfig.Encryption.KMS.Vault.LogLevel,
}, nil
}

Expand All @@ -53,6 +59,7 @@ type vault struct {
config configv1.VaultKMSPluginConfig
roleID string
secretIDPath string
logLevel string
}

// Name returns the sidecar name appended by the key id.
Expand All @@ -77,6 +84,9 @@ func (v *vault) BuildSidecarContainer() (corev1.Container, error) {
if v.config.VaultNamespace != "" {
args = append(args, fmt.Sprintf("-vault-namespace=%s", v.config.VaultNamespace))
}
if v.logLevel != "" {
args = append(args, fmt.Sprintf("-log-level=%s", v.logLevel))
}

// Temporary workarounds. These should go away as we progress with the feature.
args = append(args,
Expand Down
95 changes: 94 additions & 1 deletion pkg/operator/encryption/kms/pluginlifecycle/vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func TestVaultSidecarProvider_BuildSidecarContainer(t *testing.T) {
containerName string
keyID string
udsPath string
unsupportedConfig []byte
inputContainers []corev1.Container
expectedContainers []corev1.Container
expectErr string
Expand Down Expand Up @@ -196,6 +197,98 @@ func TestVaultSidecarProvider_BuildSidecarContainer(t *testing.T) {
udsPath: "unix:///var/run/kmsplugin/kms-555.sock",
expectErr: "vault AppRole authentication secret name cannot be empty",
},
{
name: "nil unsupported config omits log level",
vaultConfig: configv1.VaultKMSPluginConfig{
KMSPluginImage: "quay.io/test/vault:v2",
VaultAddress: "https://vault.example.com:8200",
TransitKey: "my-key",
TransitMount: "transit",
Authentication: configv1.VaultAuthentication{
AppRole: configv1.VaultAppRoleAuthentication{
Secret: configv1.VaultSecretReference{Name: "vault-approle"},
},
},
},
secretData: newVaultAppRoleSecretData(t, "test-role-id", "test-secret-id"),
credentialsDir: "/var/run/secrets/kms-plugin",
containerName: "kms-plugin",
keyID: "555",
udsPath: "unix:///var/run/kmsplugin/kms-555.sock",
unsupportedConfig: nil,
expectedContainers: []corev1.Container{
{
Name: "kms-plugin-555",
Image: "quay.io/test/vault:v2",
Args: []string{
"-listen-address=unix:///var/run/kmsplugin/kms-555.sock",
"-vault-address=https://vault.example.com:8200",
"-transit-mount=transit",
"-transit-key=my-key",
"-approle-role-id=test-role-id",
"-approle-secret-id-path=/var/run/secrets/kms-plugin/kms-plugin-secret-vault-approle_secret-id-555",
"-tls-skip-verify=true",
"-metrics-port=0",
},
ImagePullPolicy: corev1.PullIfNotPresent,
RestartPolicy: ptr.To(corev1.ContainerRestartPolicyAlways),
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("64Mi"),
corev1.ResourceCPU: resource.MustParse("10m"),
},
},
},
},
},
{
name: "log level override appended to args",
vaultConfig: configv1.VaultKMSPluginConfig{
KMSPluginImage: "quay.io/test/vault:v2",
VaultAddress: "https://vault.example.com:8200",
TransitKey: "my-key",
TransitMount: "transit",
Authentication: configv1.VaultAuthentication{
AppRole: configv1.VaultAppRoleAuthentication{
Secret: configv1.VaultSecretReference{Name: "vault-approle"},
},
},
},
secretData: newVaultAppRoleSecretData(t, "test-role-id", "test-secret-id"),
credentialsDir: "/var/run/secrets/kms-plugin",
containerName: "kms-plugin",
keyID: "555",
udsPath: "unix:///var/run/kmsplugin/kms-555.sock",
unsupportedConfig: []byte(`{"encryption":{"kms":{"vault":{"logLevel":"debug-extended"}}}}`),
inputContainers: nil,
expectedContainers: []corev1.Container{
{
Name: "kms-plugin-555",
Image: "quay.io/test/vault:v2",
Args: []string{
"-listen-address=unix:///var/run/kmsplugin/kms-555.sock",
"-vault-address=https://vault.example.com:8200",
"-transit-mount=transit",
"-transit-key=my-key",
"-approle-role-id=test-role-id",
"-approle-secret-id-path=/var/run/secrets/kms-plugin/kms-plugin-secret-vault-approle_secret-id-555",
"-log-level=debug-extended",
"-tls-skip-verify=true",
"-metrics-port=0",
},
ImagePullPolicy: corev1.PullIfNotPresent,
RestartPolicy: ptr.To(corev1.ContainerRestartPolicyAlways),
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("64Mi"),
corev1.ResourceCPU: resource.MustParse("10m"),
},
},
},
},
},
}

for _, tt := range tests {
Expand All @@ -212,7 +305,7 @@ func TestVaultSidecarProvider_BuildSidecarContainer(t *testing.T) {
keyID: tt.keyID,
}

provider, err := newVaultSidecarProvider(tt.containerName, tt.keyID, tt.udsPath, tt.vaultConfig, creds)
provider, err := newVaultSidecarProvider(tt.containerName, tt.keyID, tt.udsPath, tt.vaultConfig, creds, tt.unsupportedConfig)
if tt.expectErr != "" {
require.EqualError(t, err, tt.expectErr)
return
Expand Down