Skip to content

Commit ce3ac03

Browse files
danielpaulusclaude
andcommitted
docs(ssl): update assertions to the property-scoped grammar
Rewrites the SSL assertion docs from the flat 14-source model to the new { source, property } grammar: CERTIFICATE / CONNECTION (property = field selector) + RESPONSE_TIME / JSON_RESPONSE / TEXT_RESPONSE, and the CLI SslAssertionBuilder to certificate()/connection()/responseTime()/ jsonResponse()/textResponse(). Drops the removed GREATER_THAN_OR_EQUAL / MATCHES operators. Note: api-reference/openapi.json (generated from the backend OpenAPI) will pick up the new assertion enums when the backend change is published. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013AYwkvoaANQTH5dGStbBj2
1 parent 0bf7c02 commit ce3ac03

2 files changed

Lines changed: 90 additions & 66 deletions

File tree

constructs/ssl-monitor.mdx

Lines changed: 46 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,10 @@ new SslMonitor("api-ssl-advanced", {
7171
},
7272
},
7373
assertions: [
74-
SslAssertionBuilder.certNotExpired().equals(true),
75-
SslAssertionBuilder.chainTrusted().equals(true),
76-
SslAssertionBuilder.hostnameVerified().equals(true),
77-
SslAssertionBuilder.tlsVersion().greaterThanOrEqual(TlsVersion.TLS1_2),
78-
SslAssertionBuilder.certExpiresInDays().greaterThan(45),
74+
SslAssertionBuilder.connection("chainTrusted").equals(true),
75+
SslAssertionBuilder.connection("hostnameVerified").equals(true),
76+
SslAssertionBuilder.connection("tlsVersion").equals(TlsVersion.TLS1_3),
77+
SslAssertionBuilder.certificate("daysUntilExpiry").greaterThan(45),
7978
],
8079
},
8180
})
@@ -278,51 +277,50 @@ Each rule's `severity` can be `'fail'` \| `'degrade'` \| `'ignore'`.
278277

279278
### `SslMonitor` Assertions
280279

281-
Define `assertions` using the `SslAssertionBuilder`. The following sources are available:
282-
283-
| Builder method | Source | Value type | Description |
284-
|---------------|--------|-----------|-------------|
285-
| `certNotExpired()` | `CERT_NOT_EXPIRED` | boolean | Certificate has not passed its expiry date |
286-
| `certExpiresInDays()` | `CERT_EXPIRES_IN_DAYS` | number | Days until the certificate expires |
287-
| `hostnameVerified()` | `HOSTNAME_VERIFIED` | boolean | Certificate SANs cover the monitored hostname |
288-
| `chainTrusted()` | `CHAIN_TRUSTED` | boolean | Full chain is trusted by the system root store |
289-
| `tlsVersion()` | `TLS_VERSION` | string | Negotiated TLS version |
290-
| `cipherSuite()` | `CIPHER_SUITE` | string | Negotiated IANA cipher suite name |
291-
| `signatureAlgorithm()` | `SIGNATURE_ALGORITHM` | string | Leaf certificate signature algorithm |
292-
| `keySizeBits()` | `KEY_SIZE_BITS` | number | Leaf certificate public key size in bits |
293-
| `issuerCn()` | `ISSUER_CN` | string | Common name of the certificate issuer |
294-
| `certFingerprintSha256()` | `CERT_FINGERPRINT_SHA256` | string | SHA-256 fingerprint of the leaf certificate |
295-
| `issuerFingerprintSha256()` | `ISSUER_FINGERPRINT_SHA256` | string | SHA-256 fingerprint of the issuer |
296-
| `sanContains()` | `SAN_CONTAINS` | string | Any SAN matches the given value |
297-
| `ocspStapled()` | `OCSP_STAPLED` | boolean | A stapled OCSP response was included in the handshake |
298-
| `handshakeTimeMs()` | `HANDSHAKE_TIME_MS` | number | TLS handshake duration in milliseconds |
280+
Define `assertions` using the `SslAssertionBuilder`. Five entry points map to the assertion sources — `certificate`/`connection` take a `property` (a field selector); `jsonResponse`/`textResponse` take a JSONPath/regex:
281+
282+
| Builder | Source | Description |
283+
|---------|--------|-------------|
284+
| `certificate(property)` | `CERTIFICATE` | Assert on a leaf-certificate field |
285+
| `connection(property)` | `CONNECTION` | Assert on a TLS handshake / connection field |
286+
| `responseTime()` | `RESPONSE_TIME` | TLS handshake duration in milliseconds |
287+
| `jsonResponse(jsonPath)` | `JSON_RESPONSE` | Assert on any response field via a JSONPath expression |
288+
| `textResponse(regex?)` | `TEXT_RESPONSE` | Extract a value from the serialized response with a regex, then compare |
289+
290+
`certificate(property)` properties: `daysUntilExpiry` (number), `keySizeBits` (number), `subjectCN` / `issuerCN` / `serialNumber` / `fingerprintSha256` / `issuerFingerprintSha256` / `keyAlgorithm` / `signatureAlgorithm` (string), `sans` (list — use `CONTAINS`), `selfSigned` / `isCA` (boolean).
291+
292+
`connection(property)` properties: `tlsVersion` / `cipherSuite` / `ocspStatus` / `resolvedIp` (string), `hostnameVerified` / `chainTrusted` / `ocspStapled` (boolean).
299293

300294
Examples:
301295

302296
```ts
303-
// Certificate must not be expired
304-
SslAssertionBuilder.certNotExpired().equals(true)
305-
// Equivalent to: { source: 'CERT_NOT_EXPIRED', comparison: 'EQUALS', target: 'true' }
306-
307297
// Alert when fewer than 30 days remain before expiry
308-
SslAssertionBuilder.certExpiresInDays().greaterThan(30)
309-
// Equivalent to: { source: 'CERT_EXPIRES_IN_DAYS', comparison: 'GREATER_THAN', target: '30' }
298+
SslAssertionBuilder.certificate("daysUntilExpiry").greaterThan(30)
299+
// Equivalent to: { source: 'CERTIFICATE', property: 'daysUntilExpiry', comparison: 'GREATER_THAN', target: '30' }
300+
301+
// The certificate chain must be trusted
302+
SslAssertionBuilder.connection("chainTrusted").equals(true)
303+
// Equivalent to: { source: 'CONNECTION', property: 'chainTrusted', comparison: 'EQUALS', target: 'true' }
310304

311-
// Require TLS 1.2 or newer
312-
SslAssertionBuilder.tlsVersion().greaterThanOrEqual(TlsVersion.TLS1_2)
313-
// Equivalent to: { source: 'TLS_VERSION', comparison: 'GREATER_THAN_OR_EQUAL', target: 'TLS1.2' }
305+
// Negotiate TLS 1.3 (use the security baseline's minTLSVersion for a "1.2-or-newer" policy)
306+
SslAssertionBuilder.connection("tlsVersion").equals(TlsVersion.TLS1_3)
307+
// Equivalent to: { source: 'CONNECTION', property: 'tlsVersion', comparison: 'EQUALS', target: 'TLS1.3' }
314308

315309
// Pin the cipher suite
316-
SslAssertionBuilder.cipherSuite().equals(CipherSuite.TLS_AES_256_GCM_SHA384)
317-
// Equivalent to: { source: 'CIPHER_SUITE', comparison: 'EQUALS', target: 'TLS_AES_256_GCM_SHA384' }
310+
SslAssertionBuilder.connection("cipherSuite").equals(CipherSuite.TLS_AES_256_GCM_SHA384)
311+
// Equivalent to: { source: 'CONNECTION', property: 'cipherSuite', comparison: 'EQUALS', target: 'TLS_AES_256_GCM_SHA384' }
318312

319313
// Verify a specific issuer
320-
SslAssertionBuilder.issuerCn().equals("Let's Encrypt")
321-
// Equivalent to: { source: 'ISSUER_CN', comparison: 'EQUALS', target: "Let's Encrypt" }
314+
SslAssertionBuilder.certificate("issuerCN").equals("Let's Encrypt")
315+
// Equivalent to: { source: 'CERTIFICATE', property: 'issuerCN', comparison: 'EQUALS', target: "Let's Encrypt" }
316+
317+
// Reject weak keys (the security baseline's minKeySizeBits enforces a hard minimum)
318+
SslAssertionBuilder.certificate("keySizeBits").greaterThan(1024)
319+
// Equivalent to: { source: 'CERTIFICATE', property: 'keySizeBits', comparison: 'GREATER_THAN', target: '1024' }
322320

323-
// Require a minimum key size
324-
SslAssertionBuilder.keySizeBits().greaterThanOrEqual(2048)
325-
// Equivalent to: { source: 'KEY_SIZE_BITS', comparison: 'GREATER_THAN_OR_EQUAL', target: '2048' }
321+
// Assert on a nested chain field with a JSONPath expression
322+
SslAssertionBuilder.jsonResponse("$.chain[0].keySizeBits").greaterThan(2048)
323+
// Equivalent to: { source: 'JSON_RESPONSE', property: '$.chain[0].keySizeBits', comparison: 'GREATER_THAN', target: '2048' }
326324
```
327325

328326
Use the `TlsVersion` and `CipherSuite` constants for type-safe comparisons:
@@ -334,8 +332,8 @@ import {
334332
TlsVersion,
335333
} from "checkly/constructs"
336334

337-
SslAssertionBuilder.tlsVersion().equals(TlsVersion.TLS1_3)
338-
SslAssertionBuilder.cipherSuite().equals(CipherSuite.TLS_AES_128_GCM_SHA256)
335+
SslAssertionBuilder.connection("tlsVersion").equals(TlsVersion.TLS1_3)
336+
SslAssertionBuilder.connection("cipherSuite").equals(CipherSuite.TLS_AES_128_GCM_SHA256)
339337
```
340338

341339
Learn more in our docs on [Assertions](/detect/assertions).
@@ -431,9 +429,9 @@ new SslMonitor("my-ssl-monitor", {
431429
},
432430
},
433431
assertions: [
434-
SslAssertionBuilder.chainTrusted().equals(true),
435-
SslAssertionBuilder.hostnameVerified().equals(true),
436-
SslAssertionBuilder.tlsVersion().greaterThanOrEqual(TlsVersion.TLS1_2),
432+
SslAssertionBuilder.connection("chainTrusted").equals(true),
433+
SslAssertionBuilder.connection("hostnameVerified").equals(true),
434+
SslAssertionBuilder.connection("tlsVersion").equals(TlsVersion.TLS1_3),
437435
],
438436
},
439437
})
@@ -456,8 +454,8 @@ new SslMonitor("my-ssl-monitor", {
456454
alertDaysBeforeExpiry: 14,
457455
},
458456
assertions: [
459-
SslAssertionBuilder.certNotExpired().equals(true),
460-
SslAssertionBuilder.certExpiresInDays().greaterThan(7),
457+
SslAssertionBuilder.certificate("selfSigned").equals(true),
458+
SslAssertionBuilder.certificate("daysUntilExpiry").greaterThan(7),
461459
],
462460
},
463461
})
@@ -479,8 +477,8 @@ new SslMonitor("my-ssl-monitor", {
479477
alertDaysBeforeExpiry: 30,
480478
},
481479
assertions: [
482-
SslAssertionBuilder.certNotExpired().equals(true),
483-
SslAssertionBuilder.hostnameVerified().equals(true),
480+
SslAssertionBuilder.certificate("daysUntilExpiry").greaterThan(0),
481+
SslAssertionBuilder.connection("hostnameVerified").equals(true),
484482
],
485483
},
486484
})

detect/uptime-monitoring/ssl-monitors/configuration.mdx

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,24 +67,50 @@ Omitting the `securityBaseline` field inherits the account-level default baselin
6767

6868
Use assertions to validate specific certificate and TLS handshake properties beyond what the security baseline covers.
6969

70-
Available assertion sources:
71-
72-
| Source | Type | Description |
73-
|--------|------|-------------|
74-
| `CERT_NOT_EXPIRED` | boolean | Certificate has not passed its `notAfter` date |
75-
| `CERT_EXPIRES_IN_DAYS` | number | Days until the certificate expires |
76-
| `HOSTNAME_VERIFIED` | boolean | The certificate's SANs cover the monitored hostname |
77-
| `CHAIN_TRUSTED` | boolean | The full certificate chain is trusted by the system root store |
78-
| `TLS_VERSION` | string | Negotiated TLS protocol version (e.g. `TLS1.2`, `TLS1.3`) |
79-
| `CIPHER_SUITE` | string | IANA cipher suite name negotiated during the handshake |
80-
| `SIGNATURE_ALGORITHM` | string | Leaf certificate signature algorithm (e.g. `SHA256-RSA`, `ECDSA-SHA256`) |
81-
| `KEY_SIZE_BITS` | number | Leaf certificate public key size in bits |
82-
| `ISSUER_CN` | string | Common name of the certificate issuer |
83-
| `CERT_FINGERPRINT_SHA256` | string | SHA-256 fingerprint of the leaf certificate |
84-
| `ISSUER_FINGERPRINT_SHA256` | string | SHA-256 fingerprint of the issuer certificate |
85-
| `SAN_CONTAINS` | string | True when any Subject Alternative Name matches the given value |
86-
| `OCSP_STAPLED` | boolean | A stapled OCSP response was included in the handshake |
87-
| `HANDSHAKE_TIME_MS` | number | TLS handshake duration in milliseconds |
70+
Each assertion has a `source`, an optional `property` (a field selector), a `comparison`, and a `target`. Available sources:
71+
72+
| Source | `property` | Description |
73+
|--------|-----------|-------------|
74+
| `CERTIFICATE` | a certificate field (see below) | Assert on a leaf-certificate field |
75+
| `CONNECTION` | a connection field (see below) | Assert on a TLS handshake / connection field |
76+
| `RESPONSE_TIME` || TLS handshake duration in milliseconds |
77+
| `JSON_RESPONSE` | JSONPath | Assert on any field of the response document via a JSONPath expression (e.g. `$.chain[0].keySizeBits`) |
78+
| `TEXT_RESPONSE` | regex | Extract a value from the serialized response with a regex, then compare |
79+
80+
`CERTIFICATE` `property` values:
81+
82+
| `property` | Type | Description |
83+
|-----------|------|-------------|
84+
| `daysUntilExpiry` | number | Days until the certificate expires |
85+
| `subjectCN` | string | Certificate subject common name |
86+
| `issuerCN` | string | Common name of the certificate issuer |
87+
| `serialNumber` | string | Certificate serial number |
88+
| `fingerprintSha256` | string | SHA-256 fingerprint of the leaf certificate |
89+
| `issuerFingerprintSha256` | string | SHA-256 fingerprint of the issuer certificate |
90+
| `keySizeBits` | number | Leaf certificate public key size in bits |
91+
| `keyAlgorithm` | string | Public key algorithm (e.g. `RSA`, `ECDSA`) |
92+
| `signatureAlgorithm` | string | Leaf certificate signature algorithm (e.g. `SHA256-RSA`, `ECDSA-SHA256`) |
93+
| `sans` | list | Subject Alternative Names — use `CONTAINS` / `NOT_CONTAINS` to test membership |
94+
| `selfSigned` | boolean | The leaf certificate is self-signed |
95+
| `isCA` | boolean | The leaf certificate is a CA certificate |
96+
97+
`CONNECTION` `property` values:
98+
99+
| `property` | Type | Description |
100+
|-----------|------|-------------|
101+
| `tlsVersion` | string | Negotiated TLS protocol version (e.g. `TLS1.2`, `TLS1.3`) |
102+
| `cipherSuite` | string | IANA cipher suite name negotiated during the handshake |
103+
| `hostnameVerified` | boolean | The certificate's SANs cover the monitored hostname |
104+
| `chainTrusted` | boolean | The full certificate chain is trusted by the system root store |
105+
| `ocspStapled` | boolean | A stapled OCSP response was included in the handshake |
106+
| `ocspStatus` | string | Decoded stapled-OCSP status: `good`, `revoked`, or `unknown` |
107+
| `resolvedIp` | string | The IP address the hostname resolved to |
108+
109+
Comparisons follow the property's type: numbers support `EQUALS`, `NOT_EQUALS`, `GREATER_THAN`, `LESS_THAN`; strings add `CONTAINS` / `NOT_CONTAINS`; booleans use `EQUALS`; the `sans` list uses `CONTAINS` / `NOT_CONTAINS`.
110+
111+
<Note>
112+
Certificate validity, minimum TLS version and minimum key size are also enforced by the security baseline above — reach for assertions when you need an exact value check (a specific issuer, fingerprint pinning, a SAN, etc.).
113+
</Note>
88114

89115
For more details, see our documentation on [Assertions](/detect/assertions).
90116

0 commit comments

Comments
 (0)