@checkstack/healthcheck-tls-backend 0.1.13 → 0.1.14
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.
- package/CHANGELOG.md +30 -0
- package/package.json +6 -6
- package/src/certificate-collector.test.ts +5 -3
- package/src/certificate-collector.ts +40 -29
- package/src/strategy.test.ts +5 -3
- package/src/strategy.ts +59 -45
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @checkstack/healthcheck-tls-backend
|
|
2
2
|
|
|
3
|
+
## 0.1.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 48c2080: Migrate aggregation from batch to incremental (`mergeResult`)
|
|
8
|
+
|
|
9
|
+
### Breaking Changes (Internal)
|
|
10
|
+
|
|
11
|
+
- Replaced `aggregateResult(runs[])` with `mergeResult(existing, run)` interface across all HealthCheckStrategy and CollectorStrategy implementations
|
|
12
|
+
|
|
13
|
+
### New Features
|
|
14
|
+
|
|
15
|
+
- Added incremental aggregation utilities in `@checkstack/backend-api`:
|
|
16
|
+
- `mergeCounter()` - track occurrences
|
|
17
|
+
- `mergeAverage()` - track sum/count, compute avg
|
|
18
|
+
- `mergeRate()` - track success/total, compute %
|
|
19
|
+
- `mergeMinMax()` - track min/max values
|
|
20
|
+
- Exported Zod schemas for internal state: `averageStateSchema`, `rateStateSchema`, `minMaxStateSchema`, `counterStateSchema`
|
|
21
|
+
|
|
22
|
+
### Improvements
|
|
23
|
+
|
|
24
|
+
- Enables O(1) storage overhead by maintaining incremental aggregation state
|
|
25
|
+
- Prepares for real-time hourly aggregation without batch accumulation
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [f676e11]
|
|
28
|
+
- Updated dependencies [48c2080]
|
|
29
|
+
- @checkstack/common@0.6.2
|
|
30
|
+
- @checkstack/backend-api@0.6.0
|
|
31
|
+
- @checkstack/healthcheck-common@0.8.2
|
|
32
|
+
|
|
3
33
|
## 0.1.13
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-tls-backend",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"lint:code": "eslint . --max-warnings 0"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@checkstack/backend-api": "0.5.
|
|
13
|
-
"@checkstack/common": "0.6.
|
|
14
|
-
"@checkstack/healthcheck-common": "0.
|
|
12
|
+
"@checkstack/backend-api": "0.5.2",
|
|
13
|
+
"@checkstack/common": "0.6.1",
|
|
14
|
+
"@checkstack/healthcheck-common": "0.8.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/bun": "^1.0.0",
|
|
18
18
|
"typescript": "^5.0.0",
|
|
19
|
-
"@checkstack/tsconfig": "0.0.
|
|
20
|
-
"@checkstack/scripts": "0.1.
|
|
19
|
+
"@checkstack/tsconfig": "0.0.3",
|
|
20
|
+
"@checkstack/scripts": "0.1.1"
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -76,7 +76,7 @@ describe("CertificateCollector", () => {
|
|
|
76
76
|
});
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
describe("
|
|
79
|
+
describe("mergeResult", () => {
|
|
80
80
|
it("should calculate average days remaining", () => {
|
|
81
81
|
const collector = new CertificateCollector();
|
|
82
82
|
const runs = [
|
|
@@ -112,7 +112,8 @@ describe("CertificateCollector", () => {
|
|
|
112
112
|
},
|
|
113
113
|
];
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
116
|
+
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
116
117
|
|
|
117
118
|
expect(aggregated.avgDaysRemaining).toBe(45);
|
|
118
119
|
expect(aggregated.validRate).toBe(100);
|
|
@@ -153,7 +154,8 @@ describe("CertificateCollector", () => {
|
|
|
153
154
|
},
|
|
154
155
|
];
|
|
155
156
|
|
|
156
|
-
|
|
157
|
+
let aggregated = collector.mergeResult(undefined, runs[0]);
|
|
158
|
+
aggregated = collector.mergeResult(aggregated, runs[1]);
|
|
157
159
|
|
|
158
160
|
expect(aggregated.validRate).toBe(50);
|
|
159
161
|
});
|
|
@@ -4,6 +4,12 @@ import {
|
|
|
4
4
|
type HealthCheckRunForAggregation,
|
|
5
5
|
type CollectorResult,
|
|
6
6
|
type CollectorStrategy,
|
|
7
|
+
mergeAverage,
|
|
8
|
+
averageStateSchema,
|
|
9
|
+
mergeRate,
|
|
10
|
+
rateStateSchema,
|
|
11
|
+
type AverageState,
|
|
12
|
+
type RateState,
|
|
7
13
|
} from "@checkstack/backend-api";
|
|
8
14
|
import {
|
|
9
15
|
healthResultNumber,
|
|
@@ -58,7 +64,7 @@ const certificateResultSchema = healthResultSchema({
|
|
|
58
64
|
|
|
59
65
|
export type CertificateResult = z.infer<typeof certificateResultSchema>;
|
|
60
66
|
|
|
61
|
-
const
|
|
67
|
+
const certificateAggregatedDisplaySchema = healthResultSchema({
|
|
62
68
|
avgDaysRemaining: healthResultNumber({
|
|
63
69
|
"x-chart-type": "gauge",
|
|
64
70
|
"x-chart-label": "Avg Days Remaining",
|
|
@@ -71,6 +77,17 @@ const certificateAggregatedSchema = healthResultSchema({
|
|
|
71
77
|
}),
|
|
72
78
|
});
|
|
73
79
|
|
|
80
|
+
const certificateAggregatedInternalSchema = z.object({
|
|
81
|
+
_daysRemaining: averageStateSchema
|
|
82
|
+
.optional(),
|
|
83
|
+
_valid: rateStateSchema
|
|
84
|
+
.optional(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const certificateAggregatedSchema = certificateAggregatedDisplaySchema.merge(
|
|
88
|
+
certificateAggregatedInternalSchema,
|
|
89
|
+
);
|
|
90
|
+
|
|
74
91
|
export type CertificateAggregatedResult = z.infer<
|
|
75
92
|
typeof certificateAggregatedSchema
|
|
76
93
|
>;
|
|
@@ -83,15 +100,12 @@ export type CertificateAggregatedResult = z.infer<
|
|
|
83
100
|
* Built-in TLS certificate collector.
|
|
84
101
|
* Returns certificate information from the TLS connection.
|
|
85
102
|
*/
|
|
86
|
-
export class CertificateCollector
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
CertificateAggregatedResult
|
|
93
|
-
>
|
|
94
|
-
{
|
|
103
|
+
export class CertificateCollector implements CollectorStrategy<
|
|
104
|
+
TlsTransportClient,
|
|
105
|
+
CertificateConfig,
|
|
106
|
+
CertificateResult,
|
|
107
|
+
CertificateAggregatedResult
|
|
108
|
+
> {
|
|
95
109
|
id = "certificate";
|
|
96
110
|
displayName = "TLS Certificate";
|
|
97
111
|
description = "Check TLS certificate validity and expiration";
|
|
@@ -142,30 +156,27 @@ export class CertificateCollector
|
|
|
142
156
|
};
|
|
143
157
|
}
|
|
144
158
|
|
|
145
|
-
|
|
146
|
-
|
|
159
|
+
mergeResult(
|
|
160
|
+
existing: CertificateAggregatedResult | undefined,
|
|
161
|
+
run: HealthCheckRunForAggregation<CertificateResult>,
|
|
147
162
|
): CertificateAggregatedResult {
|
|
148
|
-
const
|
|
149
|
-
.map((r) => r.metadata?.daysRemaining)
|
|
150
|
-
.filter((v): v is number => typeof v === "number");
|
|
163
|
+
const metadata = run.metadata;
|
|
151
164
|
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
const daysState = mergeAverage(
|
|
166
|
+
existing?._daysRemaining as AverageState | undefined,
|
|
167
|
+
metadata?.daysRemaining,
|
|
168
|
+
);
|
|
155
169
|
|
|
156
|
-
const
|
|
170
|
+
const validState = mergeRate(
|
|
171
|
+
existing?._valid as RateState | undefined,
|
|
172
|
+
metadata?.valid,
|
|
173
|
+
);
|
|
157
174
|
|
|
158
175
|
return {
|
|
159
|
-
avgDaysRemaining:
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
)
|
|
164
|
-
: 0,
|
|
165
|
-
validRate:
|
|
166
|
-
validResults.length > 0
|
|
167
|
-
? Math.round((validCount / validResults.length) * 100)
|
|
168
|
-
: 0,
|
|
176
|
+
avgDaysRemaining: daysState.avg,
|
|
177
|
+
validRate: validState.rate,
|
|
178
|
+
_daysRemaining: daysState,
|
|
179
|
+
_valid: validState,
|
|
169
180
|
};
|
|
170
181
|
}
|
|
171
182
|
}
|
package/src/strategy.test.ts
CHANGED
|
@@ -164,7 +164,7 @@ describe("TlsHealthCheckStrategy", () => {
|
|
|
164
164
|
});
|
|
165
165
|
});
|
|
166
166
|
|
|
167
|
-
describe("
|
|
167
|
+
describe("mergeResult", () => {
|
|
168
168
|
it("should calculate averages correctly", () => {
|
|
169
169
|
const strategy = new TlsHealthCheckStrategy();
|
|
170
170
|
const runs = [
|
|
@@ -204,7 +204,8 @@ describe("TlsHealthCheckStrategy", () => {
|
|
|
204
204
|
},
|
|
205
205
|
];
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
208
|
+
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
208
209
|
|
|
209
210
|
expect(aggregated.avgDaysUntilExpiry).toBe(25);
|
|
210
211
|
expect(aggregated.minDaysUntilExpiry).toBe(20);
|
|
@@ -252,7 +253,8 @@ describe("TlsHealthCheckStrategy", () => {
|
|
|
252
253
|
},
|
|
253
254
|
];
|
|
254
255
|
|
|
255
|
-
|
|
256
|
+
let aggregated = strategy.mergeResult(undefined, runs[0]);
|
|
257
|
+
aggregated = strategy.mergeResult(aggregated, runs[1]);
|
|
256
258
|
|
|
257
259
|
expect(aggregated.invalidCount).toBe(2);
|
|
258
260
|
expect(aggregated.errorCount).toBe(1);
|
package/src/strategy.ts
CHANGED
|
@@ -5,6 +5,14 @@ import {
|
|
|
5
5
|
Versioned,
|
|
6
6
|
z,
|
|
7
7
|
type ConnectedClient,
|
|
8
|
+
mergeAverage,
|
|
9
|
+
averageStateSchema,
|
|
10
|
+
mergeCounter,
|
|
11
|
+
counterStateSchema,
|
|
12
|
+
mergeMinMax,
|
|
13
|
+
type AverageState,
|
|
14
|
+
type CounterState,
|
|
15
|
+
type MinMaxState,
|
|
8
16
|
} from "@checkstack/backend-api";
|
|
9
17
|
import {
|
|
10
18
|
healthResultBoolean,
|
|
@@ -83,7 +91,7 @@ type TlsResult = z.infer<typeof tlsResultSchema>;
|
|
|
83
91
|
/**
|
|
84
92
|
* Aggregated metadata for buckets.
|
|
85
93
|
*/
|
|
86
|
-
const
|
|
94
|
+
const tlsAggregatedDisplaySchema = healthResultSchema({
|
|
87
95
|
avgDaysUntilExpiry: healthResultNumber({
|
|
88
96
|
"x-chart-type": "line",
|
|
89
97
|
"x-chart-label": "Avg Days Until Expiry",
|
|
@@ -104,6 +112,19 @@ const tlsAggregatedSchema = healthResultSchema({
|
|
|
104
112
|
}),
|
|
105
113
|
});
|
|
106
114
|
|
|
115
|
+
const tlsAggregatedInternalSchema = z.object({
|
|
116
|
+
_daysUntilExpiry: averageStateSchema.optional(),
|
|
117
|
+
_minDaysUntilExpiry: z
|
|
118
|
+
.object({ min: z.number(), max: z.number() })
|
|
119
|
+
.optional(),
|
|
120
|
+
_invalid: counterStateSchema.optional(),
|
|
121
|
+
_errors: counterStateSchema.optional(),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const tlsAggregatedSchema = tlsAggregatedDisplaySchema.merge(
|
|
125
|
+
tlsAggregatedInternalSchema,
|
|
126
|
+
);
|
|
127
|
+
|
|
107
128
|
type TlsAggregatedResult = z.infer<typeof tlsAggregatedSchema>;
|
|
108
129
|
|
|
109
130
|
// ============================================================================
|
|
@@ -156,7 +177,7 @@ const defaultTlsClient: TlsClient = {
|
|
|
156
177
|
getCipher: () => socket.getCipher(),
|
|
157
178
|
end: () => socket.end(),
|
|
158
179
|
});
|
|
159
|
-
}
|
|
180
|
+
},
|
|
160
181
|
);
|
|
161
182
|
|
|
162
183
|
socket.on("error", reject);
|
|
@@ -172,15 +193,12 @@ const defaultTlsClient: TlsClient = {
|
|
|
172
193
|
// STRATEGY
|
|
173
194
|
// ============================================================================
|
|
174
195
|
|
|
175
|
-
export class TlsHealthCheckStrategy
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
TlsAggregatedResult
|
|
182
|
-
>
|
|
183
|
-
{
|
|
196
|
+
export class TlsHealthCheckStrategy implements HealthCheckStrategy<
|
|
197
|
+
TlsConfig,
|
|
198
|
+
TlsTransportClient,
|
|
199
|
+
TlsResult,
|
|
200
|
+
TlsAggregatedResult
|
|
201
|
+
> {
|
|
184
202
|
id = "tls";
|
|
185
203
|
displayName = "TLS/SSL Health Check";
|
|
186
204
|
description = "SSL/TLS certificate validation and expiry monitoring";
|
|
@@ -222,50 +240,46 @@ export class TlsHealthCheckStrategy
|
|
|
222
240
|
schema: tlsAggregatedSchema,
|
|
223
241
|
});
|
|
224
242
|
|
|
225
|
-
|
|
226
|
-
|
|
243
|
+
mergeResult(
|
|
244
|
+
existing: TlsAggregatedResult | undefined,
|
|
245
|
+
run: HealthCheckRunForAggregation<TlsResult>,
|
|
227
246
|
): TlsAggregatedResult {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
if (validRuns.length === 0) {
|
|
231
|
-
return {
|
|
232
|
-
avgDaysUntilExpiry: 0,
|
|
233
|
-
minDaysUntilExpiry: 0,
|
|
234
|
-
invalidCount: 0,
|
|
235
|
-
errorCount: 0,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
247
|
+
const metadata = run.metadata;
|
|
238
248
|
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const avgDaysUntilExpiry =
|
|
244
|
-
daysValues.length > 0
|
|
245
|
-
? Math.round(daysValues.reduce((a, b) => a + b, 0) / daysValues.length)
|
|
246
|
-
: 0;
|
|
249
|
+
const daysState = mergeAverage(
|
|
250
|
+
existing?._daysUntilExpiry as AverageState | undefined,
|
|
251
|
+
metadata?.daysUntilExpiry,
|
|
252
|
+
);
|
|
247
253
|
|
|
248
|
-
const
|
|
249
|
-
|
|
254
|
+
const minDaysState = mergeMinMax(
|
|
255
|
+
existing?._minDaysUntilExpiry as MinMaxState | undefined,
|
|
256
|
+
metadata?.daysUntilExpiry,
|
|
257
|
+
);
|
|
250
258
|
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
259
|
+
const invalidState = mergeCounter(
|
|
260
|
+
existing?._invalid as CounterState | undefined,
|
|
261
|
+
metadata?.isValid === false,
|
|
262
|
+
);
|
|
254
263
|
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
264
|
+
const errorState = mergeCounter(
|
|
265
|
+
existing?._errors as CounterState | undefined,
|
|
266
|
+
metadata?.error !== undefined,
|
|
267
|
+
);
|
|
258
268
|
|
|
259
269
|
return {
|
|
260
|
-
avgDaysUntilExpiry,
|
|
261
|
-
minDaysUntilExpiry,
|
|
262
|
-
invalidCount,
|
|
263
|
-
errorCount,
|
|
270
|
+
avgDaysUntilExpiry: daysState.avg,
|
|
271
|
+
minDaysUntilExpiry: minDaysState.min,
|
|
272
|
+
invalidCount: invalidState.count,
|
|
273
|
+
errorCount: errorState.count,
|
|
274
|
+
_daysUntilExpiry: daysState,
|
|
275
|
+
_minDaysUntilExpiry: minDaysState,
|
|
276
|
+
_invalid: invalidState,
|
|
277
|
+
_errors: errorState,
|
|
264
278
|
};
|
|
265
279
|
}
|
|
266
280
|
|
|
267
281
|
async createClient(
|
|
268
|
-
config: TlsConfig
|
|
282
|
+
config: TlsConfig,
|
|
269
283
|
): Promise<ConnectedClient<TlsTransportClient>> {
|
|
270
284
|
const validatedConfig = this.config.validate(config);
|
|
271
285
|
|
|
@@ -280,7 +294,7 @@ export class TlsHealthCheckStrategy
|
|
|
280
294
|
const cert = connection.getPeerCertificate();
|
|
281
295
|
const validTo = new Date(cert.valid_to);
|
|
282
296
|
const daysUntilExpiry = Math.floor(
|
|
283
|
-
(validTo.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
|
|
297
|
+
(validTo.getTime() - Date.now()) / (1000 * 60 * 60 * 24),
|
|
284
298
|
);
|
|
285
299
|
|
|
286
300
|
const certInfo: TlsCertificateInfo = {
|