@zincapp/zn-vault-agent 1.5.0 → 1.6.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.
@@ -0,0 +1,212 @@
1
+ // Path: src/services/managed-key-renewal.ts
2
+ // Automatic managed API key renewal service
3
+ // Schedules key refresh based on rotation metadata from the vault server
4
+ import { loadConfig, updateManagedKey, isManagedKeyMode } from '../lib/config.js';
5
+ import { bindManagedApiKey } from '../lib/api.js';
6
+ import { createLogger } from '../lib/logger.js';
7
+ const log = createLogger({ module: 'managed-key-renewal' });
8
+ // How early before rotation to refresh (30 seconds default)
9
+ const DEFAULT_REFRESH_BEFORE_MS = 30 * 1000;
10
+ // Minimum refresh interval (don't refresh more than once per minute)
11
+ const MIN_REFRESH_INTERVAL_MS = 60 * 1000;
12
+ // Fallback interval if no rotation time is known (5 minutes)
13
+ const FALLBACK_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
14
+ let refreshTimer = null;
15
+ let isRunning = false;
16
+ let currentKey = null;
17
+ // Callback for when key changes (used to notify other parts of the system)
18
+ let onKeyChangedCallback = null;
19
+ /**
20
+ * Set callback for when the API key changes
21
+ * This allows other parts of the system (e.g., WebSocket) to be notified
22
+ */
23
+ export function onKeyChanged(callback) {
24
+ onKeyChangedCallback = callback;
25
+ }
26
+ /**
27
+ * Perform a managed key bind and update config
28
+ */
29
+ async function refreshManagedKey() {
30
+ const config = loadConfig();
31
+ if (!config.managedKey?.name) {
32
+ log.debug('No managed key configured, skipping refresh');
33
+ return null;
34
+ }
35
+ if (!config.auth.apiKey) {
36
+ log.error('No API key available to perform bind');
37
+ return null;
38
+ }
39
+ try {
40
+ log.debug({ name: config.managedKey.name }, 'Binding to managed key');
41
+ const bindResponse = await bindManagedApiKey(config.managedKey.name);
42
+ const oldKey = currentKey;
43
+ currentKey = bindResponse.key;
44
+ // Update config with new key and metadata
45
+ updateManagedKey(bindResponse.key, {
46
+ nextRotationAt: bindResponse.nextRotationAt,
47
+ graceExpiresAt: bindResponse.graceExpiresAt,
48
+ rotationMode: bindResponse.rotationMode,
49
+ });
50
+ // Notify if key changed
51
+ if (oldKey && oldKey !== bindResponse.key) {
52
+ log.info({
53
+ oldPrefix: oldKey.substring(0, 8),
54
+ newPrefix: bindResponse.key.substring(0, 8),
55
+ nextRotationAt: bindResponse.nextRotationAt,
56
+ }, 'Managed key rotated');
57
+ if (onKeyChangedCallback) {
58
+ onKeyChangedCallback(bindResponse.key);
59
+ }
60
+ }
61
+ else {
62
+ log.debug({
63
+ prefix: bindResponse.key.substring(0, 8),
64
+ nextRotationAt: bindResponse.nextRotationAt,
65
+ }, 'Managed key refreshed (no change)');
66
+ }
67
+ return bindResponse;
68
+ }
69
+ catch (err) {
70
+ log.error({ err, name: config.managedKey.name }, 'Failed to bind managed key');
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Calculate when to next refresh the key
76
+ */
77
+ function calculateNextRefreshMs(bindResponse) {
78
+ const config = loadConfig();
79
+ const now = Date.now();
80
+ // Priority 1: Use nextRotationAt from bind response or config
81
+ const nextRotationAt = bindResponse?.nextRotationAt || config.managedKey?.nextRotationAt;
82
+ if (nextRotationAt) {
83
+ const rotationTime = new Date(nextRotationAt).getTime();
84
+ const refreshTime = rotationTime - DEFAULT_REFRESH_BEFORE_MS;
85
+ const delay = Math.max(refreshTime - now, MIN_REFRESH_INTERVAL_MS);
86
+ log.debug({
87
+ nextRotationAt,
88
+ refreshInMs: delay,
89
+ refreshInMinutes: Math.round(delay / 60000),
90
+ }, 'Scheduled refresh based on nextRotationAt');
91
+ return delay;
92
+ }
93
+ // Priority 2: Use graceExpiresAt (refresh well before grace ends)
94
+ const graceExpiresAt = bindResponse?.graceExpiresAt || config.managedKey?.graceExpiresAt;
95
+ if (graceExpiresAt) {
96
+ const graceEndTime = new Date(graceExpiresAt).getTime();
97
+ // Refresh at 50% of remaining grace period
98
+ const refreshTime = now + (graceEndTime - now) / 2;
99
+ const delay = Math.max(refreshTime - now, MIN_REFRESH_INTERVAL_MS);
100
+ log.debug({
101
+ graceExpiresAt,
102
+ refreshInMs: delay,
103
+ refreshInMinutes: Math.round(delay / 60000),
104
+ }, 'Scheduled refresh based on graceExpiresAt');
105
+ return delay;
106
+ }
107
+ // Fallback: use default interval
108
+ log.debug({
109
+ refreshInMs: FALLBACK_REFRESH_INTERVAL_MS,
110
+ refreshInMinutes: FALLBACK_REFRESH_INTERVAL_MS / 60000,
111
+ }, 'Using fallback refresh interval (no rotation time available)');
112
+ return FALLBACK_REFRESH_INTERVAL_MS;
113
+ }
114
+ /**
115
+ * Schedule the next key refresh
116
+ */
117
+ function scheduleNextRefresh(bindResponse) {
118
+ // Clear any existing timer
119
+ if (refreshTimer) {
120
+ clearTimeout(refreshTimer);
121
+ refreshTimer = null;
122
+ }
123
+ if (!isRunning) {
124
+ return;
125
+ }
126
+ const delay = calculateNextRefreshMs(bindResponse);
127
+ refreshTimer = setTimeout(async () => {
128
+ if (!isRunning)
129
+ return;
130
+ try {
131
+ const response = await refreshManagedKey();
132
+ scheduleNextRefresh(response);
133
+ }
134
+ catch (err) {
135
+ log.error({ err }, 'Managed key refresh failed, retrying in 1 minute');
136
+ // On error, retry in 1 minute
137
+ scheduleNextRefresh(null);
138
+ }
139
+ }, delay);
140
+ log.info({
141
+ refreshInMinutes: Math.round(delay / 60000),
142
+ refreshAt: new Date(Date.now() + delay).toISOString(),
143
+ }, 'Managed key refresh scheduled');
144
+ }
145
+ /**
146
+ * Start the managed key renewal service
147
+ * Returns the initial bind response or null if not in managed key mode
148
+ */
149
+ export async function startManagedKeyRenewal() {
150
+ if (isRunning) {
151
+ log.warn('Managed key renewal service already running');
152
+ return null;
153
+ }
154
+ if (!isManagedKeyMode()) {
155
+ log.debug('Not in managed key mode, service not started');
156
+ return null;
157
+ }
158
+ const config = loadConfig();
159
+ isRunning = true;
160
+ currentKey = config.auth.apiKey || null;
161
+ log.info({
162
+ managedKeyName: config.managedKey?.name,
163
+ rotationMode: config.managedKey?.rotationMode,
164
+ }, 'Starting managed key renewal service');
165
+ // Perform initial bind
166
+ const bindResponse = await refreshManagedKey();
167
+ // Schedule next refresh
168
+ scheduleNextRefresh(bindResponse);
169
+ return bindResponse;
170
+ }
171
+ /**
172
+ * Stop the managed key renewal service
173
+ */
174
+ export function stopManagedKeyRenewal() {
175
+ if (refreshTimer) {
176
+ clearTimeout(refreshTimer);
177
+ refreshTimer = null;
178
+ }
179
+ isRunning = false;
180
+ currentKey = null;
181
+ onKeyChangedCallback = null;
182
+ log.debug('Managed key renewal service stopped');
183
+ }
184
+ /**
185
+ * Force an immediate key refresh
186
+ */
187
+ export async function forceRefresh() {
188
+ if (!isManagedKeyMode()) {
189
+ log.warn('Cannot force refresh - not in managed key mode');
190
+ return null;
191
+ }
192
+ const response = await refreshManagedKey();
193
+ if (isRunning) {
194
+ scheduleNextRefresh(response);
195
+ }
196
+ return response;
197
+ }
198
+ /**
199
+ * Get current managed key status
200
+ */
201
+ export function getManagedKeyStatus() {
202
+ const config = loadConfig();
203
+ return {
204
+ isRunning,
205
+ isManagedMode: isManagedKeyMode(),
206
+ managedKeyName: config.managedKey?.name,
207
+ currentKeyPrefix: currentKey ? currentKey.substring(0, 8) + '...' : undefined,
208
+ nextRotationAt: config.managedKey?.nextRotationAt,
209
+ graceExpiresAt: config.managedKey?.graceExpiresAt,
210
+ };
211
+ }
212
+ //# sourceMappingURL=managed-key-renewal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-key-renewal.js","sourceRoot":"","sources":["../../src/services/managed-key-renewal.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zincapp/zn-vault-agent",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "ZN-Vault Certificate Agent - Real-time certificate and secret distribution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",