externalsignjob - External Code Signing Job (#6977)
* Moving external call back into meshcentral * Debugging logging * Moved the external call to the callback function * Updated codesigning.md * Move callback invoke of callExternalSignJob outside of err check * change console.log to obj.debug for external sign job call logging * obj debug signing failed using obj.debug and console.error inside callExternalSignJob
Chris Norman committed
Apr 21, 2025 at 11:49 UTC
5b974e8226083102ad8bcb90d3870ac0b20c395d
2 files changed
+75
docs/docs/meshcentral/codesigning.md
+47
@@ -99,3 +99,50 @@ Now that MeshCentral customizes and signs the agent, you can set that value to a
99
}
100
}
101
```
102
+
103
+## External Signing Job
104
+
105
+The externalsignjob feature allows you to perform additional operations on the agent after MeshCentral completes its code signing process. This is particularly useful for:
106
+
107
+1. Using hardware security tokens for signing
108
+2. Performing signing on a separate server or cloud host
109
+3. Archiving signed agents
110
+4. Adding additional security measures
111
+
112
+The externalsignjob is called after MeshCentral completes its entire code signing process, including:
113
+- Resource modification
114
+- Digital signature application
115
+- Timestamp application (if configured)
116
+
117
+To use this feature, add the following to your config.json:
118
+
119
+```json
120
+"settings": {
121
+ "externalsignjob": "path/to/your/script.bat"
122
+}
123
+```
124
+
125
+The script will receive the path to the agent as its first argument. Here are example scripts:
126
+
127
+### Batch File Example
128
+```batch
129
+@echo off
130
+Echo External Signing Job
131
+signtool sign /tr http://timestamp.sectigo.com /td SHA256 /fd SHA256 /a /v /f path/to/your/signing.cer /csp "eToken Base Cryptographic Provider" /k "[{{MyPassword}}]=PrivateKeyContainerName" "%~1"
132
+```
133
+
134
+### PowerShell Example
135
+```powershell
136
+$file = $args[0]
137
+signtool sign /tr http://timestamp.sectigo.com /td SHA256 /fd SHA256 /a /v /f path/to/your/signing.cer /csp "eToken Base Cryptographic Provider" /k "[{{MyPassword}}]=PrivateKeyContainerName" $file
138
+```
139
+
140
+The externalsignjob can be used for more than just signing. For example, you could:
141
+
142
+1. Archive signed agents to a secure location
143
+2. Upload signed agents to a distribution server
144
+3. Perform additional security checks
145
+4. Add custom metadata or watermarks
146
+5. Integrate with your organization's build pipeline
147
+
148
+Note: The script must return a success exit code (0) for the process to be considered successful. Any non-zero exit code will be treated as a failure and will be logged.
meshcentral.js
+28
@@ -3415,6 +3415,7 @@ function CreateMeshCentralServer(config, args) {
3415
// Failed to sign agent
3416
addServerWarning('Failed to sign \"' + agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname + '\": ' + err, 22, [agentSignedFunc.objx.meshAgentsArchitectureNumbers[agentSignedFunc.archid].localname, err]);
3417
}
3418
+ obj.callExternalSignJob(agentSignedFunc.signingArguments); // Call external signing job regardless of success or failure
3419
if (--pendingOperations === 0) { agentSignedFunc.func(); }
3420
}
3421
pendingOperations++;
@@ -3470,7 +3471,10 @@ function CreateMeshCentralServer(config, args) {
3471
}
3472
3473
const signingArguments = { out: signeedagentpath, desc: signDesc, url: signUrl, time: timeStampUrl, proxy: timeStampProxy }; // Shallow clone
3474
+ signingArguments.resChanges = resChanges;
3475
+
3476
obj.debug('main', "Code signing with arguments: " + JSON.stringify(signingArguments));
3477
+ xagentSignedFunc.signingArguments = signingArguments; // Attach the signing arguments to the callback function
3478
if (resChanges == false) {
3479
// Sign the agent the simple way, without changing any resources.
3480
originalAgent.sign(agentSignCertInfo, signingArguments, xagentSignedFunc);
@@ -3479,16 +3483,40 @@ function CreateMeshCentralServer(config, args) {
3483
// NOTE: This is experimental and could corupt the agent.
3484
originalAgent.writeExecutable(signingArguments, agentSignCertInfo, xagentSignedFunc);
3485
}
3486
+
3487
} else {
3488
// Signed agent is already ok, use it.
3489
originalAgent.close();
3490
}
3491
+
3492
+
3493
}
3494
}
3495
3496
if (--pendingOperations === 0) { func(); }
3497
}
3498
3499
+ obj.callExternalSignJob = function (signingArguments) {
3500
+ if (obj.config.settings && !obj.config.settings.externalsignjob) {
3501
+ return;
3502
+ }
3503
+ obj.debug('main', "External signing job called for file: " + signingArguments.out);
3504
+
3505
+ const { spawnSync } = require('child_process');
3506
+
3507
+ const signResult = spawnSync('"' + obj.config.settings.externalsignjob + '"', ['"' + signingArguments.out + '"'], {
3508
+ encoding: 'utf-8',
3509
+ shell: true,
3510
+ stdio: 'inherit'
3511
+ });
3512
+
3513
+ if (signResult.error || signResult.status !== 0) {
3514
+ obj.debug('main', "External signing failed for file: " + signingArguments.out);
3515
+ console.error("External signing failed for file: " + signingArguments.out);
3516
+ return;
3517
+ }
3518
+ }
3519
+
3520
// Update the list of available mesh agents
3521
obj.updateMeshAgentsTable = function (domain, func) {
3522
// Check if a custom agent signing certificate is available