cfn-guardian 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/docs/custom_checks/http.md +131 -2
- data/lib/cfnguardian/models/check.rb +1 -1
- data/lib/cfnguardian/models/event.rb +14 -0
- data/lib/cfnguardian/resources/dms_cluster.rb +2 -0
- data/lib/cfnguardian/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a1ed7183d14b7d1bc05fdfcffc1ff4272c04ff6db43fc47e4fdcf2124b1d898
|
|
4
|
+
data.tar.gz: 9b3bcc2a4b2d578983f549f93a0ca482b9842a30eac3ef84fe6e4781575a5440
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b058796599f33efd09aff544129071b087a5cde59d87b06d3b19f5036947fc4b1679a32ba371a1a7707b05923a978c31af25e72f486b65c0f9e96d0bedff5122
|
|
7
|
+
data.tar.gz: 00ec6b9c0c79dd9afc146e135a0eb9512008a1dae030f8e38beed322f5ca3f6ed2ba5eaeb2a82af5b2b1335970d3f3244ff4773c65db3056d81a1b951e692e07
|
data/docs/custom_checks/http.md
CHANGED
|
@@ -13,7 +13,7 @@ Resources:
|
|
|
13
13
|
StatusCode: 200
|
|
14
14
|
# enables the SSL check
|
|
15
15
|
Ssl: true
|
|
16
|
-
# boolean
|
|
16
|
+
# boolean to request a compressed response
|
|
17
17
|
Compressed: true
|
|
18
18
|
- Id: https://www.example.com
|
|
19
19
|
StatusCode: 301
|
|
@@ -38,6 +38,55 @@ Resources:
|
|
|
38
38
|
Payload: '{"name": "john"}'
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## HMAC signed requests
|
|
42
|
+
|
|
43
|
+
For health endpoints that require authenticated requests, Guardian can send HMAC-signed headers so your application can verify the request came from Guardian and resist replay attacks.
|
|
44
|
+
|
|
45
|
+
When enabled, the HTTP check Lambda (see [aws-lambda-http-check](https://github.com/base2/aws-lambda-http-check)) adds these headers to each request:
|
|
46
|
+
|
|
47
|
+
| Header (default prefix `X-Health`) | Description |
|
|
48
|
+
|-----------------------------------|-------------|
|
|
49
|
+
| `X-Health-Signature` | HMAC-SHA256 hex digest of the canonical string |
|
|
50
|
+
| `X-Health-Key-Id` | Key identifier (e.g. `default`) |
|
|
51
|
+
| `X-Health-Timestamp` | Unix epoch timestamp in seconds |
|
|
52
|
+
| `X-Health-Nonce` | Random value (e.g. UUID hex) to prevent replay |
|
|
53
|
+
|
|
54
|
+
**Configuration (Public or Internal HTTP):**
|
|
55
|
+
|
|
56
|
+
| Key | Required | Description |
|
|
57
|
+
|-----|----------|-------------|
|
|
58
|
+
| `HmacSecretSsm` | Yes | SSM Parameter Store path to the HMAC secret (SecureString). The Guardian-generated IAM role grants the Lambda `ssm:GetParameter` for this path. |
|
|
59
|
+
| `HmacKeyId` | No | Key id sent in the `Key-Id` header. Default: `default`. |
|
|
60
|
+
| `HmacHeaderPrefix` | No | Prefix for all HMAC header names. Default: `X-Health` (yields `X-Health-Signature`, `X-Health-Key-Id`, etc.). |
|
|
61
|
+
|
|
62
|
+
**Example:**
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
Resources:
|
|
66
|
+
Http:
|
|
67
|
+
- Id: https://api.example.com/health
|
|
68
|
+
StatusCode: 200
|
|
69
|
+
HmacSecretSsm: /guardian/myapp/hmac-secret
|
|
70
|
+
HmacKeyId: default
|
|
71
|
+
HmacHeaderPrefix: X-Health
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Internal HTTP checks support the same keys under each host:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
Resources:
|
|
78
|
+
InternalHttp:
|
|
79
|
+
- Environment: Prod
|
|
80
|
+
VpcId: vpc-1234
|
|
81
|
+
Subnets: [subnet-abcd]
|
|
82
|
+
Hosts:
|
|
83
|
+
- Id: http://api.example.com/health
|
|
84
|
+
StatusCode: 200
|
|
85
|
+
HmacSecretSsm: /guardian/myapp/hmac-secret
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Create the secret in SSM (e.g. SecureString) and use the same value in your application when verifying the signature.
|
|
89
|
+
|
|
41
90
|
## Private HTTP Check
|
|
42
91
|
|
|
43
92
|
Cloudwatch NameSpace: `InternalHttpCheck`
|
|
@@ -58,4 +107,84 @@ Resources:
|
|
|
58
107
|
# Array of resources defining the http endpoint with the Id: key
|
|
59
108
|
# All the same options as Http including ssl check on the internal endpoint
|
|
60
109
|
- Id: http://api.example.com
|
|
61
|
-
```
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Supporting HMAC-signed health checks in your application
|
|
113
|
+
|
|
114
|
+
If you want Guardian to call a health endpoint that only accepts HMAC-authenticated requests, your server must verify the same scheme the Lambda uses.
|
|
115
|
+
|
|
116
|
+
**Canonical string (what gets signed):**
|
|
117
|
+
|
|
118
|
+
The Lambda signs this string (newline-separated, no trailing newline):
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
METHOD\nPATH\nTIMESTAMP\nNONCE\nQUERY\nBODY_HASH
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- `METHOD` – HTTP method (e.g. `GET`).
|
|
125
|
+
- `PATH` – URL path (e.g. `/health`), no query.
|
|
126
|
+
- `TIMESTAMP` – Same value as the `{prefix}-Timestamp` header (Unix seconds).
|
|
127
|
+
- `NONCE` – Same value as the `{prefix}-Nonce` header.
|
|
128
|
+
- `QUERY` – Raw query string (e.g. `foo=bar` or empty).
|
|
129
|
+
- `BODY_HASH` – SHA-256 hex digest of the raw request body (empty string for GET; for POST/PUT, hash the body as sent).
|
|
130
|
+
|
|
131
|
+
**Verification steps (pseudo code):**
|
|
132
|
+
|
|
133
|
+
1. Read headers (default prefix `X-Health`):
|
|
134
|
+
`signature`, `key_id`, `timestamp`, `nonce`.
|
|
135
|
+
2. **Optional – replay protection:**
|
|
136
|
+
Reject if `timestamp` is too old (e.g. outside last 5 minutes).
|
|
137
|
+
Reject if `nonce` has been seen before (e.g. cache or DB) and treat as replay.
|
|
138
|
+
3. Look up the shared secret for `key_id` (e.g. from config or secrets store – same value as in SSM for Guardian).
|
|
139
|
+
4. Build the canonical string from the incoming request:
|
|
140
|
+
- Use request method, path, and query.
|
|
141
|
+
- Use `timestamp` and `nonce` from the headers.
|
|
142
|
+
- For body: compute SHA-256 hex of the raw request body (empty string for no body).
|
|
143
|
+
5. Compute `expected = HMAC-SHA256(secret, canonical_string)` and compare to `signature` (constant-time).
|
|
144
|
+
6. If equal, treat the request as authenticated.
|
|
145
|
+
|
|
146
|
+
**Pseudo code example:**
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
import hmac
|
|
150
|
+
import hashlib
|
|
151
|
+
|
|
152
|
+
def verify_health_request(request, secret_by_key_id, header_prefix="X-Health", max_age_seconds=300):
|
|
153
|
+
sig_h = f"{header_prefix}-Signature"
|
|
154
|
+
key_h = f"{header_prefix}-Key-Id"
|
|
155
|
+
ts_h = f"{header_prefix}-Timestamp"
|
|
156
|
+
nonce_h = f"{header_prefix}-Nonce"
|
|
157
|
+
|
|
158
|
+
signature = request.headers.get(sig_h)
|
|
159
|
+
key_id = request.headers.get(key_h)
|
|
160
|
+
timestamp = request.headers.get(ts_h)
|
|
161
|
+
nonce = request.headers.get(nonce_h)
|
|
162
|
+
|
|
163
|
+
if not all([signature, key_id, timestamp, nonce]):
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
# Replay: reject old timestamps
|
|
167
|
+
if abs(int(timestamp) - time.time()) > max_age_seconds:
|
|
168
|
+
return False
|
|
169
|
+
# Replay: reject duplicate nonce (check your cache/DB)
|
|
170
|
+
if nonce_already_used(nonce):
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
secret = secret_by_key_id.get(key_id)
|
|
174
|
+
if not secret:
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
body_hash = hashlib.sha256(request.body or b"").hexdigest()
|
|
178
|
+
canonical = "\n".join([
|
|
179
|
+
request.method,
|
|
180
|
+
request.path,
|
|
181
|
+
timestamp,
|
|
182
|
+
nonce,
|
|
183
|
+
request.query_string or "",
|
|
184
|
+
body_hash,
|
|
185
|
+
])
|
|
186
|
+
expected = hmac.new(secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()
|
|
187
|
+
return hmac.compare_digest(expected, signature)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Use the same HMAC secret in SSM (Guardian config) and in your app’s config or secrets store. Keep the secret in SecureString or equivalent and restrict access appropriately.
|
|
@@ -55,6 +55,10 @@ module CfnGuardian
|
|
|
55
55
|
@user_agent = resource.fetch('UserAgent',nil)
|
|
56
56
|
@payload = resource.fetch('Payload',nil)
|
|
57
57
|
@compressed = resource.fetch('Compressed',false)
|
|
58
|
+
@hmac_secret_ssm = resource.fetch('HmacSecretSsm',nil)
|
|
59
|
+
@hmac_key_id = resource.fetch('HmacKeyId','default')
|
|
60
|
+
@hmac_header_prefix = resource.fetch('HmacHeaderPrefix','X-Health')
|
|
61
|
+
@report_response_body = resource.fetch('ReportResponseBody',false)
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
def payload
|
|
@@ -69,8 +73,18 @@ module CfnGuardian
|
|
|
69
73
|
payload['USER_AGENT'] = @user_agent unless @user_agent.nil?
|
|
70
74
|
payload['PAYLOAD'] = @payload unless @payload.nil?
|
|
71
75
|
payload['COMPRESSED'] = '1' if @compressed
|
|
76
|
+
payload['REPORT_RESPONSE_BODY'] = '1' if @report_response_body
|
|
77
|
+
unless @hmac_secret_ssm.nil?
|
|
78
|
+
payload['HMAC_SECRET_SSM'] = @hmac_secret_ssm
|
|
79
|
+
payload['HMAC_KEY_ID'] = @hmac_key_id
|
|
80
|
+
payload['HMAC_HEADER_PREFIX'] = @hmac_header_prefix
|
|
81
|
+
end
|
|
72
82
|
return payload.to_json
|
|
73
83
|
end
|
|
84
|
+
|
|
85
|
+
def ssm_parameters
|
|
86
|
+
@hmac_secret_ssm.nil? ? [] : [@hmac_secret_ssm]
|
|
87
|
+
end
|
|
74
88
|
end
|
|
75
89
|
|
|
76
90
|
class WebSocketEvent < BaseEvent
|
|
@@ -25,6 +25,7 @@ module CfnGuardian::Resource
|
|
|
25
25
|
alarm.statistic = 'Minimum'
|
|
26
26
|
alarm.threshold = 10000000000
|
|
27
27
|
alarm.evaluation_periods = 1
|
|
28
|
+
alarm.comparison_operator = 'LessThanThreshold'
|
|
28
29
|
@alarms.push(alarm)
|
|
29
30
|
|
|
30
31
|
alarm = CfnGuardian::Models::DMSClusterAlarm.new(@resource)
|
|
@@ -34,6 +35,7 @@ module CfnGuardian::Resource
|
|
|
34
35
|
alarm.threshold = 20000000000
|
|
35
36
|
alarm.evaluation_periods = 1
|
|
36
37
|
alarm.alarm_action = 'Warning'
|
|
38
|
+
alarm.comparison_operator = 'LessThanThreshold'
|
|
37
39
|
@alarms.push(alarm)
|
|
38
40
|
end
|
|
39
41
|
end
|
data/lib/cfnguardian/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cfn-guardian
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.12.
|
|
4
|
+
version: 0.12.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Guslington
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-03-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: thor
|