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
2 changes: 1 addition & 1 deletion api/operator/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions api/operator/v1beta1/vmagent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,20 @@ func (cr *VMAgent) Validate() error {
return err
}
}
for _, e := range cr.Spec.ExtraEnvs {
if strings.HasPrefix(e.Name, RemoteWriteEnvPrefix) {
return fmt.Errorf("spec.extraEnvs[%q] cannot use reserved prefix %q", e.Name, RemoteWriteEnvPrefix)
}
}
for idx, rw := range cr.Spec.RemoteWrite {
if rw.URL == "" {
return fmt.Errorf("remoteWrite[%d].url cannot be empty", idx)
}
if rw.AWS != nil {
if err := rw.AWS.validate(); err != nil {
return fmt.Errorf("bad remoteWrite[%d].aws: %w", idx, err)
}
}
if len(rw.InlineUrlRelabelConfig) > 0 {
if err := checkRelabelConfigs(rw.InlineUrlRelabelConfig); err != nil {
return fmt.Errorf("bad remoteWrite[%d].urlRelabelingConfig: %w", idx, err)
Expand Down Expand Up @@ -372,6 +382,30 @@ type AWS struct {
STSEndpoint string `json:"stsEndpoint,omitempty"`
// UseSigv4 enables SigV4 request signing for the corresponding -remoteWrite.url
UseSigv4 bool `json:"useSigv4,omitempty"`
// AccessKeySecret sets the AWS AccessKey for the -remoteWrite.url if useSigv4 is set.
// +optional
AccessKeySecret *corev1.SecretKeySelector `json:"accessKeySecret,omitempty"`
// SecretKeySecret sets the SecretKey for the -remoteWrite.url if useSigv4 is set.
// +optional
SecretKeySecret *corev1.SecretKeySelector `json:"secretKeySecret,omitempty"`
}

func (a *AWS) validate() error {
if (a.AccessKeySecret == nil) != (a.SecretKeySecret == nil) {
return fmt.Errorf("accessKeySecret and secretKeySecret must be set together")
}
if a.AccessKeySecret == nil {
return nil
}
if !a.UseSigv4 {
return fmt.Errorf("accessKeySecret and secretKeySecret require useSigv4=true")
}
for name, sel := range map[string]*corev1.SecretKeySelector{"accessKeySecret": a.AccessKeySecret, "secretKeySecret": a.SecretKeySecret} {
if sel.Name == "" || sel.Key == "" {
return fmt.Errorf("%s must have both name and key set", name)
}
}
Comment on lines +403 to +407

@cubic-dev-ai cubic-dev-ai Bot Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: Validation reports a random selector when both AWS selectors are incomplete because Go map iteration is nondeterministic. Validate accessKeySecret and secretKeySecret in a fixed order.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At api/operator/v1beta1/vmagent_types.go, line 401:

<comment>Validation reports a random selector when both AWS selectors are incomplete because Go map iteration is nondeterministic. Validate `accessKeySecret` and `secretKeySecret` in a fixed order.</comment>

<file context>
@@ -372,6 +382,28 @@ type AWS struct {
+	if !a.UseSigv4 {
+		return fmt.Errorf("accessKeySecret and secretKeySecret require useSigv4=true")
+	}
+	for name, sel := range map[string]*corev1.SecretKeySelector{"accessKeySecret": a.AccessKeySecret, "secretKeySecret": a.SecretKeySecret} {
+		if sel.Name == "" || sel.Key == "" {
+			return fmt.Errorf("%s must have both name and key set", name)
</file context>
Suggested change
for name, sel := range map[string]*corev1.SecretKeySelector{"accessKeySecret": a.AccessKeySecret, "secretKeySecret": a.SecretKeySecret} {
if sel.Name == "" || sel.Key == "" {
return fmt.Errorf("%s must have both name and key set", name)
}
}
if a.AccessKeySecret.Name == "" || a.AccessKeySecret.Key == "" {
return fmt.Errorf("accessKeySecret must have both name and key set")
}
if a.SecretKeySecret.Name == "" || a.SecretKeySecret.Key == "" {
return fmt.Errorf("secretKeySecret must have both name and key set")
}
Fix with cubic

return nil
}

// VMAgentRemoteWriteSpec defines the remote storage configuration for VmAgent
Expand Down
47 changes: 47 additions & 0 deletions api/operator/v1beta1/vmagent_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/ptr"
)

Expand Down Expand Up @@ -182,3 +183,49 @@ func TestVMAgent_IsUnmanaged(t *testing.T) {
Spec: VMAgentSpec{CommonScrapeParams: CommonScrapeParams{SelectAllByDefault: true}},
}, true)
}

func TestVMAgent_Validate_AWSRemoteWriteSettings(t *testing.T) {
f := func(rw VMAgentRemoteWriteSpec, wantErr bool) {
t.Helper()
rw.URL = "http://some-rw"
r := &VMAgent{Spec: VMAgentSpec{RemoteWrite: []VMAgentRemoteWriteSpec{rw}}}
if wantErr {
assert.Error(t, r.Validate())
} else {
assert.NoError(t, r.Validate())
}
}
// aws static credentials
validSel := &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: "aws-creds"},
Key: "key",
}
f(VMAgentRemoteWriteSpec{AWS: &AWS{UseSigv4: true, AccessKeySecret: validSel, SecretKeySecret: validSel}}, false)
// must be set together
f(VMAgentRemoteWriteSpec{AWS: &AWS{UseSigv4: true, AccessKeySecret: validSel}}, true)
f(VMAgentRemoteWriteSpec{AWS: &AWS{UseSigv4: true, SecretKeySecret: validSel}}, true)
// silently ignored by vmagent without useSigv4
f(VMAgentRemoteWriteSpec{AWS: &AWS{AccessKeySecret: validSel, SecretKeySecret: validSel}}, true)
// incomplete selector
f(VMAgentRemoteWriteSpec{AWS: &AWS{
UseSigv4: true,
AccessKeySecret: &corev1.SecretKeySelector{Key: "key"},
SecretKeySecret: validSel,
}}, true)
f(VMAgentRemoteWriteSpec{AWS: &AWS{
UseSigv4: true,
AccessKeySecret: validSel,
SecretKeySecret: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: "aws-creds"}},
}}, true)
// the pre-existing aws fields keep working on their own
f(VMAgentRemoteWriteSpec{AWS: &AWS{UseSigv4: true, Region: "us-east-1"}}, false)

// extraEnvs must not collide with the operator-managed prefix
r := &VMAgent{Spec: VMAgentSpec{
RemoteWrite: []VMAgentRemoteWriteSpec{{URL: "http://some-rw"}},
CommonAppsParams: CommonAppsParams{
ExtraEnvs: []corev1.EnvVar{{Name: RemoteWriteEnvPrefix + "0_AWS_ACCESS_KEY", Value: "x"}},
},
}}
assert.Error(t, r.Validate())
}
13 changes: 13 additions & 0 deletions api/operator/v1beta1/vmextra_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const (
VMAuthLBServiceProxyTargetLabel = "operator.victoriametrics.com/vmauthlb-proxy-name"
VMAuthLBServiceProxyJobNameLabel = "operator.victoriametrics.com/vmauthlb-proxy-job-name"
KubeNodeEnvName = "KUBE_NODE_NAME"
RemoteWriteEnvPrefix = "VM_REMOTEWRITE_"
// IgnoreConversionLabel this annotation disables updating of corresponding VMObject
// must be added to annotation of VMObject
// annotations:
Expand Down Expand Up @@ -130,6 +131,18 @@ const (
ConditionDomainTypeAppliedSuffix = ".victoriametrics.com/Applied"
)

// RemoteWriteAWSAccessKeyEnvName returns the name of the environment variable
// which holds AWS accessKey for spec.remoteWrite with the given idx.
func RemoteWriteAWSAccessKeyEnvName(idx int) string {
return fmt.Sprintf("%s%d_AWS_ACCESS_KEY", RemoteWriteEnvPrefix, idx)
}

// RemoteWriteAWSSecretKeyEnvName returns the name of the environment variable
// which holds AWS secretKey for spec.remoteWrite with the given idx.
func RemoteWriteAWSSecretKeyEnvName(idx int) string {
return fmt.Sprintf("%s%d_AWS_SECRET_KEY", RemoteWriteEnvPrefix, idx)
}

// skip validation, if object has annotation.
func MustSkipCRValidation(cr client.Object) bool {
return cr.GetAnnotations()[SkipValidationAnnotation] == SkipValidationValue
Expand Down
12 changes: 11 additions & 1 deletion api/operator/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions config/crd/overlay/crd.descriptionless.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions config/crd/overlay/crd.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ aliases:

## tip

**Update note 1**: [vmagent](https://docs.victoriametrics.com/operator/resources/vmagent/): `VMAgent's` CRD must be updated after support for remote write AWS AccessKeySecret and SecretKeySecret was added.

* Dependency: [vmoperator](https://docs.victoriametrics.com/operator/): Updated default versions for VM apps to [v1.154.0](https://github.com/VictoriaMetrics/VictoriaMetrics/releases/tag/v1.154.0) version
* Dependency: [vmoperator](https://docs.victoriametrics.com/operator/): Updated default version for VMAnomaly to [v1.31.0](https://docs.victoriametrics.com/anomaly-detection/changelog/#v1310).

Expand All @@ -22,6 +24,7 @@ aliases:
* Dependency: [vmoperator](https://docs.victoriametrics.com/operator/): Updated default versions for VT apps to [v0.12.0](https://github.com/VictoriaMetrics/VictoriaTraces/releases/tag/v0.12.0).

* FEATURE: [vmuser](https://docs.victoriametrics.com/operator/resources/vmuser/): allow `targetRef.crd.kind` to reference a `VTAgent`, so `vmauth` can route to it by name instead of a static URL. See [#2687](https://github.com/VictoriaMetrics/operator/issues/2687).
* FEATURE: [vmagent](https://docs.victoriametrics.com/operator/resources/vmagent/): add `spec.remoteWrite[].aws.accessKeySecret` and `spec.remoteWrite[].aws.secretKeySecret` fields for static AWS SigV4 credentials. These fields keep credentials out of the pod spec and process arguments. The `VMAgent` CRD must be updated to get this feature. See [#2737](https://github.com/VictoriaMetrics/operator/pull/2737).

* BUGFIX: [vmanomaly](https://docs.victoriametrics.com/operator/resources/vmanomaly/): allow `spec.configRawYaml` and `spec.configSecret` without `reader.queries` when queries are provided by selected [VMAnomalyConfig](https://docs.victoriametrics.com/operator/resources/vmanomalyconfig/) objects. Previously such a settings-only base config failed reconciliation with `reader.queries must be provided via configRawYaml or configSecret`. See [#2685](https://github.com/VictoriaMetrics/operator/pull/2685).
* BUGFIX: [helm-converter](https://docs.victoriametrics.com/operator/helm-converter/): fix the `victoria-metrics-alert` chart's legacy value aliases (`server.remote.read`, `server.remote.write`, `server.notifier.alertmanager`, nested `basicAuth`/`bearer` auth keys and a list-valued `server.notifier.url`) being silently dropped during conversion; they are now resolved the same way the chart does. Plaintext `remoteWrite` credentials are moved into a generated Secret like the other blocks. See [#2601](https://github.com/VictoriaMetrics/operator/issues/2601).
Expand Down
2 changes: 2 additions & 0 deletions docs/api.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading