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
4 changes: 2 additions & 2 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This repository consolidates AWS DevOps Agent skills. Follow these conventions w
## Repository Structure

```
sample-devops-agent-tools/
tools-for-devops-agent/
├── README.md # Project overview with skills table
├── .gitignore # Root-level ignores
├── skills/
Expand Down Expand Up @@ -126,7 +126,7 @@ Only these extensions are permitted inside skill directories (enforced by `skill
6. Test the skill with DevOps Agent before submitting.
7. Update the root `README.md` skills table with the new skill's name, agent types, author, and docs link.
8. Update the `llms.txt` file at the repo root — add the new skill to the "Available Skills" section following the existing format: `- [Skill Name](skills/<name>/SKILL.md): One-line description`.
9. If the skill requires IAM permissions beyond the `AIDevOpsAgentAccessPolicy` managed policy, add a new parameter, condition, and inline policy resource to `cloudformation/devops-agent-skill-policies.yaml`, and update the `SkillPolicySummary` output.
9. If the skill requires IAM permissions beyond the `AIDevOpsAgentAccessPolicy` managed policy, add a new parameter, condition, and inline policy resource to `cloudformation/devops-agent-skill-policies/devops-agent-skill-policies.yaml`, and update the `SkillPolicySummary` output.

## Zipping for Upload

Expand Down
4 changes: 2 additions & 2 deletions .kiro/steering/project-conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This repository consolidates AWS DevOps Agent skills. Follow these conventions w
## Repository Structure

```
sample-devops-agent-tools/
tools-for-devops-agent/
├── README.md # Project overview with skills table
├── .gitignore # Root-level ignores
├── skills/
Expand Down Expand Up @@ -125,7 +125,7 @@ Only these extensions are permitted inside skill directories (enforced by `skill
6. Test the skill with DevOps Agent before submitting.
7. Update the root `README.md` skills table with the new skill's name, description, agent types, author, and docs link.
8. Update the `llms.txt` file at the repo root — add the new skill to the "Available Skills" section following the existing format: `- [Skill Name](skills/<name>/SKILL.md): One-line description`.
9. If the skill requires IAM permissions beyond the `AIDevOpsAgentAccessPolicy` managed policy, add a new parameter, condition, and inline policy resource to `cloudformation/devops-agent-skill-policies.yaml`, and update the `SkillPolicySummary` output.
9. If the skill requires IAM permissions beyond the `AIDevOpsAgentAccessPolicy` managed policy, add a new parameter, condition, and inline policy resource to `cloudformation/devops-agent-skill-policies/devops-agent-skill-policies.yaml`, and update the `SkillPolicySummary` output.

## Maintaining llms.txt

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ Most permissions are already covered by the AWS managed policy [`AIDevOpsAgentAc

```bash
aws cloudformation deploy \
--template-file cloudformation/devops-agent-skill-policies.yaml \
--template-file cloudformation/devops-agent-skill-policies/devops-agent-skill-policies.yaml \
--stack-name devops-agent-skill-policies \
--parameter-overrides ExistingRoleName=<YOUR-DEVOPS-AGENT-ROLE-NAME> \
--capabilities CAPABILITY_NAMED_IAM
```

The template supports enabling/disabling policies per skill, optional region restrictions, and can either attach to an existing role or create a new one. See [`cloudformation/devops-agent-skill-policies.yaml`](cloudformation/devops-agent-skill-policies.yaml) for details.
The template supports enabling/disabling policies per skill, optional region restrictions, and can either attach to an existing role or create a new one. See [`cloudformation/devops-agent-skill-policies/devops-agent-skill-policies.yaml`](cloudformation/devops-agent-skill-policies/devops-agent-skill-policies.yaml) for details.

## Contributing

Expand Down
80 changes: 80 additions & 0 deletions cloudformation/devops-agent-alarm-investigations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# DevOps Agent alarm investigations

Forwards **one** Amazon CloudWatch alarm to an AWS DevOps Agent generic webhook, so the
alarm opens an investigation. **One stack = one alarm** — deploy it again per alarm.

No Lambda and no code: an Amazon EventBridge rule scoped to the alarm ARN matches
`CloudWatch Alarm State Change` with `state.value = ALARM`, an input transformer builds
the incident payload, and an EventBridge API destination POSTs it. The API key lives in an
EventBridge connection, which sends it as `Authorization: Bearer <key>`. An IAM role grants
the rule `events:InvokeApiDestination` on that one destination.

The payload carries only the alarm ARN and the raised state — no alarm name, reason, or
metric data. DevOps Agent enriches from the ARN.

## Before you deploy

The webhook is created in the console (there is no `CreateWebhook` API) and its API key is
shown only once, so it must exist first:

1. Create the Agent Space.
2. Console → **Capabilities → Agent Space Webhook → Generate webhook**, authentication type
**API key**. Copy the URL and the key — the key is not retrievable later.
3. Deploy this stack with that URL, that key, and the alarm ARN.

Lost the key? Rotate the webhook from the Capabilities tab; rotation keeps the URL and
issues a new key. Update the stack afterwards — the connection re-reads the key only when
the stack is updated.

## Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `WebhookUrl` | *(required)* | HTTPS URL of the generic webhook. |
| `WebhookApiKey` | *(required)* | The API key from webhook creation. `NoEcho`, so it is masked in stack events and `describe-stacks`. |
| `AlarmArn` | *(required)* | ARN of the single alarm to forward. |
| `InvocationRateLimitPerSecond` | `300` | Cap on webhook invocations per second — 300 is the per-destination quota, so the stack adds no throttling. Lower it only to deliberately rate-limit the webhook. |
| `DevOpsAgentAlarmIntegrationTag` | *(stack name)* | Value of the `DevOpsAgent` tag on the rule and role. Identification and cost allocation only; does not affect routing. |

## Deploy

```bash
aws cloudformation deploy \
--template-file cloudformation/devops-agent-alarm-investigations/devops-agent-alarm-investigations.yaml \
--stack-name devops-agent-alarm-investigations-<alarm-name> \
--capabilities CAPABILITY_IAM \
--parameter-overrides \
WebhookUrl="https://<your-webhook-url>" \
WebhookApiKey="<your-webhook-api-key>" \
AlarmArn="arn:aws:cloudwatch:<region>:<account>:alarm:<alarm-name>"
```

## Notes

- **Cross-Region and cross-account alarms.** Deploy this stack once, in the DevOps Agent's
account and Region, and set `AlarmArn` to the alarm's real (possibly remote) ARN. Remote
alarms reach it via ordinary
[bus-to-bus forwarding](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-cross-account.html):
a rule in the alarm's own Region/account targeting the agent Region's default bus, plus —
cross-account only — a resource policy on the agent bus allowing `PutEvents` from the
source account. The forwarded event still carries the alarm ARN in `resources`, so this
stack's rule matches it.
- **Region availability.** API destinations to public HTTPS endpoints are not available in
every Region — check
[API destinations as targets](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-api-destinations.html#eb-api-destination-regions).
- **Deduplication.** `incidentId` is the EventBridge event id, constant across retries, so
redeliveries reuse it instead of opening duplicates. Control flapping at the alarm's
datapoints-to-alarm setting.
- **Delivery.** Up to 32 retry attempts over 8 hours; API destinations require a response
within 5 seconds. `MaximumEventAgeInSeconds` stops retries once an event is older than
the window, so a late redelivery cannot open a stale investigation.
- **Monitoring.** There is no log group. The rule publishes `InvocationAttempts`,
`SuccessfulInvocationAttempts`, `RetryInvocationAttempts` and `FailedInvocations` in
`AWS/Events` by `RuleName`. Alarm on `FailedInvocations`; add a dead-letter queue to the
target to inspect events that never landed.
- **Keeping the key in your own secret.** Replace `ApiKeyValue` with
`'{{resolve:secretsmanager:MyWebhookSecret}}'` and drop the `WebhookApiKey` parameter, so
the key never passes through a stack parameter. Same rotation caveat as above.
- **API key versus HMAC.** The webhook also supports HMAC, but EventBridge connections
support only Basic, API key, and OAuth and cannot sign per request — HMAC would need a
signing Lambda in between. This template takes the API key path to stay code-free.
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: >
Forwards a single Amazon CloudWatch alarm to an AWS DevOps Agent webhook so that
alarm opens an investigation. One stack = one alarm: it creates an Amazon
EventBridge rule scoped to that alarm's ARN, an EventBridge connection holding the
webhook API key, an EventBridge API destination pointing at the webhook, and a role
that lets the rule invoke it. No AWS Lambda function is involved.

Prerequisite: create the Agent Space and its generic webhook with API key
authentication first (console — no API), then pass the webhook URL, the API key,
and the alarm ARN to this stack.

Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: AWS DevOps Agent Webhook
Parameters:
- WebhookUrl
- WebhookApiKey
- Label:
default: Alarm
Parameters:
- AlarmArn
- Label:
default: Delivery Tuning (optional)
Parameters:
- InvocationRateLimitPerSecond
- DevOpsAgentAlarmIntegrationTag
ParameterLabels:
WebhookUrl:
default: DevOps Agent generic (API key) webhook URL
WebhookApiKey:
default: Webhook API key (bearer token)
AlarmArn:
default: ARN of the single CloudWatch alarm to forward
InvocationRateLimitPerSecond:
default: Max webhook invocations per second
DevOpsAgentAlarmIntegrationTag:
default: Value for the DevOpsAgent tag (optional; defaults to the stack name)

Parameters:
WebhookUrl:
Type: String
Description: >
HTTPS URL of the AWS DevOps Agent generic webhook, created in the Agent Space
console with API key authentication.
AllowedPattern: '^https://[A-Za-z0-9.-]+(:[0-9]+)?(/.*)?$'
ConstraintDescription: Must be an HTTPS URL.

WebhookApiKey:
Type: String
NoEcho: true
Description: >
The API key (bearer token) shown once when you created the webhook. It is stored
in an AWS Secrets Manager secret that EventBridge creates and owns for the
connection. To keep the key in a secret you own instead, see the README.
MinLength: 1
ConstraintDescription: Must not be empty.

AlarmArn:
Type: String
Description: >
ARN of the single Amazon CloudWatch alarm this stack forwards. Only ALARM
state changes for this exact alarm are sent to the webhook.
AllowedPattern: '^arn:aws[a-zA-Z-]*:cloudwatch:[a-z0-9-]+:[0-9]{12}:alarm:.+'
ConstraintDescription: Must be a CloudWatch alarm ARN.

DevOpsAgentAlarmIntegrationTag:
Type: String
Default: ''
Description: >
(Optional) Value applied as the DevOpsAgent tag on the resources this stack
creates (identification / cost allocation only; does not affect routing, which
is determined by the webhook URL). Defaults to the stack name when left empty.
MaxLength: 256
AllowedPattern: "^[A-Za-z0-9 _.:/=+@-]{0,256}$"
ConstraintDescription: >
Up to 256 characters using letters, numbers, spaces, and _ . : / = + - @
(the AWS tag-value character set), or empty to use the stack name.

InvocationRateLimitPerSecond:
Type: Number
Default: 300
MinValue: 1
MaxValue: 300
Description: >
(Optional) Maximum webhook invocations per second for the API destination. The
default of 300 is the per-destination quota, so this stack adds no throttling of
its own: one alarm cannot approach that rate, while a low cap would queue
concurrent events (a flapping alarm overlapping a retry) and delay them. Lower it
only to deliberately rate-limit the webhook.
ConstraintDescription: >
Between 1 and 300. 300 is the default 'Rate of invocations per API destination'
quota; lower this value to throttle, and raise the quota first if you need more.

Conditions:
# An empty tag value falls back to the stack name. A parameter Default cannot
# itself be an intrinsic, so the fallback is resolved here instead.
HasTagValue: !Not [!Equals [!Ref DevOpsAgentAlarmIntegrationTag, '']]

Resources:
# Holds the bearer token. EventBridge stores it in a Secrets Manager secret it
# creates and owns for this connection, and adds the header on every invocation.
WebhookConnection:
Type: AWS::Events::Connection
Properties:
Description: !Sub 'API key for the AWS DevOps Agent webhook (${AWS::StackName}).'
AuthorizationType: API_KEY
AuthParameters:
ApiKeyAuthParameters:
# The DevOps Agent webhook expects the token as a bearer token, so the
# header name is Authorization and the value carries the Bearer prefix.
ApiKeyName: Authorization
ApiKeyValue: !Sub 'Bearer ${WebhookApiKey}'

WebhookApiDestination:
Type: AWS::Events::ApiDestination
Properties:
Description: !Sub 'AWS DevOps Agent webhook (${AWS::StackName}).'
ConnectionArn: !GetAtt WebhookConnection.Arn
InvocationEndpoint: !Ref WebhookUrl
HttpMethod: POST
InvocationRateLimitPerSecond: !Ref InvocationRateLimitPerSecond

# An API destination target requires a role granting events:InvokeApiDestination.
InvokeWebhookRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: events.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount: !Ref 'AWS::AccountId'
Policies:
- PolicyName: invoke-devops-agent-webhook
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: InvokeWebhookApiDestination
Effect: Allow
Action: events:InvokeApiDestination
Resource: !GetAtt WebhookApiDestination.Arn
Tags:
- Key: ManagedBy
Value: CloudFormation
- Key: DevOpsAgent
Value: !If
- HasTagValue
- !Ref DevOpsAgentAlarmIntegrationTag
- !Ref 'AWS::StackName'

# Rule scoped to exactly one alarm ARN — the rule itself is the filter, so no
# code is needed to decide whether an event should be forwarded.
AlarmStateChangeRule:
Type: AWS::Events::Rule
Properties:
Description: !Sub 'Forwards ALARM-state changes for ${AlarmArn} to the DevOps Agent webhook.'
EventPattern:
source:
- aws.cloudwatch
detail-type:
- CloudWatch Alarm State Change
resources:
- !Ref AlarmArn
detail:
state:
value:
- ALARM
State: ENABLED
Targets:
- Id: DevOpsAgentWebhook
Arn: !GetAtt WebhookApiDestination.Arn
RoleArn: !GetAtt InvokeWebhookRole.Arn
HttpParameters:
HeaderParameters:
Content-Type: application/json
# incidentId is the EventBridge event id, which is constant across
# retries, so DevOps Agent deduplicates redeliveries of the same event
# instead of opening a new investigation for each attempt.
InputTransformer:
InputPathsMap:
eventId: $.id
eventTime: $.time
alarmArn: $.resources[0]
InputTemplate: |
{
"eventType": "incident",
"incidentId": "<eventId>",
"action": "created",
"priority": "HIGH",
"title": "CloudWatch alarm in ALARM state",
"description": "CloudWatch alarm <alarmArn> entered ALARM state.",
"timestamp": "<eventTime>",
"data": {
"metadata": {
"alarmArn": "<alarmArn>",
"state": "ALARM"
}
}
}
RetryPolicy:
MaximumRetryAttempts: 32
MaximumEventAgeInSeconds: 28800
Tags:
- Key: ManagedBy
Value: CloudFormation
- Key: DevOpsAgent
Value: !If
- HasTagValue
- !Ref DevOpsAgentAlarmIntegrationTag
- !Ref 'AWS::StackName'

Outputs:
ApiDestinationArn:
Description: ARN of the API destination that posts to the webhook.
Value: !GetAtt WebhookApiDestination.Arn

ConnectionArn:
Description: ARN of the EventBridge connection holding the webhook API key.
Value: !GetAtt WebhookConnection.Arn

EventRuleArn:
Description: ARN of the EventBridge rule scoped to the alarm.
Value: !GetAtt AlarmStateChangeRule.Arn

ForwardedAlarmArn:
Description: The CloudWatch alarm this stack forwards.
Value: !Ref AlarmArn
Loading