Skip to content

Commit b5afa73

Browse files
committed
Support schedules within notification rules
1 parent c606b56 commit b5afa73

File tree

2 files changed

+110
-2
lines changed

2 files changed

+110
-2
lines changed

integrations/access/accessmonitoring/access_monitoring_rules.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,20 @@ func (amrh *RuleHandler) RecipientsFromAccessMonitoringRules(ctx context.Context
154154
}
155155

156156
for _, rule := range amrh.getAccessMonitoringRules() {
157+
// Check if creation time is within rule schedules.
158+
isInSchedules, err := accessmonitoring.InSchedules(rule.GetSpec().GetSchedules(), env.CreationTime)
159+
if err != nil {
160+
log.WarnContext(ctx, "Failed to evaluate access monitoring rule",
161+
"error", err,
162+
"rule", rule.GetMetadata().GetName(),
163+
)
164+
continue
165+
}
166+
if len(rule.GetSpec().GetSchedules()) != 0 && !isInSchedules {
167+
continue
168+
}
169+
170+
// Check if environment matches rule conditions.
157171
match, err := accessmonitoring.EvaluateCondition(rule.Spec.Condition, env)
158172
if err != nil {
159173
log.WarnContext(ctx, "Failed to parse access monitoring notification rule",
@@ -188,6 +202,20 @@ func (amrh *RuleHandler) RawRecipientsFromAccessMonitoringRules(ctx context.Cont
188202
}
189203

190204
for _, rule := range amrh.getAccessMonitoringRules() {
205+
// Check if creation time is within rule schedules.
206+
isInSchedules, err := accessmonitoring.InSchedules(rule.GetSpec().GetSchedules(), env.CreationTime)
207+
if err != nil {
208+
log.WarnContext(ctx, "Failed to evaluate access monitoring rule",
209+
"error", err,
210+
"rule", rule.GetMetadata().GetName(),
211+
)
212+
continue
213+
}
214+
if len(rule.GetSpec().GetSchedules()) != 0 && !isInSchedules {
215+
continue
216+
}
217+
218+
// Check if environment matches rule conditions.
191219
match, err := accessmonitoring.EvaluateCondition(rule.Spec.Condition, env)
192220
if err != nil {
193221
log.WarnContext(ctx, "Failed to parse access monitoring notification rule",

integrations/access/accessmonitoring/access_monitoring_rules_test.go

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package accessmonitoring
2121
import (
2222
"context"
2323
"testing"
24+
"time"
2425

2526
"github.com/stretchr/testify/mock"
2627
"github.com/stretchr/testify/require"
@@ -52,7 +53,7 @@ func mockFetchRecipient(ctx context.Context, recipient string) (*common.Recipien
5253
return nil, nil
5354
}
5455

55-
func TestRecipeints(t *testing.T) {
56+
func TestRecipients(t *testing.T) {
5657
const (
5758
pluginName = "fakePluginName"
5859
pluginType = "fakePluginType"
@@ -133,7 +134,7 @@ func TestRecipeints(t *testing.T) {
133134
require.ElementsMatch(t, []string{}, rawRecipients)
134135
}
135136

136-
func TestRecipeintsWithResources(t *testing.T) {
137+
func TestRecipientsWithResources(t *testing.T) {
137138
const (
138139
pluginName = "fakePluginName"
139140
pluginType = "fakePluginType"
@@ -205,6 +206,85 @@ func TestRecipeintsWithResources(t *testing.T) {
205206
require.ElementsMatch(t, []string{recipient}, rawRecipients)
206207
}
207208

209+
func TestRecipientsWithSchedules(t *testing.T) {
210+
const (
211+
pluginName = "fakePluginName"
212+
pluginType = "fakePluginType"
213+
recipient = "[email protected]"
214+
)
215+
216+
teleportClient := &mockTeleportClient{}
217+
teleportClient.
218+
On("GetUser", mock.Anything, mock.Anything, mock.Anything).
219+
Return(&types.UserV2{}, nil)
220+
221+
amrh := NewRuleHandler(RuleHandlerConfig{
222+
Client: teleportClient,
223+
PluginType: pluginType,
224+
PluginName: pluginName,
225+
FetchRecipientCallback: func(ctx context.Context, recipient string) (*common.Recipient, error) {
226+
return emailRecipient(recipient), nil
227+
},
228+
})
229+
230+
rule1, err := services.NewAccessMonitoringRuleWithLabels("rule1", nil, &pb.AccessMonitoringRuleSpec{
231+
Subjects: []string{types.KindAccessRequest},
232+
Schedules: map[string]*pb.Schedule{
233+
"default": {
234+
Time: &pb.TimeSchedule{
235+
Shifts: []*pb.TimeSchedule_Shift{
236+
{
237+
Weekday: time.Monday.String(),
238+
Start: "14:00",
239+
End: "15:00",
240+
},
241+
},
242+
},
243+
},
244+
},
245+
Condition: `true`,
246+
Notification: &pb.Notification{
247+
Name: pluginName,
248+
Recipients: []string{recipient},
249+
},
250+
})
251+
require.NoError(t, err)
252+
err = amrh.HandleAccessMonitoringRule(context.Background(), types.Event{
253+
Type: types.OpPut,
254+
Resource: types.Resource153ToLegacy(rule1),
255+
})
256+
require.NoError(t, err)
257+
require.Len(t, amrh.getAccessMonitoringRules(), 1)
258+
259+
ctx := context.Background()
260+
261+
// Expect recipient from matching rule.
262+
req := &types.AccessRequestV3{
263+
Spec: types.AccessRequestSpecV3{
264+
Created: time.Date(2025, time.August, 11, 14, 30, 0, 0, time.UTC),
265+
},
266+
}
267+
268+
recipients := amrh.RecipientsFromAccessMonitoringRules(ctx, req)
269+
require.ElementsMatch(t, []common.Recipient{*emailRecipient(recipient)}, recipients.ToSlice())
270+
271+
rawRecipients := amrh.RawRecipientsFromAccessMonitoringRules(ctx, req)
272+
require.ElementsMatch(t, []string{recipient}, rawRecipients)
273+
274+
// Expect no recipient when not in schedule.
275+
req = &types.AccessRequestV3{
276+
Spec: types.AccessRequestSpecV3{
277+
Created: time.Date(2025, time.August, 11, 15, 30, 0, 0, time.UTC),
278+
},
279+
}
280+
281+
recipients = amrh.RecipientsFromAccessMonitoringRules(ctx, req)
282+
require.ElementsMatch(t, []common.Recipient{}, recipients.ToSlice())
283+
284+
rawRecipients = amrh.RawRecipientsFromAccessMonitoringRules(ctx, req)
285+
require.ElementsMatch(t, []string{}, rawRecipients)
286+
}
287+
208288
func emailRecipient(recipient string) *common.Recipient {
209289
return &common.Recipient{
210290
Name: recipient,

0 commit comments

Comments
 (0)