-
Notifications
You must be signed in to change notification settings - Fork 72
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(SecretsManager): Add support for secrets count metric (#333)
Added new Secrets Manager Monitor to support Account-Level Secrets Count. This includes a new SecretsManagerMonitor, a new SecretsManagerMetricsFactory, and a new SecretsManagerAlarmFactory. There is alarm support for Min/Max Secrets count and change in secrets count. Closes #137 --- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
- Loading branch information
Showing
10 changed files
with
2,271 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
lib/common/monitoring/alarms/SecretsManagerAlarmFactory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { | ||
ComparisonOperator, | ||
TreatMissingData, | ||
} from "aws-cdk-lib/aws-cloudwatch"; | ||
|
||
import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; | ||
import { MetricWithAlarmSupport } from "../../metric"; | ||
|
||
const NUMBER_OF_DATAPOINTS = 1; | ||
|
||
export interface MinSecretCountThreshold extends CustomAlarmThreshold { | ||
readonly minSecretCount: number; | ||
} | ||
|
||
export interface MaxSecretCountThreshold extends CustomAlarmThreshold { | ||
readonly maxSecretCount: number; | ||
} | ||
|
||
export interface ChangeInSecretCountThreshold extends CustomAlarmThreshold { | ||
readonly requiredSecretCount: number; | ||
readonly alarmWhenIncreased: boolean; | ||
readonly alarmWhenDecreased: boolean; | ||
readonly additionalDescription?: string; | ||
} | ||
|
||
export class SecretsManagerAlarmFactory { | ||
protected readonly alarmFactory: AlarmFactory; | ||
|
||
constructor(alarmFactory: AlarmFactory) { | ||
this.alarmFactory = alarmFactory; | ||
} | ||
|
||
addMinSecretCountAlarm( | ||
metric: MetricWithAlarmSupport, | ||
props: MinSecretCountThreshold, | ||
disambiguator?: string | ||
) { | ||
return this.alarmFactory.addAlarm(metric, { | ||
treatMissingData: | ||
props.treatMissingDataOverride ?? TreatMissingData.MISSING, | ||
datapointsToAlarm: props.datapointsToAlarm ?? NUMBER_OF_DATAPOINTS, | ||
comparisonOperator: | ||
props.comparisonOperatorOverride ?? | ||
ComparisonOperator.LESS_THAN_THRESHOLD, | ||
...props, | ||
disambiguator, | ||
threshold: props.minSecretCount, | ||
alarmNameSuffix: "Secrets-Count-Min", | ||
alarmDescription: "Number of secrets is too low.", | ||
}); | ||
} | ||
|
||
addMaxSecretCountAlarm( | ||
metric: MetricWithAlarmSupport, | ||
props: MaxSecretCountThreshold, | ||
disambiguator?: string | ||
) { | ||
return this.alarmFactory.addAlarm(metric, { | ||
treatMissingData: | ||
props.treatMissingDataOverride ?? TreatMissingData.MISSING, | ||
comparisonOperator: | ||
props.comparisonOperatorOverride ?? | ||
ComparisonOperator.GREATER_THAN_THRESHOLD, | ||
datapointsToAlarm: props.datapointsToAlarm ?? NUMBER_OF_DATAPOINTS, | ||
...props, | ||
disambiguator, | ||
threshold: props.maxSecretCount, | ||
alarmNameSuffix: "Secrets-Count-Max", | ||
alarmDescription: "Number of secrets is too high.", | ||
}); | ||
} | ||
|
||
addChangeInSecretCountAlarm( | ||
metric: MetricWithAlarmSupport, | ||
props: ChangeInSecretCountThreshold, | ||
disambiguator?: string | ||
) { | ||
return this.alarmFactory.addAlarm(metric, { | ||
...props, | ||
disambiguator, | ||
treatMissingData: | ||
props.treatMissingDataOverride ?? TreatMissingData.MISSING, | ||
threshold: props.requiredSecretCount, | ||
comparisonOperator: this.getComparisonOperator(props), | ||
datapointsToAlarm: props.datapointsToAlarm ?? NUMBER_OF_DATAPOINTS, | ||
alarmNameSuffix: "Secrets-Count-Change", | ||
alarmDescription: this.getDefaultDescription(props), | ||
}); | ||
} | ||
|
||
private getDefaultDescription(props: ChangeInSecretCountThreshold) { | ||
if (props.alarmWhenIncreased && props.alarmWhenDecreased) { | ||
return "Secret count: Secret count has changed."; | ||
} else if (props.alarmWhenIncreased) { | ||
return "Secret count: Secret count has increased."; | ||
} else if (props.alarmWhenDecreased) { | ||
return "Secret count: Secret count has decreased."; | ||
} else { | ||
throw new Error( | ||
"You need to alarm when the value has increased, decreased, or both." | ||
); | ||
} | ||
} | ||
|
||
private getComparisonOperator(props: ChangeInSecretCountThreshold) { | ||
if (props.alarmWhenIncreased && props.alarmWhenDecreased) { | ||
return ComparisonOperator.LESS_THAN_LOWER_OR_GREATER_THAN_UPPER_THRESHOLD; | ||
} else if (props.alarmWhenDecreased) { | ||
return ComparisonOperator.LESS_THAN_THRESHOLD; | ||
} else if (props.alarmWhenIncreased) { | ||
return ComparisonOperator.GREATER_THAN_THRESHOLD; | ||
} else { | ||
throw new Error( | ||
"You need to alarm when the value has increased, decreased, or both." | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
lib/monitoring/aws-secretsmanager/SecretsManagerMetricFactory.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Duration } from "aws-cdk-lib"; | ||
import { MetricFactory, MetricStatistic } from "../../common"; | ||
|
||
const CLASS = "None"; | ||
const DEFAULT_METRIC_PERIOD = Duration.hours(1); | ||
const METRICNAMESECRETCOUNT = "ResourceCount"; | ||
const NAMESPACE = "AWS/SecretsManager"; | ||
const RESOURCE = "SecretCount"; | ||
const SERVICE = "Secrets Manager"; | ||
const TYPE = "Resource"; | ||
|
||
export class SecretsManagerMetricFactory { | ||
protected readonly metricFactory: MetricFactory; | ||
|
||
constructor(metricFactory: MetricFactory) { | ||
this.metricFactory = metricFactory; | ||
} | ||
|
||
metricSecretCount() { | ||
const dimensionsMap = { | ||
Class: CLASS, | ||
Resource: RESOURCE, | ||
Service: SERVICE, | ||
Type: TYPE, | ||
}; | ||
|
||
return this.metricFactory.createMetric( | ||
METRICNAMESECRETCOUNT, | ||
MetricStatistic.AVERAGE, | ||
"Count", | ||
dimensionsMap, | ||
undefined, | ||
NAMESPACE, | ||
DEFAULT_METRIC_PERIOD | ||
); | ||
} | ||
} |
139 changes: 139 additions & 0 deletions
139
lib/monitoring/aws-secretsmanager/SecretsManagerMonitoring.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { | ||
GraphWidget, | ||
HorizontalAnnotation, | ||
IWidget, | ||
} from "aws-cdk-lib/aws-cloudwatch"; | ||
import { SecretsManagerMetricFactory } from "./SecretsManagerMetricFactory"; | ||
import { | ||
BaseMonitoringProps, | ||
ChangeInSecretCountThreshold, | ||
CountAxisFromZero, | ||
DefaultGraphWidgetHeight, | ||
DefaultSummaryWidgetHeight, | ||
HalfWidth, | ||
MaxSecretCountThreshold, | ||
MetricWithAlarmSupport, | ||
MinSecretCountThreshold, | ||
Monitoring, | ||
MonitoringScope, | ||
SecretsManagerAlarmFactory, | ||
ThirdWidth, | ||
} from "../../common"; | ||
import { | ||
MonitoringHeaderWidget, | ||
MonitoringNamingStrategy, | ||
} from "../../dashboard"; | ||
|
||
export interface SecretsManagerMonitoringOptions extends BaseMonitoringProps { | ||
readonly addMinNumberSecretsAlarm?: Record<string, MinSecretCountThreshold>; | ||
readonly addMaxNumberSecretsAlarm?: Record<string, MaxSecretCountThreshold>; | ||
readonly addChangeInSecretsAlarm?: Record< | ||
string, | ||
ChangeInSecretCountThreshold | ||
>; | ||
} | ||
|
||
export interface SecretsManagerMonitoringProps | ||
extends SecretsManagerMonitoringOptions {} | ||
|
||
export class SecretsManagerMonitoring extends Monitoring { | ||
readonly title: string; | ||
|
||
readonly secretsManagerAlarmFactory: SecretsManagerAlarmFactory; | ||
readonly secretsCountAnnotation: HorizontalAnnotation[]; | ||
|
||
readonly secretsCountMetric: MetricWithAlarmSupport; | ||
|
||
constructor(scope: MonitoringScope, props: SecretsManagerMonitoringProps) { | ||
super(scope); | ||
|
||
const namingStrategy = new MonitoringNamingStrategy({ | ||
...props, | ||
fallbackConstructName: "SecretsManager", | ||
}); | ||
|
||
this.title = namingStrategy.resolveHumanReadableName(); | ||
|
||
const alarmFactory = this.createAlarmFactory( | ||
namingStrategy.resolveAlarmFriendlyName() | ||
); | ||
this.secretsManagerAlarmFactory = new SecretsManagerAlarmFactory( | ||
alarmFactory | ||
); | ||
this.secretsCountAnnotation = []; | ||
|
||
const metricFactory = new SecretsManagerMetricFactory( | ||
scope.createMetricFactory() | ||
); | ||
this.secretsCountMetric = metricFactory.metricSecretCount(); | ||
|
||
for (const disambiguator in props.addMaxNumberSecretsAlarm) { | ||
const alarmProps = props.addMaxNumberSecretsAlarm[disambiguator]; | ||
const createdAlarm = | ||
this.secretsManagerAlarmFactory.addMaxSecretCountAlarm( | ||
this.secretsCountMetric, | ||
alarmProps, | ||
disambiguator | ||
); | ||
this.secretsCountAnnotation.push(createdAlarm.annotation); | ||
this.addAlarm(createdAlarm); | ||
} | ||
|
||
for (const disambiguator in props.addMinNumberSecretsAlarm) { | ||
const alarmProps = props.addMinNumberSecretsAlarm[disambiguator]; | ||
const createdAlarm = | ||
this.secretsManagerAlarmFactory.addMinSecretCountAlarm( | ||
this.secretsCountMetric, | ||
alarmProps, | ||
disambiguator | ||
); | ||
this.secretsCountAnnotation.push(createdAlarm.annotation); | ||
this.addAlarm(createdAlarm); | ||
} | ||
|
||
for (const disambiguator in props.addChangeInSecretsAlarm) { | ||
const alarmProps = props.addChangeInSecretsAlarm[disambiguator]; | ||
const createdAlarm = | ||
this.secretsManagerAlarmFactory.addChangeInSecretCountAlarm( | ||
this.secretsCountMetric, | ||
alarmProps, | ||
disambiguator | ||
); | ||
this.secretsCountAnnotation.push(createdAlarm.annotation); | ||
this.addAlarm(createdAlarm); | ||
} | ||
props.useCreatedAlarms?.consume(this.createdAlarms()); | ||
} | ||
|
||
summaryWidgets(): IWidget[] { | ||
return [ | ||
this.createTitleWidget(), | ||
this.createSecretsCountWidget(HalfWidth, DefaultSummaryWidgetHeight), | ||
]; | ||
} | ||
|
||
widgets(): IWidget[] { | ||
return [ | ||
this.createTitleWidget(), | ||
this.createSecretsCountWidget(ThirdWidth, DefaultGraphWidgetHeight), | ||
]; | ||
} | ||
|
||
createTitleWidget() { | ||
return new MonitoringHeaderWidget({ | ||
family: "Secrets Manager Secrets", | ||
title: this.title, | ||
}); | ||
} | ||
|
||
createSecretsCountWidget(width: number, height: number) { | ||
return new GraphWidget({ | ||
width, | ||
height, | ||
title: "Secret Count", | ||
left: [this.secretsCountMetric], | ||
leftYAxis: CountAxisFromZero, | ||
leftAnnotations: this.secretsCountAnnotation, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
export * from "./SecretsManagerMetricFactory"; | ||
export * from "./SecretsManagerMetricsPublisher"; | ||
export * from "./SecretsManagerSecretMetricFactory"; | ||
export * from "./SecretsManagerMonitoring"; | ||
export * from "./SecretsManagerSecretMonitoring"; |
Oops, something went wrong.