diff --git a/go.mod b/go.mod index 3b872f030f5..d8bde533370 100644 --- a/go.mod +++ b/go.mod @@ -69,7 +69,7 @@ require ( github.com/googleapis/gax-go/v2 v2.14.1 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.3 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20250128163937-4446935bbcce // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20250123104008-e99947858901 // @grafana/identity-access-team github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c // @grafana/identity-access-team github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 57b33a896ee..42231876a94 100644 --- a/go.sum +++ b/go.sum @@ -1498,8 +1498,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250128163937-4446935bbcce h1:lilqLsOGzo+0SuyXjaN5XRVJbnkJRB0bXMoIlYHTIPE= -github.com/grafana/alerting v0.0.0-20250128163937-4446935bbcce/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a h1:44E+I3EPdh/W02Uyfyig86EJKPjvzcF3y0A+FEi1fBk= +github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901 h1:nqV1YrtX+ZG+EYB5dcmFMWhg2Y038OMaAHAADbOC9RA= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= diff --git a/go.work.sum b/go.work.sum index cad88eb884c..c335966892c 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1502,6 +1502,7 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20250115195200-209e052dba64/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:V63rh3udd7sqXJeaG+nGUmViwVnM/bY6t8U9Tols2GU= github.com/grafana/authlib v0.0.0-20250120145936-5f0e28e7a87c/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120144156-d6737a7dc8f5/go.mod h1:qYjSd1tmJiuVoSICp7Py9/zD54O9uQQA3wuM6Gg4DFM= diff --git a/pkg/services/ngalert/api/tooling/definitions/contact_points.go b/pkg/services/ngalert/api/tooling/definitions/contact_points.go index 1e523a8cbe6..9df9b8ee444 100644 --- a/pkg/services/ngalert/api/tooling/definitions/contact_points.go +++ b/pkg/services/ngalert/api/tooling/definitions/contact_points.go @@ -268,7 +268,7 @@ type ThreemaIntegration struct { type VictoropsIntegration struct { DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"` - URL string `json:"url" yaml:"url" hcl:"url"` + URL Secret `json:"url" yaml:"url" hcl:"url"` MessageType *string `json:"messageType,omitempty" yaml:"messageType,omitempty" hcl:"message_type"` Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"` diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index 595ac33c447..e1748a7550b 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -388,6 +388,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "VictorOps url", PropertyName: "url", Required: true, + Secure: true, }, { // New in 8.0. Label: "Message Type", diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels_test.go b/pkg/services/ngalert/notifier/channels_config/available_channels_test.go index 5240c177873..5e305cb1224 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels_test.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels_test.go @@ -15,7 +15,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { {receiverType: "kafka", expectedSecretFields: []string{"password"}}, {receiverType: "email", expectedSecretFields: []string{}}, {receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}}, - {receiverType: "victorops", expectedSecretFields: []string{}}, + {receiverType: "victorops", expectedSecretFields: []string{"url"}}, {receiverType: "oncall", expectedSecretFields: []string{"password", "authorization_credentials"}}, {receiverType: "pushover", expectedSecretFields: []string{"apiToken", "userKey"}}, {receiverType: "slack", expectedSecretFields: []string{"token", "url"}}, diff --git a/pkg/services/ngalert/notifier/receiver_svc.go b/pkg/services/ngalert/notifier/receiver_svc.go index bd4852660aa..a4ba285d311 100644 --- a/pkg/services/ngalert/notifier/receiver_svc.go +++ b/pkg/services/ngalert/notifier/receiver_svc.go @@ -485,6 +485,14 @@ func (rs *ReceiverService) UpdateReceiver(ctx context.Context, r *models.Receive return nil, err } + // We re-encrypt the existing receiver to ensure any unencrypted secure fields that are correctly encrypted, note this should NOT re-encrypt secure fields that are already encrypted. + // This is rare, but can happen if a receiver is created with unencrypted secure fields and then the secure option is added later. + // Preferably, this would be handled by receiver config versions and migrations but for now this is a good safety net. + err = existing.Encrypt(rs.encryptor(ctx)) + if err != nil { + return nil, err + } + span.AddEvent("Loaded current receiver", trace.WithAttributes( attribute.String("concurrency_token", revision.ConcurrencyToken), attribute.String("receiver", existing.Name), diff --git a/pkg/services/ngalert/notifier/receiver_svc_test.go b/pkg/services/ngalert/notifier/receiver_svc_test.go index d8c27a5d0c7..d8b5d019237 100644 --- a/pkg/services/ngalert/notifier/receiver_svc_test.go +++ b/pkg/services/ngalert/notifier/receiver_svc_test.go @@ -583,6 +583,44 @@ func TestReceiverService_Update(t *testing.T) { ), rm.Encrypted(models.Base64Enrypt)), expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone}, }, + { + name: "encrypts previously unencrypted secure fields", + user: writer, + receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations( + models.CopyIntegrationWith(slackIntegration, im.AddSetting("token", "unencryptedValue"))), + ), + existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations( + models.CopyIntegrationWith(slackIntegration, + im.AddSetting("token", "unencryptedValue"), // This will get encrypted. + ), + ))), + expectedUpdate: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations( + models.CopyIntegrationWith(slackIntegration, + im.AddSecureSetting("token", "dW5lbmNyeXB0ZWRWYWx1ZQ==")), + ), rm.Encrypted(models.Base64Enrypt)), + expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone}, + }, + { + // This test is important for covering the rare case when an existing field is marked as secure. + // The UI will receive the field as secure and, if unchanged, will pass it back on update as a secureField instead of a Setting. + name: "encrypts previously unencrypted secure fields when passed in as secureFields", + user: writer, + receiver: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations( + models.CopyIntegrationWith(slackIntegration, im.AddSetting("newField", "newValue"))), + ), + secureFields: map[string][]string{slackIntegration.UID: {"token"}}, + existing: util.Pointer(models.CopyReceiverWith(baseReceiver, rm.WithIntegrations( + models.CopyIntegrationWith(slackIntegration, + im.AddSetting("token", "unencryptedValue"), // This will get encrypted. + ), + ))), + expectedUpdate: models.CopyReceiverWith(baseReceiver, rm.WithIntegrations( + models.CopyIntegrationWith(slackIntegration, + im.AddSetting("newField", "newValue"), + im.AddSecureSetting("token", "dW5lbmNyeXB0ZWRWYWx1ZQ==")), + ), rm.Encrypted(models.Base64Enrypt)), + expectedProvenances: map[string]models.Provenance{slackIntegration.UID: models.ProvenanceNone}, + }, { name: "doesn't copy existing unsecure fields", user: writer, @@ -684,8 +722,22 @@ func TestReceiverService_Update(t *testing.T) { sut := createReceiverServiceSut(t, &secretsService) if tc.existing != nil { - created, err := sut.CreateReceiver(context.Background(), tc.existing, tc.user.GetOrgID(), tc.user) + // Create route after receivers as they will be referenced. + revision, err := sut.cfgStore.Get(context.Background(), tc.user.GetOrgID()) require.NoError(t, err) + result, err := revision.CreateReceiver(tc.existing) + require.NoError(t, err) + + created, err := PostableApiReceiverToReceiver(result, tc.existing.Provenance) + require.NoError(t, err) + err = sut.cfgStore.Save(context.Background(), revision, tc.user.GetOrgID()) + require.NoError(t, err) + + for _, integration := range created.Integrations { + target := definitions.EmbeddedContactPoint{UID: integration.UID} + err = sut.provisioningStore.SetProvenance(context.Background(), &target, tc.user.GetOrgID(), created.Provenance) + require.NoError(t, err) + } if tc.version == "" { tc.version = created.Version diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod index ede76c6bdfb..32b82f51bdc 100644 --- a/pkg/storage/unified/apistore/go.mod +++ b/pkg/storage/unified/apistore/go.mod @@ -170,7 +170,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20250128163937-4446935bbcce // indirect + github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a // indirect github.com/grafana/authlib v0.0.0-20250123104008-e99947858901 // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/dskit v0.0.0-20241105154643-a6b453a88040 // indirect diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum index 6a81ac11767..d25df5fb16f 100644 --- a/pkg/storage/unified/apistore/go.sum +++ b/pkg/storage/unified/apistore/go.sum @@ -547,8 +547,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20250128163937-4446935bbcce h1:lilqLsOGzo+0SuyXjaN5XRVJbnkJRB0bXMoIlYHTIPE= -github.com/grafana/alerting v0.0.0-20250128163937-4446935bbcce/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a h1:44E+I3EPdh/W02Uyfyig86EJKPjvzcF3y0A+FEi1fBk= +github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901 h1:nqV1YrtX+ZG+EYB5dcmFMWhg2Y038OMaAHAADbOC9RA= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo= diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 13ee3bd6f3b..1f87f8bc70e 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -115,7 +115,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/alerting v0.0.0-20250128163937-4446935bbcce // indirect + github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a // indirect github.com/grafana/dataplane/sdata v0.0.9 // indirect github.com/grafana/grafana-app-sdk/logging v0.29.0 // indirect github.com/grafana/grafana-aws-sdk v0.31.5 // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 4674123e79e..0e30c13985c 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -403,8 +403,8 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/alerting v0.0.0-20250128163937-4446935bbcce h1:lilqLsOGzo+0SuyXjaN5XRVJbnkJRB0bXMoIlYHTIPE= -github.com/grafana/alerting v0.0.0-20250128163937-4446935bbcce/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a h1:44E+I3EPdh/W02Uyfyig86EJKPjvzcF3y0A+FEi1fBk= +github.com/grafana/alerting v0.0.0-20250129195454-3e5b80036b7a/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901 h1:nqV1YrtX+ZG+EYB5dcmFMWhg2Y038OMaAHAADbOC9RA= github.com/grafana/authlib v0.0.0-20250123104008-e99947858901/go.mod h1:/gYfphsNu9v1qYWXxpv1NSvMEMSwvdf8qb8YlgwIRl8= github.com/grafana/authlib/types v0.0.0-20250120145936-5f0e28e7a87c h1:b0sPDtt33uFdmvUJjSCld3kwE2E49dUvevuUDSJsEuo=