mirror of
https://github.com/ptarmiganlabs/butler-sos.git
synced 2025-12-19 17:58:18 -05:00
Merge branch 'master' into copilot/fix-1042
This commit is contained in:
140
docs/system-information-security.md
Normal file
140
docs/system-information-security.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# System Information and OS Command Execution
|
||||
|
||||
## Overview
|
||||
|
||||
Butler SOS collects system information for monitoring and diagnostic purposes. This information helps with troubleshooting, resource monitoring, and identifying system characteristics. However, on Windows systems, this may trigger security alerts in enterprise environments due to OS command execution.
|
||||
|
||||
## Root Cause
|
||||
|
||||
Butler SOS uses the [systeminformation](https://www.npmjs.com/package/systeminformation) npm package to gather detailed system information. On Windows, this package internally executes several OS commands to collect system details:
|
||||
|
||||
- `cmd.exe /d /s /c \chcp` - Gets code page information
|
||||
- `netstat -r` - Gets routing table information
|
||||
- `cmd.exe /d /s /c \echo %COMPUTERNAME%.%USERDNSDOMAIN%` - Gets computer name and domain
|
||||
|
||||
These commands are **not executed directly by Butler SOS** but by the systeminformation package dependency. The commands are legitimate system information gathering commands, but they may trigger alerts in security monitoring tools.
|
||||
|
||||
## Code Location
|
||||
|
||||
The system information gathering occurs in the `initHostInfo()` function in `src/globals.js` (lines 1027-1085), which is called during Butler SOS startup from `src/butler-sos.js`.
|
||||
|
||||
## Security Configuration
|
||||
|
||||
Starting with version 11.1.0, Butler SOS provides a configuration option to disable detailed system information gathering for security-sensitive environments.
|
||||
|
||||
### Configuration Option
|
||||
|
||||
Add this section to your Butler SOS configuration file:
|
||||
|
||||
```yaml
|
||||
Butler-SOS:
|
||||
# System information gathering
|
||||
# Butler SOS collects system information for monitoring and diagnostic purposes.
|
||||
# On Windows, this may trigger security alerts as it executes OS commands like:
|
||||
# - cmd.exe /d /s /c \chcp (to get code page info)
|
||||
# - netstat -r (to get routing table)
|
||||
# - cmd.exe /d /s /c \echo %COMPUTERNAME%.%USERDNSDOMAIN% (to get computer/domain names)
|
||||
# These commands are executed by the 'systeminformation' npm package, not directly by Butler SOS.
|
||||
systemInfo:
|
||||
enable: true # Set to false in security-sensitive environments
|
||||
```
|
||||
|
||||
### When to Disable System Information
|
||||
|
||||
Consider setting `systemInfo.enable: false` if:
|
||||
|
||||
- Your security monitoring tools flag the OS command execution as suspicious
|
||||
- Your organization has strict policies against any automated OS command execution
|
||||
- You don't need detailed system information in logs and monitoring outputs
|
||||
- Butler SOS runs in a highly secured environment
|
||||
|
||||
### Impact of Disabling System Information
|
||||
|
||||
When `systemInfo.enable` is set to `false`:
|
||||
|
||||
**✅ Benefits:**
|
||||
- No OS commands are executed by the systeminformation package
|
||||
- Eliminates security alerts from monitoring tools
|
||||
- Butler SOS continues to function normally
|
||||
- Basic system information is still collected using Node.js built-in APIs
|
||||
|
||||
**⚠️ Limitations:**
|
||||
- Reduced detail in system information logs
|
||||
- Some monitoring dashboards may show less detailed host information
|
||||
- Telemetry data will contain minimal system details
|
||||
|
||||
**What's Still Collected:**
|
||||
- Node.js version and platform information
|
||||
- Basic OS platform, architecture, and version
|
||||
- Memory and CPU count from Node.js APIs
|
||||
- Application version and instance ID
|
||||
|
||||
**What's Not Collected:**
|
||||
- Detailed CPU model and specifications
|
||||
- Detailed OS distribution information
|
||||
- Network interface details
|
||||
- Docker information
|
||||
- Detailed memory specifications
|
||||
|
||||
## Example Configuration Files
|
||||
|
||||
### High Security Environment
|
||||
```yaml
|
||||
Butler-SOS:
|
||||
systemInfo:
|
||||
enable: false # Disable to prevent OS command execution
|
||||
# ... rest of configuration
|
||||
```
|
||||
|
||||
### Standard Environment
|
||||
```yaml
|
||||
Butler-SOS:
|
||||
systemInfo:
|
||||
enable: true # Default - enables full system information gathering
|
||||
# ... rest of configuration
|
||||
```
|
||||
|
||||
## Testing the Configuration
|
||||
|
||||
To test that the configuration is working correctly:
|
||||
|
||||
1. Set `systemInfo.enable: false` in your config
|
||||
2. Start Butler SOS
|
||||
3. Check the logs for: `"SYSTEM INFO: Detailed system information gathering is disabled. Using minimal system info."`
|
||||
4. Verify that your security monitoring tools no longer flag the OS command execution
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Configuration Validation Errors
|
||||
|
||||
If you see configuration validation errors related to `systemInfo`:
|
||||
|
||||
1. Ensure the `systemInfo` section is properly nested under `Butler-SOS`
|
||||
2. Verify that `enable` is set to a boolean value (`true` or `false`), not a string
|
||||
3. Check YAML indentation is correct
|
||||
|
||||
### Missing System Information
|
||||
|
||||
If you need some system information but want to minimize OS command execution:
|
||||
|
||||
1. Consider using `systemInfo.enable: true` but monitor which specific commands trigger alerts
|
||||
2. Work with your security team to whitelist the specific systeminformation package commands
|
||||
3. Use Butler SOS logging to capture the minimal system information that's still collected
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Principle of Least Privilege**: Only enable detailed system information gathering if you need it for your monitoring use case
|
||||
2. **Security Monitoring**: Work with your security team to understand which specific commands trigger alerts
|
||||
3. **Documentation**: Document your `systemInfo.enable` setting choice in your deployment documentation
|
||||
4. **Testing**: Test configuration changes in a development environment first
|
||||
5. **Monitoring**: Monitor Butler SOS logs to ensure it's working correctly with your chosen configuration
|
||||
|
||||
## Related Issues
|
||||
|
||||
- [Issue #1037](https://github.com/ptarmiganlabs/butler-sos/issues/1037) - Original investigation of OS command execution
|
||||
- [systeminformation package documentation](https://github.com/sebhildebrandt/systeminformation) - For understanding what information is collected
|
||||
|
||||
## Version History
|
||||
|
||||
- **11.1.0**: Added `systemInfo.enable` configuration option
|
||||
- **11.0.3**: Initial report of OS command execution alerts
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1022,17 +1022,68 @@ class Settings {
|
||||
* Gathers and returns information about the host system where Butler SOS is running.
|
||||
* Includes OS details, network info, hardware details, and a unique ID.
|
||||
*
|
||||
* Note: On Windows, this function may execute OS commands via the 'systeminformation' npm package:
|
||||
* - cmd.exe /d /s /c \chcp (to get code page info)
|
||||
* - netstat -r (to get routing table info)
|
||||
* - cmd.exe /d /s /c \echo %COMPUTERNAME%.%USERDNSDOMAIN% (to get computer/domain names)
|
||||
*
|
||||
* These commands are not executed directly by Butler SOS, but by the systeminformation package
|
||||
* to gather system details. If this triggers security alerts, you can disable detailed system
|
||||
* information gathering by setting Butler-SOS.systemInfo.enable to false in the config file.
|
||||
*
|
||||
* @returns {object | null} Object containing host information or null if an error occurs
|
||||
*/
|
||||
async initHostInfo() {
|
||||
try {
|
||||
const siCPU = await si.cpu();
|
||||
const siSystem = await si.system();
|
||||
const siMem = await si.mem();
|
||||
const siOS = await si.osInfo();
|
||||
const siDocker = await si.dockerInfo();
|
||||
const siNetwork = await si.networkInterfaces();
|
||||
const siNetworkDefault = await si.networkInterfaceDefault();
|
||||
// Check if detailed system info gathering is enabled
|
||||
const enableSystemInfo = this.config.get('Butler-SOS.systemInfo.enable');
|
||||
|
||||
let siCPU = {};
|
||||
let siSystem = {};
|
||||
let siMem = {};
|
||||
let siOS = {};
|
||||
let siDocker = {};
|
||||
let siNetwork = [];
|
||||
let siNetworkDefault = '';
|
||||
|
||||
// Only gather detailed system info if enabled in config
|
||||
if (enableSystemInfo) {
|
||||
siCPU = await si.cpu();
|
||||
siSystem = await si.system();
|
||||
siMem = await si.mem();
|
||||
siOS = await si.osInfo();
|
||||
siDocker = await si.dockerInfo();
|
||||
siNetwork = await si.networkInterfaces();
|
||||
siNetworkDefault = await si.networkInterfaceDefault();
|
||||
} else {
|
||||
// If detailed system info is disabled, use minimal fallback values
|
||||
this.logger.info(
|
||||
'SYSTEM INFO: Detailed system information gathering is disabled. Using minimal system info.'
|
||||
);
|
||||
siSystem = { uuid: 'disabled' };
|
||||
siMem = { total: 0 };
|
||||
siOS = {
|
||||
platform: os.platform(),
|
||||
arch: os.arch(),
|
||||
release: 'unknown',
|
||||
distro: 'unknown',
|
||||
codename: 'unknown',
|
||||
};
|
||||
siCPU = {
|
||||
processors: 1,
|
||||
physicalCores: 1,
|
||||
cores: 1,
|
||||
hypervizor: 'unknown',
|
||||
};
|
||||
siNetwork = [
|
||||
{
|
||||
iface: 'default',
|
||||
mac: '00:00:00:00:00:00',
|
||||
ip4: '127.0.0.1',
|
||||
},
|
||||
];
|
||||
siNetworkDefault = 'default';
|
||||
}
|
||||
|
||||
const defaultNetworkInterface = siNetworkDefault;
|
||||
|
||||
@@ -1040,8 +1091,17 @@ class Settings {
|
||||
(item) => item.iface === defaultNetworkInterface
|
||||
);
|
||||
|
||||
const idSrc = networkInterface[0].mac + networkInterface[0].ip4 + siSystem.uuid;
|
||||
const salt = networkInterface[0].mac;
|
||||
// Ensure we have at least one network interface for ID generation
|
||||
const netIface =
|
||||
networkInterface.length > 0
|
||||
? networkInterface[0]
|
||||
: siNetwork[0] || {
|
||||
mac: '00:00:00:00:00:00',
|
||||
ip4: '127.0.0.1',
|
||||
};
|
||||
|
||||
const idSrc = netIface.mac + netIface.ip4 + siSystem.uuid;
|
||||
const salt = netIface.mac;
|
||||
const hash = crypto.createHmac('sha256', salt);
|
||||
hash.update(idSrc);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Test suite for conditional configuration validation based on feature enable flags.
|
||||
*
|
||||
*
|
||||
* Tests that Butler SOS only validates configuration sections when the associated feature is enabled.
|
||||
* When a feature is disabled, its detailed configuration should not be validated.
|
||||
*/
|
||||
@@ -55,16 +55,16 @@ describe('Conditional Configuration Validation', () => {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 3100,
|
||||
obfuscate: true
|
||||
obfuscate: true,
|
||||
},
|
||||
heartbeat: {
|
||||
enable: false,
|
||||
remoteURL: 'http://example.com',
|
||||
frequency: 'every 1 hour'
|
||||
frequency: 'every 1 hour',
|
||||
},
|
||||
dockerHealthCheck: {
|
||||
enable: false,
|
||||
port: 12398
|
||||
port: 12398,
|
||||
},
|
||||
uptimeMonitor: {
|
||||
enable: true,
|
||||
@@ -72,7 +72,7 @@ describe('Conditional Configuration Validation', () => {
|
||||
logLevel: 'verbose',
|
||||
storeInInfluxdb: {
|
||||
butlerSOSMemoryUsage: true,
|
||||
instanceTag: 'DEV'
|
||||
instanceTag: 'DEV',
|
||||
},
|
||||
storeNewRelic: {
|
||||
enable: false,
|
||||
@@ -80,45 +80,45 @@ describe('Conditional Configuration Validation', () => {
|
||||
metric: {
|
||||
dynamic: {
|
||||
butlerMemoryUsage: { enable: true },
|
||||
butlerUptime: { enable: true }
|
||||
}
|
||||
butlerUptime: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
thirdPartyToolsCredentials: {
|
||||
newRelic: []
|
||||
newRelic: [],
|
||||
},
|
||||
qlikSenseEvents: {
|
||||
influxdb: {
|
||||
enable: false,
|
||||
writeFrequency: 20000
|
||||
writeFrequency: 20000,
|
||||
},
|
||||
eventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'event_count',
|
||||
tags: []
|
||||
}
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
rejectedEventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'rejected_event_count'
|
||||
}
|
||||
}
|
||||
measurementName: 'rejected_event_count',
|
||||
},
|
||||
},
|
||||
},
|
||||
userEvents: {
|
||||
enable: false,
|
||||
excludeUser: [],
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portUserActivityEvents: 9997
|
||||
portUserActivityEvents: 9997,
|
||||
},
|
||||
tags: [],
|
||||
sendToMQTT: {
|
||||
@@ -128,20 +128,20 @@ describe('Conditional Configuration Validation', () => {
|
||||
sessionStartTopic: { enable: true, topic: 'test' },
|
||||
sessionStopTopic: { enable: true, topic: 'test' },
|
||||
connectionOpenTopic: { enable: true, topic: 'test' },
|
||||
connectionCloseTopic: { enable: true, topic: 'test' }
|
||||
}
|
||||
connectionCloseTopic: { enable: true, topic: 'test' },
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: true },
|
||||
sendToNewRelic: {
|
||||
enable: false,
|
||||
destinationAccount: [],
|
||||
scramble: true
|
||||
}
|
||||
scramble: true,
|
||||
},
|
||||
},
|
||||
logEvents: {
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portLogEvents: 9996
|
||||
portLogEvents: 9996,
|
||||
},
|
||||
tags: [],
|
||||
source: {
|
||||
@@ -149,44 +149,44 @@ describe('Conditional Configuration Validation', () => {
|
||||
proxy: { enable: false },
|
||||
repository: { enable: false },
|
||||
scheduler: { enable: false },
|
||||
qixPerf: { enable: true }
|
||||
qixPerf: { enable: true },
|
||||
},
|
||||
categorise: {
|
||||
enable: false,
|
||||
rules: []
|
||||
rules: [],
|
||||
},
|
||||
appNameLookup: {
|
||||
enable: true
|
||||
enable: true,
|
||||
},
|
||||
appAccess: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
enable: false,
|
||||
measurementName: 'app_access'
|
||||
measurementName: 'app_access',
|
||||
},
|
||||
rejectedEventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'rejected_app_access'
|
||||
}
|
||||
measurementName: 'rejected_app_access',
|
||||
},
|
||||
},
|
||||
allApps: {
|
||||
enable: false,
|
||||
appsInclude: [],
|
||||
appsExclude: []
|
||||
appsExclude: [],
|
||||
},
|
||||
someApps: {
|
||||
enable: false,
|
||||
appsInclude: []
|
||||
}
|
||||
appsInclude: [],
|
||||
},
|
||||
},
|
||||
sendToMQTT: {
|
||||
enable: false,
|
||||
baseTopic: 'qliksense/logevent',
|
||||
postTo: {
|
||||
baseTopic: true,
|
||||
subsystemTopics: true
|
||||
}
|
||||
subsystemTopics: true,
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: false },
|
||||
sendToNewRelic: {
|
||||
@@ -195,34 +195,34 @@ describe('Conditional Configuration Validation', () => {
|
||||
source: {
|
||||
engine: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
proxy: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
repository: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
scheduler: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cert: {
|
||||
clientCert: '/path/to/cert',
|
||||
clientCertKey: '/path/to/key',
|
||||
clientCertCA: '/path/to/ca',
|
||||
clientCertPassphrase: ''
|
||||
clientCertPassphrase: '',
|
||||
},
|
||||
mqttConfig: {
|
||||
enable: false,
|
||||
brokerHost: 'localhost',
|
||||
brokerPort: 1883,
|
||||
baseTopic: 'butler-sos/'
|
||||
baseTopic: 'butler-sos/',
|
||||
},
|
||||
newRelic: {
|
||||
enable: false,
|
||||
@@ -232,9 +232,9 @@ describe('Conditional Configuration Validation', () => {
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
metric: {
|
||||
destinationAccount: [],
|
||||
@@ -248,33 +248,33 @@ describe('Conditional Configuration Validation', () => {
|
||||
selections: { enable: true },
|
||||
sessions: { enable: true },
|
||||
users: { enable: true },
|
||||
saturated: { enable: true }
|
||||
saturated: { enable: true },
|
||||
},
|
||||
apps: {
|
||||
docCount: { enable: true },
|
||||
activeDocs: { enable: true },
|
||||
loadedDocs: { enable: true },
|
||||
inMemoryDocs: { enable: true }
|
||||
inMemoryDocs: { enable: true },
|
||||
},
|
||||
cache: {
|
||||
cache: { enable: true }
|
||||
cache: { enable: true },
|
||||
},
|
||||
proxy: {
|
||||
sessions: { enable: true }
|
||||
}
|
||||
sessions: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
prometheus: {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 9842
|
||||
port: 9842,
|
||||
},
|
||||
influxdbConfig: {
|
||||
enable: true,
|
||||
@@ -284,7 +284,7 @@ describe('Conditional Configuration Validation', () => {
|
||||
auth: {
|
||||
enable: false,
|
||||
username: '',
|
||||
password: ''
|
||||
password: '',
|
||||
},
|
||||
dbName: 'butler-sos',
|
||||
instanceTag: 'DEV',
|
||||
@@ -292,70 +292,70 @@ describe('Conditional Configuration Validation', () => {
|
||||
host: 'localhost',
|
||||
port: 8086,
|
||||
database: 'butler-sos',
|
||||
retentionPolicy: 'autogen'
|
||||
retentionPolicy: 'autogen',
|
||||
},
|
||||
v2Config: {
|
||||
url: 'http://localhost:8086',
|
||||
token: 'token',
|
||||
org: 'org',
|
||||
bucket: 'bucket'
|
||||
bucket: 'bucket',
|
||||
},
|
||||
writeSchedule: {
|
||||
frequency: 10000,
|
||||
tags: []
|
||||
}
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
appNames: {
|
||||
enableAppNameLookup: true,
|
||||
lookupFrequency: 30000,
|
||||
influxdb: {
|
||||
enable: true
|
||||
enable: true,
|
||||
},
|
||||
newRelic: {
|
||||
enable: true,
|
||||
destinationAccount: [],
|
||||
metric: {
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
userSessions: {
|
||||
enableSessionExtract: true,
|
||||
pollingFrequency: 30000,
|
||||
excludeUser: [],
|
||||
influxdb: {
|
||||
enable: true
|
||||
enable: true,
|
||||
},
|
||||
newRelic: {
|
||||
enable: false,
|
||||
destinationAccount: [],
|
||||
metric: {
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
serversToMonitor: {
|
||||
pollingFrequency: 30000,
|
||||
serverTagsDefinition: [],
|
||||
servers: []
|
||||
}
|
||||
}
|
||||
servers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test('should pass validation with minimal config when MQTT is disabled', async () => {
|
||||
@@ -370,16 +370,16 @@ describe('Conditional Configuration Validation', () => {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 3100,
|
||||
obfuscate: true
|
||||
obfuscate: true,
|
||||
},
|
||||
heartbeat: {
|
||||
enable: false,
|
||||
remoteURL: 'http://example.com',
|
||||
frequency: 'every 1 hour'
|
||||
frequency: 'every 1 hour',
|
||||
},
|
||||
dockerHealthCheck: {
|
||||
enable: false,
|
||||
port: 12398
|
||||
port: 12398,
|
||||
},
|
||||
uptimeMonitor: {
|
||||
enable: true,
|
||||
@@ -387,7 +387,7 @@ describe('Conditional Configuration Validation', () => {
|
||||
logLevel: 'verbose',
|
||||
storeInInfluxdb: {
|
||||
butlerSOSMemoryUsage: true,
|
||||
instanceTag: 'DEV'
|
||||
instanceTag: 'DEV',
|
||||
},
|
||||
storeNewRelic: {
|
||||
enable: false,
|
||||
@@ -395,45 +395,45 @@ describe('Conditional Configuration Validation', () => {
|
||||
metric: {
|
||||
dynamic: {
|
||||
butlerMemoryUsage: { enable: true },
|
||||
butlerUptime: { enable: true }
|
||||
}
|
||||
butlerUptime: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
thirdPartyToolsCredentials: {
|
||||
newRelic: []
|
||||
newRelic: [],
|
||||
},
|
||||
qlikSenseEvents: {
|
||||
influxdb: {
|
||||
enable: false,
|
||||
writeFrequency: 20000
|
||||
writeFrequency: 20000,
|
||||
},
|
||||
eventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'event_count',
|
||||
tags: []
|
||||
}
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
rejectedEventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'rejected_event_count'
|
||||
}
|
||||
}
|
||||
measurementName: 'rejected_event_count',
|
||||
},
|
||||
},
|
||||
},
|
||||
userEvents: {
|
||||
enable: false,
|
||||
excludeUser: [],
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portUserActivityEvents: 9997
|
||||
portUserActivityEvents: 9997,
|
||||
},
|
||||
tags: [],
|
||||
sendToMQTT: {
|
||||
@@ -443,20 +443,20 @@ describe('Conditional Configuration Validation', () => {
|
||||
sessionStartTopic: { enable: true, topic: 'test' },
|
||||
sessionStopTopic: { enable: true, topic: 'test' },
|
||||
connectionOpenTopic: { enable: true, topic: 'test' },
|
||||
connectionCloseTopic: { enable: true, topic: 'test' }
|
||||
}
|
||||
connectionCloseTopic: { enable: true, topic: 'test' },
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: true },
|
||||
sendToNewRelic: {
|
||||
enable: false,
|
||||
destinationAccount: [],
|
||||
scramble: true
|
||||
}
|
||||
scramble: true,
|
||||
},
|
||||
},
|
||||
logEvents: {
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portLogEvents: 9996
|
||||
portLogEvents: 9996,
|
||||
},
|
||||
tags: [],
|
||||
source: {
|
||||
@@ -464,23 +464,23 @@ describe('Conditional Configuration Validation', () => {
|
||||
proxy: { enable: false },
|
||||
repository: { enable: false },
|
||||
scheduler: { enable: false },
|
||||
qixPerf: { enable: true }
|
||||
qixPerf: { enable: true },
|
||||
},
|
||||
categorise: {
|
||||
enable: false,
|
||||
ruleDefault: { enable: false },
|
||||
rules: []
|
||||
rules: [],
|
||||
},
|
||||
appNameLookup: {
|
||||
enable: true
|
||||
enable: true,
|
||||
},
|
||||
sendToMQTT: {
|
||||
enable: false,
|
||||
baseTopic: 'qliksense/logevent',
|
||||
postTo: {
|
||||
baseTopic: true,
|
||||
subsystemTopics: true
|
||||
}
|
||||
subsystemTopics: true,
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: false },
|
||||
sendToNewRelic: {
|
||||
@@ -489,34 +489,34 @@ describe('Conditional Configuration Validation', () => {
|
||||
source: {
|
||||
engine: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
proxy: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
repository: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
scheduler: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cert: {
|
||||
clientCert: '/path/to/cert',
|
||||
clientCertKey: '/path/to/key',
|
||||
clientCertCA: '/path/to/ca',
|
||||
clientCertPassphrase: ''
|
||||
clientCertPassphrase: '',
|
||||
},
|
||||
mqttConfig: {
|
||||
enable: false,
|
||||
brokerHost: 'INVALID_HOST_FORMAT', // This should be ignored when disabled
|
||||
brokerPort: 'INVALID_PORT', // This should be ignored when disabled
|
||||
baseTopic: '' // This should be ignored when disabled
|
||||
brokerHost: 'INVALID_HOST_FORMAT', // This should be ignored when disabled
|
||||
brokerPort: 'INVALID_PORT', // This should be ignored when disabled
|
||||
baseTopic: '', // This should be ignored when disabled
|
||||
},
|
||||
newRelic: {
|
||||
enable: false,
|
||||
@@ -526,9 +526,9 @@ describe('Conditional Configuration Validation', () => {
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
metric: {
|
||||
destinationAccount: [],
|
||||
@@ -542,33 +542,33 @@ describe('Conditional Configuration Validation', () => {
|
||||
selections: { enable: true },
|
||||
sessions: { enable: true },
|
||||
users: { enable: true },
|
||||
saturated: { enable: true }
|
||||
saturated: { enable: true },
|
||||
},
|
||||
apps: {
|
||||
docCount: { enable: true },
|
||||
activeDocs: { enable: true },
|
||||
loadedDocs: { enable: true },
|
||||
inMemoryDocs: { enable: true }
|
||||
inMemoryDocs: { enable: true },
|
||||
},
|
||||
cache: {
|
||||
cache: { enable: true }
|
||||
cache: { enable: true },
|
||||
},
|
||||
proxy: {
|
||||
sessions: { enable: true }
|
||||
}
|
||||
sessions: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
prometheus: {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 9842
|
||||
port: 9842,
|
||||
},
|
||||
influxdbConfig: {
|
||||
enable: false,
|
||||
@@ -580,42 +580,42 @@ describe('Conditional Configuration Validation', () => {
|
||||
bucket: 'bucket',
|
||||
description: 'description',
|
||||
token: 'token',
|
||||
retentionDuration: '30d'
|
||||
retentionDuration: '30d',
|
||||
},
|
||||
v1Config: {
|
||||
auth: {
|
||||
enable: false,
|
||||
username: 'user',
|
||||
password: 'pass'
|
||||
password: 'pass',
|
||||
},
|
||||
dbName: 'butler-sos',
|
||||
retentionPolicy: {
|
||||
name: 'autogen',
|
||||
duration: '30d'
|
||||
}
|
||||
duration: '30d',
|
||||
},
|
||||
},
|
||||
includeFields: {
|
||||
activeDocs: true,
|
||||
loadedDocs: true,
|
||||
inMemoryDocs: true
|
||||
}
|
||||
inMemoryDocs: true,
|
||||
},
|
||||
},
|
||||
appNames: {
|
||||
enableAppNameLookup: true,
|
||||
lookupFrequency: 30000
|
||||
lookupFrequency: 30000,
|
||||
},
|
||||
userSessions: {
|
||||
enableSessionExtract: true,
|
||||
pollingInterval: 30000,
|
||||
excludeUser: []
|
||||
excludeUser: [],
|
||||
},
|
||||
serversToMonitor: {
|
||||
pollingInterval: 30000,
|
||||
rejectUnauthorized: true,
|
||||
serverTagsDefinition: [],
|
||||
servers: []
|
||||
}
|
||||
}
|
||||
servers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const configPath = await createTempConfig(minimalConfig);
|
||||
@@ -629,9 +629,9 @@ describe('Conditional Configuration Validation', () => {
|
||||
const config = JSON.parse(JSON.stringify(baseValidConfig));
|
||||
config['Butler-SOS'].mqttConfig = {
|
||||
enable: true,
|
||||
brokerHost: 'INVALID_HOST_FORMAT', // This should cause validation to fail
|
||||
brokerPort: 'INVALID_PORT', // This should cause validation to fail
|
||||
baseTopic: '' // This should cause validation to fail
|
||||
brokerHost: 'INVALID_HOST_FORMAT', // This should cause validation to fail
|
||||
brokerPort: 'INVALID_PORT', // This should cause validation to fail
|
||||
baseTopic: '', // This should cause validation to fail
|
||||
};
|
||||
|
||||
const configPath = await createTempConfig(config);
|
||||
@@ -646,10 +646,10 @@ describe('Conditional Configuration Validation', () => {
|
||||
config['Butler-SOS'].newRelic = {
|
||||
enable: false,
|
||||
event: {
|
||||
url: 'INVALID_URL', // This should be ignored when disabled
|
||||
header: 'INVALID_HEADER', // This should be ignored when disabled
|
||||
attribute: 'INVALID_ATTRIBUTE' // This should be ignored when disabled
|
||||
}
|
||||
url: 'INVALID_URL', // This should be ignored when disabled
|
||||
header: 'INVALID_HEADER', // This should be ignored when disabled
|
||||
attribute: 'INVALID_ATTRIBUTE', // This should be ignored when disabled
|
||||
},
|
||||
// Missing required 'metric' property - should be ignored when disabled
|
||||
};
|
||||
|
||||
@@ -673,4 +673,4 @@ describe('Conditional Configuration Validation', () => {
|
||||
const result = await verifyConfigFileSchema(configPath);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,6 +32,9 @@ describe('config-file-schema', () => {
|
||||
fileLogging: true,
|
||||
logDirectory: './log',
|
||||
anonTelemetry: false,
|
||||
systemInfo: {
|
||||
enable: true,
|
||||
},
|
||||
configVisualisation: {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
|
||||
@@ -48,28 +48,28 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
fileLogging: true,
|
||||
logDirectory: 'log',
|
||||
anonTelemetry: true,
|
||||
|
||||
|
||||
// Features with enable flags - all disabled with placeholder/invalid values
|
||||
configVisualisation: {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 3100,
|
||||
obfuscate: true
|
||||
obfuscate: true,
|
||||
},
|
||||
heartbeat: {
|
||||
enable: false,
|
||||
remoteURL: 'http://my.monitoring.server/some/path/', // Placeholder from template
|
||||
frequency: 'every 1 hour'
|
||||
frequency: 'every 1 hour',
|
||||
},
|
||||
dockerHealthCheck: {
|
||||
enable: false,
|
||||
port: 12398
|
||||
port: 12398,
|
||||
},
|
||||
mqttConfig: {
|
||||
enable: false,
|
||||
brokerHost: '<IP of MQTT broker/server>', // Placeholder - invalid hostname
|
||||
brokerHost: '<IP of MQTT broker/server>', // Placeholder - invalid hostname
|
||||
brokerPort: 1883,
|
||||
baseTopic: 'butler-sos/'
|
||||
baseTopic: 'butler-sos/',
|
||||
},
|
||||
newRelic: {
|
||||
enable: false,
|
||||
@@ -79,9 +79,9 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
metric: {
|
||||
destinationAccount: [],
|
||||
@@ -95,33 +95,33 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
selections: { enable: true },
|
||||
sessions: { enable: true },
|
||||
users: { enable: true },
|
||||
saturated: { enable: true }
|
||||
saturated: { enable: true },
|
||||
},
|
||||
apps: {
|
||||
docCount: { enable: true },
|
||||
activeDocs: { enable: true },
|
||||
loadedDocs: { enable: true },
|
||||
inMemoryDocs: { enable: true }
|
||||
inMemoryDocs: { enable: true },
|
||||
},
|
||||
cache: {
|
||||
cache: { enable: true }
|
||||
cache: { enable: true },
|
||||
},
|
||||
proxy: {
|
||||
sessions: { enable: true }
|
||||
}
|
||||
sessions: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
prometheus: {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 9842
|
||||
port: 9842,
|
||||
},
|
||||
influxdbConfig: {
|
||||
enable: false,
|
||||
@@ -133,27 +133,27 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
bucket: 'bucket',
|
||||
description: 'description',
|
||||
token: 'token',
|
||||
retentionDuration: '30d'
|
||||
retentionDuration: '30d',
|
||||
},
|
||||
v1Config: {
|
||||
auth: {
|
||||
enable: false,
|
||||
username: 'user',
|
||||
password: 'pass'
|
||||
password: 'pass',
|
||||
},
|
||||
dbName: 'butler-sos',
|
||||
retentionPolicy: {
|
||||
name: 'autogen',
|
||||
duration: '30d'
|
||||
}
|
||||
duration: '30d',
|
||||
},
|
||||
},
|
||||
includeFields: {
|
||||
activeDocs: true,
|
||||
loadedDocs: true,
|
||||
inMemoryDocs: true
|
||||
}
|
||||
inMemoryDocs: true,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// Required sections with minimal valid config
|
||||
uptimeMonitor: {
|
||||
enable: true,
|
||||
@@ -161,7 +161,7 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
logLevel: 'verbose',
|
||||
storeInInfluxdb: {
|
||||
butlerSOSMemoryUsage: true,
|
||||
instanceTag: 'DEV'
|
||||
instanceTag: 'DEV',
|
||||
},
|
||||
storeNewRelic: {
|
||||
enable: false,
|
||||
@@ -169,45 +169,45 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
metric: {
|
||||
dynamic: {
|
||||
butlerMemoryUsage: { enable: true },
|
||||
butlerUptime: { enable: true }
|
||||
}
|
||||
butlerUptime: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
thirdPartyToolsCredentials: {
|
||||
newRelic: []
|
||||
newRelic: [],
|
||||
},
|
||||
qlikSenseEvents: {
|
||||
influxdb: {
|
||||
enable: false,
|
||||
writeFrequency: 20000
|
||||
writeFrequency: 20000,
|
||||
},
|
||||
eventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'event_count',
|
||||
tags: []
|
||||
}
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
rejectedEventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'rejected_event_count'
|
||||
}
|
||||
}
|
||||
measurementName: 'rejected_event_count',
|
||||
},
|
||||
},
|
||||
},
|
||||
userEvents: {
|
||||
enable: false,
|
||||
excludeUser: [],
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portUserActivityEvents: 9997
|
||||
portUserActivityEvents: 9997,
|
||||
},
|
||||
tags: [],
|
||||
sendToMQTT: {
|
||||
@@ -217,20 +217,20 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
sessionStartTopic: { enable: true, topic: 'test' },
|
||||
sessionStopTopic: { enable: true, topic: 'test' },
|
||||
connectionOpenTopic: { enable: true, topic: 'test' },
|
||||
connectionCloseTopic: { enable: true, topic: 'test' }
|
||||
}
|
||||
connectionCloseTopic: { enable: true, topic: 'test' },
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: true },
|
||||
sendToNewRelic: {
|
||||
enable: false,
|
||||
destinationAccount: [],
|
||||
scramble: true
|
||||
}
|
||||
scramble: true,
|
||||
},
|
||||
},
|
||||
logEvents: {
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portLogEvents: 9996
|
||||
portLogEvents: 9996,
|
||||
},
|
||||
tags: [],
|
||||
source: {
|
||||
@@ -238,26 +238,26 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
proxy: { enable: false },
|
||||
repository: { enable: false },
|
||||
scheduler: { enable: false },
|
||||
qixPerf: { enable: true }
|
||||
qixPerf: { enable: true },
|
||||
},
|
||||
categorise: {
|
||||
enable: false,
|
||||
ruleDefault: {
|
||||
ruleDefault: {
|
||||
enable: false,
|
||||
category: []
|
||||
category: [],
|
||||
},
|
||||
rules: []
|
||||
rules: [],
|
||||
},
|
||||
appNameLookup: {
|
||||
enable: true
|
||||
enable: true,
|
||||
},
|
||||
sendToMQTT: {
|
||||
enable: false,
|
||||
baseTopic: 'qliksense/logevent',
|
||||
postTo: {
|
||||
baseTopic: true,
|
||||
subsystemTopics: true
|
||||
}
|
||||
subsystemTopics: true,
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: false },
|
||||
sendToNewRelic: {
|
||||
@@ -266,53 +266,53 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
source: {
|
||||
engine: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
proxy: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
repository: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
scheduler: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cert: {
|
||||
clientCert: '/path/to/cert',
|
||||
clientCertKey: '/path/to/key',
|
||||
clientCertCA: '/path/to/ca',
|
||||
clientCertPassphrase: ''
|
||||
clientCertPassphrase: '',
|
||||
},
|
||||
appNames: {
|
||||
enableAppNameExtract: true,
|
||||
extractInterval: 30000,
|
||||
hostIP: 'localhost',
|
||||
enableAppNameLookup: true,
|
||||
lookupFrequency: 30000
|
||||
lookupFrequency: 30000,
|
||||
},
|
||||
userSessions: {
|
||||
enableSessionExtract: true,
|
||||
pollingInterval: 30000,
|
||||
excludeUser: []
|
||||
excludeUser: [],
|
||||
},
|
||||
serversToMonitor: {
|
||||
pollingInterval: 30000,
|
||||
rejectUnauthorized: true,
|
||||
serverTagsDefinition: [],
|
||||
servers: []
|
||||
}
|
||||
}
|
||||
servers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const configPath = await createTempConfig(config);
|
||||
const result = await verifyConfigFileSchema(configPath);
|
||||
|
||||
|
||||
// Before the fix, this would fail because MQTT validation would reject '<IP of MQTT broker/server>'
|
||||
// After the fix, this should pass because MQTT is disabled
|
||||
expect(result).toBe(true);
|
||||
@@ -329,22 +329,22 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 3100,
|
||||
obfuscate: true
|
||||
obfuscate: true,
|
||||
},
|
||||
heartbeat: {
|
||||
enable: false,
|
||||
remoteURL: 'http://example.com',
|
||||
frequency: 'every 1 hour'
|
||||
frequency: 'every 1 hour',
|
||||
},
|
||||
dockerHealthCheck: {
|
||||
enable: false,
|
||||
port: 12398
|
||||
port: 12398,
|
||||
},
|
||||
mqttConfig: {
|
||||
enable: true, // Enabled with invalid config
|
||||
brokerHost: '<INVALID>', // Invalid hostname format
|
||||
enable: true, // Enabled with invalid config
|
||||
brokerHost: '<INVALID>', // Invalid hostname format
|
||||
brokerPort: 1883,
|
||||
baseTopic: 'butler-sos/'
|
||||
baseTopic: 'butler-sos/',
|
||||
},
|
||||
newRelic: {
|
||||
enable: false,
|
||||
@@ -354,9 +354,9 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
metric: {
|
||||
destinationAccount: [],
|
||||
@@ -370,33 +370,33 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
selections: { enable: true },
|
||||
sessions: { enable: true },
|
||||
users: { enable: true },
|
||||
saturated: { enable: true }
|
||||
saturated: { enable: true },
|
||||
},
|
||||
apps: {
|
||||
docCount: { enable: true },
|
||||
activeDocs: { enable: true },
|
||||
loadedDocs: { enable: true },
|
||||
inMemoryDocs: { enable: true }
|
||||
inMemoryDocs: { enable: true },
|
||||
},
|
||||
cache: {
|
||||
cache: { enable: true }
|
||||
cache: { enable: true },
|
||||
},
|
||||
proxy: {
|
||||
sessions: { enable: true }
|
||||
}
|
||||
sessions: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
prometheus: {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 9842
|
||||
port: 9842,
|
||||
},
|
||||
influxdbConfig: {
|
||||
enable: false,
|
||||
@@ -408,25 +408,25 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
bucket: 'bucket',
|
||||
description: 'description',
|
||||
token: 'token',
|
||||
retentionDuration: '30d'
|
||||
retentionDuration: '30d',
|
||||
},
|
||||
v1Config: {
|
||||
auth: {
|
||||
enable: false,
|
||||
username: 'user',
|
||||
password: 'pass'
|
||||
password: 'pass',
|
||||
},
|
||||
dbName: 'butler-sos',
|
||||
retentionPolicy: {
|
||||
name: 'autogen',
|
||||
duration: '30d'
|
||||
}
|
||||
duration: '30d',
|
||||
},
|
||||
},
|
||||
includeFields: {
|
||||
activeDocs: true,
|
||||
loadedDocs: true,
|
||||
inMemoryDocs: true
|
||||
}
|
||||
inMemoryDocs: true,
|
||||
},
|
||||
},
|
||||
uptimeMonitor: {
|
||||
enable: true,
|
||||
@@ -434,7 +434,7 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
logLevel: 'verbose',
|
||||
storeInInfluxdb: {
|
||||
butlerSOSMemoryUsage: true,
|
||||
instanceTag: 'DEV'
|
||||
instanceTag: 'DEV',
|
||||
},
|
||||
storeNewRelic: {
|
||||
enable: false,
|
||||
@@ -442,45 +442,45 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
metric: {
|
||||
dynamic: {
|
||||
butlerMemoryUsage: { enable: true },
|
||||
butlerUptime: { enable: true }
|
||||
}
|
||||
butlerUptime: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
thirdPartyToolsCredentials: {
|
||||
newRelic: []
|
||||
newRelic: [],
|
||||
},
|
||||
qlikSenseEvents: {
|
||||
influxdb: {
|
||||
enable: false,
|
||||
writeFrequency: 20000
|
||||
writeFrequency: 20000,
|
||||
},
|
||||
eventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'event_count',
|
||||
tags: []
|
||||
}
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
rejectedEventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'rejected_event_count'
|
||||
}
|
||||
}
|
||||
measurementName: 'rejected_event_count',
|
||||
},
|
||||
},
|
||||
},
|
||||
userEvents: {
|
||||
enable: false,
|
||||
excludeUser: [],
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portUserActivityEvents: 9997
|
||||
portUserActivityEvents: 9997,
|
||||
},
|
||||
tags: [],
|
||||
sendToMQTT: {
|
||||
@@ -490,20 +490,20 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
sessionStartTopic: { enable: true, topic: 'test' },
|
||||
sessionStopTopic: { enable: true, topic: 'test' },
|
||||
connectionOpenTopic: { enable: true, topic: 'test' },
|
||||
connectionCloseTopic: { enable: true, topic: 'test' }
|
||||
}
|
||||
connectionCloseTopic: { enable: true, topic: 'test' },
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: true },
|
||||
sendToNewRelic: {
|
||||
enable: false,
|
||||
destinationAccount: [],
|
||||
scramble: true
|
||||
}
|
||||
scramble: true,
|
||||
},
|
||||
},
|
||||
logEvents: {
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portLogEvents: 9996
|
||||
portLogEvents: 9996,
|
||||
},
|
||||
tags: [],
|
||||
source: {
|
||||
@@ -511,26 +511,26 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
proxy: { enable: false },
|
||||
repository: { enable: false },
|
||||
scheduler: { enable: false },
|
||||
qixPerf: { enable: true }
|
||||
qixPerf: { enable: true },
|
||||
},
|
||||
categorise: {
|
||||
enable: false,
|
||||
ruleDefault: {
|
||||
ruleDefault: {
|
||||
enable: false,
|
||||
category: []
|
||||
category: [],
|
||||
},
|
||||
rules: []
|
||||
rules: [],
|
||||
},
|
||||
appNameLookup: {
|
||||
enable: true
|
||||
enable: true,
|
||||
},
|
||||
sendToMQTT: {
|
||||
enable: false,
|
||||
baseTopic: 'qliksense/logevent',
|
||||
postTo: {
|
||||
baseTopic: true,
|
||||
subsystemTopics: true
|
||||
}
|
||||
subsystemTopics: true,
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: false },
|
||||
sendToNewRelic: {
|
||||
@@ -539,54 +539,54 @@ describe('Issue #1036: Conditional Config Validation', () => {
|
||||
source: {
|
||||
engine: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
proxy: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
repository: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
scheduler: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cert: {
|
||||
clientCert: '/path/to/cert',
|
||||
clientCertKey: '/path/to/key',
|
||||
clientCertCA: '/path/to/ca',
|
||||
clientCertPassphrase: ''
|
||||
clientCertPassphrase: '',
|
||||
},
|
||||
appNames: {
|
||||
enableAppNameExtract: true,
|
||||
extractInterval: 30000,
|
||||
hostIP: 'localhost',
|
||||
enableAppNameLookup: true,
|
||||
lookupFrequency: 30000
|
||||
lookupFrequency: 30000,
|
||||
},
|
||||
userSessions: {
|
||||
enableSessionExtract: true,
|
||||
pollingInterval: 30000,
|
||||
excludeUser: []
|
||||
excludeUser: [],
|
||||
},
|
||||
serversToMonitor: {
|
||||
pollingInterval: 30000,
|
||||
rejectUnauthorized: true,
|
||||
serverTagsDefinition: [],
|
||||
servers: []
|
||||
}
|
||||
}
|
||||
servers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const configPath = await createTempConfig(config);
|
||||
const result = await verifyConfigFileSchema(configPath);
|
||||
|
||||
|
||||
// This should fail because MQTT is enabled with invalid hostname
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,16 +46,16 @@ describe('MQTT Conditional Validation', () => {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 3100,
|
||||
obfuscate: true
|
||||
obfuscate: true,
|
||||
},
|
||||
heartbeat: {
|
||||
enable: false,
|
||||
remoteURL: 'http://example.com',
|
||||
frequency: 'every 1 hour'
|
||||
frequency: 'every 1 hour',
|
||||
},
|
||||
dockerHealthCheck: {
|
||||
enable: false,
|
||||
port: 12398
|
||||
port: 12398,
|
||||
},
|
||||
uptimeMonitor: {
|
||||
enable: true,
|
||||
@@ -63,7 +63,7 @@ describe('MQTT Conditional Validation', () => {
|
||||
logLevel: 'verbose',
|
||||
storeInInfluxdb: {
|
||||
butlerSOSMemoryUsage: true,
|
||||
instanceTag: 'DEV'
|
||||
instanceTag: 'DEV',
|
||||
},
|
||||
storeNewRelic: {
|
||||
enable: false,
|
||||
@@ -71,45 +71,45 @@ describe('MQTT Conditional Validation', () => {
|
||||
metric: {
|
||||
dynamic: {
|
||||
butlerMemoryUsage: { enable: true },
|
||||
butlerUptime: { enable: true }
|
||||
}
|
||||
butlerUptime: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
thirdPartyToolsCredentials: {
|
||||
newRelic: []
|
||||
newRelic: [],
|
||||
},
|
||||
qlikSenseEvents: {
|
||||
influxdb: {
|
||||
enable: false,
|
||||
writeFrequency: 20000
|
||||
writeFrequency: 20000,
|
||||
},
|
||||
eventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'event_count',
|
||||
tags: []
|
||||
}
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
rejectedEventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'rejected_event_count'
|
||||
}
|
||||
}
|
||||
measurementName: 'rejected_event_count',
|
||||
},
|
||||
},
|
||||
},
|
||||
userEvents: {
|
||||
enable: false,
|
||||
excludeUser: [],
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portUserActivityEvents: 9997
|
||||
portUserActivityEvents: 9997,
|
||||
},
|
||||
tags: [],
|
||||
sendToMQTT: {
|
||||
@@ -119,20 +119,20 @@ describe('MQTT Conditional Validation', () => {
|
||||
sessionStartTopic: { enable: true, topic: 'test' },
|
||||
sessionStopTopic: { enable: true, topic: 'test' },
|
||||
connectionOpenTopic: { enable: true, topic: 'test' },
|
||||
connectionCloseTopic: { enable: true, topic: 'test' }
|
||||
}
|
||||
connectionCloseTopic: { enable: true, topic: 'test' },
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: true },
|
||||
sendToNewRelic: {
|
||||
enable: false,
|
||||
destinationAccount: [],
|
||||
scramble: true
|
||||
}
|
||||
scramble: true,
|
||||
},
|
||||
},
|
||||
logEvents: {
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portLogEvents: 9996
|
||||
portLogEvents: 9996,
|
||||
},
|
||||
tags: [],
|
||||
source: {
|
||||
@@ -140,26 +140,26 @@ describe('MQTT Conditional Validation', () => {
|
||||
proxy: { enable: false },
|
||||
repository: { enable: false },
|
||||
scheduler: { enable: false },
|
||||
qixPerf: { enable: true }
|
||||
qixPerf: { enable: true },
|
||||
},
|
||||
categorise: {
|
||||
enable: false,
|
||||
ruleDefault: {
|
||||
ruleDefault: {
|
||||
enable: false,
|
||||
category: []
|
||||
category: [],
|
||||
},
|
||||
rules: []
|
||||
rules: [],
|
||||
},
|
||||
appNameLookup: {
|
||||
enable: true
|
||||
enable: true,
|
||||
},
|
||||
sendToMQTT: {
|
||||
enable: false,
|
||||
baseTopic: 'qliksense/logevent',
|
||||
postTo: {
|
||||
baseTopic: true,
|
||||
subsystemTopics: true
|
||||
}
|
||||
subsystemTopics: true,
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: false },
|
||||
sendToNewRelic: {
|
||||
@@ -168,34 +168,34 @@ describe('MQTT Conditional Validation', () => {
|
||||
source: {
|
||||
engine: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
proxy: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
repository: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
scheduler: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cert: {
|
||||
clientCert: '/path/to/cert',
|
||||
clientCertKey: '/path/to/key',
|
||||
clientCertCA: '/path/to/ca',
|
||||
clientCertPassphrase: ''
|
||||
clientCertPassphrase: '',
|
||||
},
|
||||
mqttConfig: {
|
||||
enable: false,
|
||||
brokerHost: 'INVALID_HOST_FORMAT', // This should be allowed when disabled
|
||||
brokerPort: 'INVALID_PORT', // This should be allowed when disabled
|
||||
baseTopic: '' // This should be allowed when disabled
|
||||
brokerHost: 'INVALID_HOST_FORMAT', // This should be allowed when disabled
|
||||
brokerPort: 'INVALID_PORT', // This should be allowed when disabled
|
||||
baseTopic: '', // This should be allowed when disabled
|
||||
},
|
||||
newRelic: {
|
||||
enable: false,
|
||||
@@ -205,9 +205,9 @@ describe('MQTT Conditional Validation', () => {
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
metric: {
|
||||
destinationAccount: [],
|
||||
@@ -221,33 +221,33 @@ describe('MQTT Conditional Validation', () => {
|
||||
selections: { enable: true },
|
||||
sessions: { enable: true },
|
||||
users: { enable: true },
|
||||
saturated: { enable: true }
|
||||
saturated: { enable: true },
|
||||
},
|
||||
apps: {
|
||||
docCount: { enable: true },
|
||||
activeDocs: { enable: true },
|
||||
loadedDocs: { enable: true },
|
||||
inMemoryDocs: { enable: true }
|
||||
inMemoryDocs: { enable: true },
|
||||
},
|
||||
cache: {
|
||||
cache: { enable: true }
|
||||
cache: { enable: true },
|
||||
},
|
||||
proxy: {
|
||||
sessions: { enable: true }
|
||||
}
|
||||
sessions: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
prometheus: {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 9842
|
||||
port: 9842,
|
||||
},
|
||||
influxdbConfig: {
|
||||
enable: false,
|
||||
@@ -259,50 +259,50 @@ describe('MQTT Conditional Validation', () => {
|
||||
bucket: 'bucket',
|
||||
description: 'description',
|
||||
token: 'token',
|
||||
retentionDuration: '30d'
|
||||
retentionDuration: '30d',
|
||||
},
|
||||
v1Config: {
|
||||
auth: {
|
||||
enable: false,
|
||||
username: 'user',
|
||||
password: 'pass'
|
||||
password: 'pass',
|
||||
},
|
||||
dbName: 'butler-sos',
|
||||
retentionPolicy: {
|
||||
name: 'autogen',
|
||||
duration: '30d'
|
||||
}
|
||||
duration: '30d',
|
||||
},
|
||||
},
|
||||
includeFields: {
|
||||
activeDocs: true,
|
||||
loadedDocs: true,
|
||||
inMemoryDocs: true
|
||||
}
|
||||
inMemoryDocs: true,
|
||||
},
|
||||
},
|
||||
appNames: {
|
||||
enableAppNameExtract: true,
|
||||
extractInterval: 30000,
|
||||
hostIP: 'localhost',
|
||||
enableAppNameLookup: true,
|
||||
lookupFrequency: 30000
|
||||
lookupFrequency: 30000,
|
||||
},
|
||||
userSessions: {
|
||||
enableSessionExtract: true,
|
||||
pollingInterval: 30000,
|
||||
excludeUser: []
|
||||
excludeUser: [],
|
||||
},
|
||||
serversToMonitor: {
|
||||
pollingInterval: 30000,
|
||||
rejectUnauthorized: true,
|
||||
serverTagsDefinition: [],
|
||||
servers: []
|
||||
}
|
||||
}
|
||||
servers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const configPath = await createTempConfig(config);
|
||||
const result = await verifyConfigFileSchema(configPath);
|
||||
|
||||
|
||||
// This should pass because MQTT is disabled, so invalid values should be ignored
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
@@ -318,16 +318,16 @@ describe('MQTT Conditional Validation', () => {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 3100,
|
||||
obfuscate: true
|
||||
obfuscate: true,
|
||||
},
|
||||
heartbeat: {
|
||||
enable: false,
|
||||
remoteURL: 'http://example.com',
|
||||
frequency: 'every 1 hour'
|
||||
frequency: 'every 1 hour',
|
||||
},
|
||||
dockerHealthCheck: {
|
||||
enable: false,
|
||||
port: 12398
|
||||
port: 12398,
|
||||
},
|
||||
uptimeMonitor: {
|
||||
enable: true,
|
||||
@@ -335,7 +335,7 @@ describe('MQTT Conditional Validation', () => {
|
||||
logLevel: 'verbose',
|
||||
storeInInfluxdb: {
|
||||
butlerSOSMemoryUsage: true,
|
||||
instanceTag: 'DEV'
|
||||
instanceTag: 'DEV',
|
||||
},
|
||||
storeNewRelic: {
|
||||
enable: false,
|
||||
@@ -343,45 +343,45 @@ describe('MQTT Conditional Validation', () => {
|
||||
metric: {
|
||||
dynamic: {
|
||||
butlerMemoryUsage: { enable: true },
|
||||
butlerUptime: { enable: true }
|
||||
}
|
||||
butlerUptime: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
thirdPartyToolsCredentials: {
|
||||
newRelic: []
|
||||
newRelic: [],
|
||||
},
|
||||
qlikSenseEvents: {
|
||||
influxdb: {
|
||||
enable: false,
|
||||
writeFrequency: 20000
|
||||
writeFrequency: 20000,
|
||||
},
|
||||
eventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'event_count',
|
||||
tags: []
|
||||
}
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
rejectedEventCount: {
|
||||
enable: false,
|
||||
influxdb: {
|
||||
measurementName: 'rejected_event_count'
|
||||
}
|
||||
}
|
||||
measurementName: 'rejected_event_count',
|
||||
},
|
||||
},
|
||||
},
|
||||
userEvents: {
|
||||
enable: false,
|
||||
excludeUser: [],
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portUserActivityEvents: 9997
|
||||
portUserActivityEvents: 9997,
|
||||
},
|
||||
tags: [],
|
||||
sendToMQTT: {
|
||||
@@ -391,20 +391,20 @@ describe('MQTT Conditional Validation', () => {
|
||||
sessionStartTopic: { enable: true, topic: 'test' },
|
||||
sessionStopTopic: { enable: true, topic: 'test' },
|
||||
connectionOpenTopic: { enable: true, topic: 'test' },
|
||||
connectionCloseTopic: { enable: true, topic: 'test' }
|
||||
}
|
||||
connectionCloseTopic: { enable: true, topic: 'test' },
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: true },
|
||||
sendToNewRelic: {
|
||||
enable: false,
|
||||
destinationAccount: [],
|
||||
scramble: true
|
||||
}
|
||||
scramble: true,
|
||||
},
|
||||
},
|
||||
logEvents: {
|
||||
udpServerConfig: {
|
||||
serverHost: 'localhost',
|
||||
portLogEvents: 9996
|
||||
portLogEvents: 9996,
|
||||
},
|
||||
tags: [],
|
||||
source: {
|
||||
@@ -412,26 +412,26 @@ describe('MQTT Conditional Validation', () => {
|
||||
proxy: { enable: false },
|
||||
repository: { enable: false },
|
||||
scheduler: { enable: false },
|
||||
qixPerf: { enable: true }
|
||||
qixPerf: { enable: true },
|
||||
},
|
||||
categorise: {
|
||||
enable: false,
|
||||
ruleDefault: {
|
||||
ruleDefault: {
|
||||
enable: false,
|
||||
category: []
|
||||
category: [],
|
||||
},
|
||||
rules: []
|
||||
rules: [],
|
||||
},
|
||||
appNameLookup: {
|
||||
enable: true
|
||||
enable: true,
|
||||
},
|
||||
sendToMQTT: {
|
||||
enable: false,
|
||||
baseTopic: 'qliksense/logevent',
|
||||
postTo: {
|
||||
baseTopic: true,
|
||||
subsystemTopics: true
|
||||
}
|
||||
subsystemTopics: true,
|
||||
},
|
||||
},
|
||||
sendToInfluxdb: { enable: false },
|
||||
sendToNewRelic: {
|
||||
@@ -440,34 +440,34 @@ describe('MQTT Conditional Validation', () => {
|
||||
source: {
|
||||
engine: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
proxy: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
repository: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
scheduler: {
|
||||
enable: true,
|
||||
logLevel: { error: true, warn: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
logLevel: { error: true, warn: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cert: {
|
||||
clientCert: '/path/to/cert',
|
||||
clientCertKey: '/path/to/key',
|
||||
clientCertCA: '/path/to/ca',
|
||||
clientCertPassphrase: ''
|
||||
clientCertPassphrase: '',
|
||||
},
|
||||
mqttConfig: {
|
||||
enable: true, // MQTT is enabled
|
||||
brokerHost: 'INVALID_HOST_FORMAT', // This should cause validation to fail
|
||||
brokerPort: 'INVALID_PORT', // This should cause validation to fail
|
||||
baseTopic: '' // This should cause validation to fail
|
||||
enable: true, // MQTT is enabled
|
||||
brokerHost: 'INVALID_HOST_FORMAT', // This should cause validation to fail
|
||||
brokerPort: 'INVALID_PORT', // This should cause validation to fail
|
||||
baseTopic: '', // This should cause validation to fail
|
||||
},
|
||||
newRelic: {
|
||||
enable: false,
|
||||
@@ -477,9 +477,9 @@ describe('MQTT Conditional Validation', () => {
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
metric: {
|
||||
destinationAccount: [],
|
||||
@@ -493,33 +493,33 @@ describe('MQTT Conditional Validation', () => {
|
||||
selections: { enable: true },
|
||||
sessions: { enable: true },
|
||||
users: { enable: true },
|
||||
saturated: { enable: true }
|
||||
saturated: { enable: true },
|
||||
},
|
||||
apps: {
|
||||
docCount: { enable: true },
|
||||
activeDocs: { enable: true },
|
||||
loadedDocs: { enable: true },
|
||||
inMemoryDocs: { enable: true }
|
||||
inMemoryDocs: { enable: true },
|
||||
},
|
||||
cache: {
|
||||
cache: { enable: true }
|
||||
cache: { enable: true },
|
||||
},
|
||||
proxy: {
|
||||
sessions: { enable: true }
|
||||
}
|
||||
sessions: { enable: true },
|
||||
},
|
||||
},
|
||||
attribute: {
|
||||
static: [],
|
||||
dynamic: {
|
||||
butlerSosVersion: { enable: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
butlerSosVersion: { enable: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
prometheus: {
|
||||
enable: false,
|
||||
host: 'localhost',
|
||||
port: 9842
|
||||
port: 9842,
|
||||
},
|
||||
influxdbConfig: {
|
||||
enable: false,
|
||||
@@ -531,51 +531,51 @@ describe('MQTT Conditional Validation', () => {
|
||||
bucket: 'bucket',
|
||||
description: 'description',
|
||||
token: 'token',
|
||||
retentionDuration: '30d'
|
||||
retentionDuration: '30d',
|
||||
},
|
||||
v1Config: {
|
||||
auth: {
|
||||
enable: false,
|
||||
username: 'user',
|
||||
password: 'pass'
|
||||
password: 'pass',
|
||||
},
|
||||
dbName: 'butler-sos',
|
||||
retentionPolicy: {
|
||||
name: 'autogen',
|
||||
duration: '30d'
|
||||
}
|
||||
duration: '30d',
|
||||
},
|
||||
},
|
||||
includeFields: {
|
||||
activeDocs: true,
|
||||
loadedDocs: true,
|
||||
inMemoryDocs: true
|
||||
}
|
||||
inMemoryDocs: true,
|
||||
},
|
||||
},
|
||||
appNames: {
|
||||
enableAppNameExtract: true,
|
||||
extractInterval: 30000,
|
||||
hostIP: 'localhost',
|
||||
enableAppNameLookup: true,
|
||||
lookupFrequency: 30000
|
||||
lookupFrequency: 30000,
|
||||
},
|
||||
userSessions: {
|
||||
enableSessionExtract: true,
|
||||
pollingInterval: 30000,
|
||||
excludeUser: []
|
||||
excludeUser: [],
|
||||
},
|
||||
serversToMonitor: {
|
||||
pollingInterval: 30000,
|
||||
rejectUnauthorized: true,
|
||||
serverTagsDefinition: [],
|
||||
servers: []
|
||||
}
|
||||
}
|
||||
servers: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const configPath = await createTempConfig(config);
|
||||
const result = await verifyConfigFileSchema(configPath);
|
||||
|
||||
|
||||
// This should fail because MQTT is enabled with invalid configuration
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -44,9 +44,9 @@ describe('Realistic Config Conditional Validation', () => {
|
||||
// Modify the MQTT config to have disabled feature with invalid placeholders
|
||||
templateConfig['Butler-SOS'].mqttConfig = {
|
||||
enable: false,
|
||||
brokerHost: '<IP of MQTT broker/server>', // Placeholder from template - should be allowed when disabled
|
||||
brokerHost: '<IP of MQTT broker/server>', // Placeholder from template - should be allowed when disabled
|
||||
brokerPort: 1883,
|
||||
baseTopic: 'butler-sos/'
|
||||
baseTopic: 'butler-sos/',
|
||||
};
|
||||
|
||||
// Also disable other features that might have validation issues in the template
|
||||
@@ -58,7 +58,7 @@ describe('Realistic Config Conditional Validation', () => {
|
||||
templateConfig['Butler-SOS'].dockerHealthCheck.enable = false;
|
||||
|
||||
const configPath = await createTempConfig(templateConfig);
|
||||
|
||||
|
||||
// This should pass validation because features are disabled
|
||||
const result = await verifyConfigFileSchema(configPath);
|
||||
expect(result).toBe(true);
|
||||
@@ -72,16 +72,16 @@ describe('Realistic Config Conditional Validation', () => {
|
||||
|
||||
// Enable MQTT but leave placeholder values that should be invalid
|
||||
templateConfig['Butler-SOS'].mqttConfig = {
|
||||
enable: true, // Enable MQTT
|
||||
brokerHost: '<IP of MQTT broker/server>', // Invalid placeholder - should cause validation to fail
|
||||
enable: true, // Enable MQTT
|
||||
brokerHost: '<IP of MQTT broker/server>', // Invalid placeholder - should cause validation to fail
|
||||
brokerPort: 1883,
|
||||
baseTopic: 'butler-sos/'
|
||||
baseTopic: 'butler-sos/',
|
||||
};
|
||||
|
||||
const configPath = await createTempConfig(templateConfig);
|
||||
|
||||
|
||||
// This should fail validation because MQTT is enabled with invalid config
|
||||
const result = await verifyConfigFileSchema(configPath);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
117
src/lib/__tests__/system-info.test.js
Normal file
117
src/lib/__tests__/system-info.test.js
Normal file
@@ -0,0 +1,117 @@
|
||||
import { jest } from '@jest/globals';
|
||||
import { basicSettingsSchema } from '../config-schemas/basic-settings.js';
|
||||
import Ajv from 'ajv';
|
||||
import addFormats from 'ajv-formats';
|
||||
import addKeywords from 'ajv-keywords';
|
||||
|
||||
describe('System Information Configuration Schema', () => {
|
||||
let ajv;
|
||||
|
||||
beforeAll(() => {
|
||||
ajv = new Ajv({ strict: false });
|
||||
addFormats(ajv);
|
||||
addKeywords(ajv);
|
||||
});
|
||||
|
||||
test('should validate configuration with systemInfo.enable set to false', () => {
|
||||
// Test just the systemInfo part of the schema for simplicity
|
||||
const systemInfoSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
systemInfo: basicSettingsSchema.systemInfo,
|
||||
},
|
||||
required: ['systemInfo'],
|
||||
};
|
||||
|
||||
const config = {
|
||||
systemInfo: {
|
||||
enable: false,
|
||||
},
|
||||
};
|
||||
|
||||
const validate = ajv.compile(systemInfoSchema);
|
||||
const isValid = validate(config);
|
||||
|
||||
if (!isValid) {
|
||||
console.log('Validation errors:', validate.errors);
|
||||
}
|
||||
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
test('should validate configuration with systemInfo.enable set to true', () => {
|
||||
// Test just the systemInfo part of the schema for simplicity
|
||||
const systemInfoSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
systemInfo: basicSettingsSchema.systemInfo,
|
||||
},
|
||||
required: ['systemInfo'],
|
||||
};
|
||||
|
||||
const config = {
|
||||
systemInfo: {
|
||||
enable: true,
|
||||
},
|
||||
};
|
||||
|
||||
const validate = ajv.compile(systemInfoSchema);
|
||||
const isValid = validate(config);
|
||||
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
test('should fail validation when systemInfo.enable is not a boolean', () => {
|
||||
// Test just the systemInfo part of the schema for simplicity
|
||||
const systemInfoSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
systemInfo: basicSettingsSchema.systemInfo,
|
||||
},
|
||||
required: ['systemInfo'],
|
||||
};
|
||||
|
||||
const config = {
|
||||
systemInfo: {
|
||||
enable: 'not-a-boolean',
|
||||
},
|
||||
};
|
||||
|
||||
const validate = ajv.compile(systemInfoSchema);
|
||||
const isValid = validate(config);
|
||||
|
||||
expect(isValid).toBe(false);
|
||||
expect(validate.errors).toContainEqual(
|
||||
expect.objectContaining({
|
||||
instancePath: '/systemInfo/enable',
|
||||
keyword: 'type',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test('should fail validation when systemInfo is missing enable property', () => {
|
||||
// Test just the systemInfo part of the schema for simplicity
|
||||
const systemInfoSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
systemInfo: basicSettingsSchema.systemInfo,
|
||||
},
|
||||
required: ['systemInfo'],
|
||||
};
|
||||
|
||||
const config = {
|
||||
systemInfo: {},
|
||||
};
|
||||
|
||||
const validate = ajv.compile(systemInfoSchema);
|
||||
const isValid = validate(config);
|
||||
|
||||
expect(isValid).toBe(false);
|
||||
expect(validate.errors).toContainEqual(
|
||||
expect.objectContaining({
|
||||
instancePath: '/systemInfo',
|
||||
keyword: 'required',
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import configFileSchema from './config-file-schema.js';
|
||||
|
||||
/**
|
||||
* Creates a modified schema that only validates sections when their associated features are enabled.
|
||||
*
|
||||
*
|
||||
* @param {object} parsedConfig - The parsed configuration object
|
||||
* @param {object} baseSchema - The base schema to modify
|
||||
* @returns {object} Modified schema with conditional validation
|
||||
@@ -14,45 +14,45 @@ import configFileSchema from './config-file-schema.js';
|
||||
function createConditionalSchema(parsedConfig, baseSchema) {
|
||||
// Deep clone the base schema to avoid modifying the original
|
||||
const schema = JSON.parse(JSON.stringify(baseSchema));
|
||||
|
||||
|
||||
// Get the Butler-SOS configuration section
|
||||
const butlerConfig = parsedConfig['Butler-SOS'];
|
||||
if (!butlerConfig) {
|
||||
return schema; // Return original schema if no Butler-SOS section
|
||||
}
|
||||
|
||||
|
||||
const butlerSchema = schema.properties['Butler-SOS'];
|
||||
|
||||
|
||||
// Helper function to create conditional validation for a feature
|
||||
const makeFeatureConditional = (featureName) => {
|
||||
const featureSchema = butlerSchema.properties[featureName];
|
||||
if (!featureSchema) return;
|
||||
|
||||
|
||||
// Store the original schema
|
||||
const originalSchema = JSON.parse(JSON.stringify(featureSchema));
|
||||
|
||||
|
||||
// Create conditional schema using if/then/else
|
||||
butlerSchema.properties[featureName] = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enable: { type: 'boolean' }
|
||||
enable: { type: 'boolean' },
|
||||
},
|
||||
required: ['enable'],
|
||||
if: {
|
||||
properties: { enable: { const: true } }
|
||||
properties: { enable: { const: true } },
|
||||
},
|
||||
then: originalSchema,
|
||||
else: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enable: { type: 'boolean' }
|
||||
enable: { type: 'boolean' },
|
||||
},
|
||||
required: ['enable'],
|
||||
additionalProperties: true // Allow any additional properties when disabled
|
||||
}
|
||||
additionalProperties: true, // Allow any additional properties when disabled
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// Apply conditional validation to features with enable flags
|
||||
makeFeatureConditional('mqttConfig');
|
||||
makeFeatureConditional('newRelic');
|
||||
@@ -62,7 +62,7 @@ function createConditionalSchema(parsedConfig, baseSchema) {
|
||||
makeFeatureConditional('configVisualisation');
|
||||
makeFeatureConditional('heartbeat');
|
||||
makeFeatureConditional('dockerHealthCheck');
|
||||
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
@@ -107,10 +107,12 @@ export async function verifyConfigFileSchema(configFile) {
|
||||
|
||||
// Create a conditional schema based on enabled features
|
||||
const conditionalSchema = createConditionalSchema(parsedFileContent, configFileSchema);
|
||||
|
||||
|
||||
// Log the schema modification for debugging (in development)
|
||||
if (process.env.NODE_ENV === 'development' || process.env.DEBUG_CONFIG_VALIDATION) {
|
||||
console.debug('VERIFY CONFIG FILE: Created conditional schema based on enabled features');
|
||||
console.debug(
|
||||
'VERIFY CONFIG FILE: Created conditional schema based on enabled features'
|
||||
);
|
||||
}
|
||||
|
||||
// Validate the parsed YAML file against the conditional schema
|
||||
@@ -173,6 +175,19 @@ export async function verifyAppConfig(cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that telemetry and system info settings are compatible
|
||||
// If telemetry is enabled but system info gathering is disabled, this creates an incompatibility
|
||||
// because telemetry relies on detailed system information for proper functionality
|
||||
const anonTelemetryEnabled = cfg.get('Butler-SOS.anonTelemetry');
|
||||
const systemInfoEnabled = cfg.get('Butler-SOS.systemInfo.enable');
|
||||
|
||||
if (anonTelemetryEnabled === true && systemInfoEnabled === false) {
|
||||
console.error(
|
||||
'VERIFY CONFIG FILE ERROR: Anonymous telemetry is enabled (Butler-SOS.anonTelemetry=true) but system information gathering is disabled (Butler-SOS.systemInfo.enable=false). Telemetry requires system information to function properly. Either disable telemetry by setting Butler-SOS.anonTelemetry=false or enable system info gathering by setting Butler-SOS.systemInfo.enable=true. Exiting.'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that server tags are correctly defined
|
||||
// In the config file section `Butler-SOS.serversToMonitor.serverTagsDefinition` it's possible to define zero or more tags that can be set for each server that is to be monitored.
|
||||
// When Butler SOS is started, do the following checks:
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('basic-settings schema', () => {
|
||||
expect(basicSettingsSchema.fileLogging).toBeDefined();
|
||||
expect(basicSettingsSchema.logDirectory).toBeDefined();
|
||||
expect(basicSettingsSchema.anonTelemetry).toBeDefined();
|
||||
expect(basicSettingsSchema.systemInfo).toBeDefined();
|
||||
});
|
||||
|
||||
describe('logLevel property', () => {
|
||||
@@ -148,11 +149,59 @@ describe('basic-settings schema', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('systemInfo property', () => {
|
||||
test('should accept valid systemInfo configuration', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: { systemInfo: basicSettingsSchema.systemInfo },
|
||||
};
|
||||
|
||||
const validate = ajv.compile(schema);
|
||||
|
||||
expect(validate({ systemInfo: { enable: true } })).toBe(true);
|
||||
expect(validate({ systemInfo: { enable: false } })).toBe(true);
|
||||
});
|
||||
|
||||
test('should reject invalid systemInfo configuration', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: { systemInfo: basicSettingsSchema.systemInfo },
|
||||
};
|
||||
|
||||
const validate = ajv.compile(schema);
|
||||
|
||||
// Missing enable property
|
||||
expect(validate({ systemInfo: {} })).toBe(false);
|
||||
|
||||
// Invalid enable type
|
||||
expect(validate({ systemInfo: { enable: 'true' } })).toBe(false);
|
||||
expect(validate({ systemInfo: { enable: 1 } })).toBe(false);
|
||||
expect(validate({ systemInfo: { enable: null } })).toBe(false);
|
||||
|
||||
// Additional properties not allowed
|
||||
expect(validate({ systemInfo: { enable: true, extra: 'value' } })).toBe(false);
|
||||
});
|
||||
|
||||
test('should require enable property', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: { systemInfo: basicSettingsSchema.systemInfo },
|
||||
required: ['systemInfo'],
|
||||
};
|
||||
|
||||
const validate = ajv.compile(schema);
|
||||
|
||||
expect(validate({ systemInfo: { enable: true } })).toBe(true);
|
||||
expect(validate({ systemInfo: {} })).toBe(false);
|
||||
expect(validate({})).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('should validate complete basic settings object', () => {
|
||||
const schema = {
|
||||
type: 'object',
|
||||
properties: basicSettingsSchema,
|
||||
required: ['logLevel', 'fileLogging', 'logDirectory', 'anonTelemetry'],
|
||||
required: ['logLevel', 'fileLogging', 'logDirectory', 'anonTelemetry', 'systemInfo'],
|
||||
};
|
||||
|
||||
const validate = ajv.compile(schema);
|
||||
@@ -162,6 +211,7 @@ describe('basic-settings schema', () => {
|
||||
fileLogging: true,
|
||||
logDirectory: './log',
|
||||
anonTelemetry: false,
|
||||
systemInfo: { enable: true },
|
||||
};
|
||||
|
||||
expect(validate(validConfig)).toBe(true);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* This schema covers the fundamental application configuration options:
|
||||
* - Log level and file logging settings
|
||||
* - Anonymous telemetry settings
|
||||
* - System information gathering settings
|
||||
*
|
||||
* @type {object} JSON Schema object for basic settings validation
|
||||
*/
|
||||
@@ -16,4 +17,25 @@ export const basicSettingsSchema = {
|
||||
fileLogging: { type: 'boolean' },
|
||||
logDirectory: { type: 'string' },
|
||||
anonTelemetry: { type: 'boolean' },
|
||||
/**
|
||||
* System information gathering configuration.
|
||||
*
|
||||
* When enabled (default), Butler SOS uses the systeminformation npm package
|
||||
* to collect detailed system information. This package executes certain OS commands
|
||||
* on Windows that may trigger security alerts in enterprise environments:
|
||||
* - `cmd.exe /d /s /c \chcp` (code page information)
|
||||
* - `netstat -r` (routing table)
|
||||
* - `cmd.exe /d /s /c \echo %COMPUTERNAME%.%USERDNSDOMAIN%` (computer/domain names)
|
||||
*
|
||||
* Set to false in security-sensitive environments to disable detailed system
|
||||
* information gathering and prevent these OS command executions.
|
||||
*/
|
||||
systemInfo: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
enable: { type: 'boolean' },
|
||||
},
|
||||
required: ['enable'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ const configFileSchema = {
|
||||
'fileLogging',
|
||||
'logDirectory',
|
||||
'anonTelemetry',
|
||||
'systemInfo',
|
||||
'configVisualisation',
|
||||
'heartbeat',
|
||||
'dockerHealthCheck',
|
||||
|
||||
@@ -37,7 +37,7 @@ function isBinaryFile(fileExtension) {
|
||||
*
|
||||
* @param {string} filePath - The path to the file to prepare
|
||||
* @param {string} [encoding] - Optional encoding for text files, defaults to 'utf8' for text files
|
||||
* @returns {Promise<Object>} - An object containing the file extension, a stream of the file contents, and a found flag
|
||||
* @returns {Promise<object>} - An object containing the file extension, a stream of the file contents, and a found flag
|
||||
*/
|
||||
export async function prepareFile(filePath, encoding) {
|
||||
globals.logger.verbose(`FILE PREP: Preparing file ${filePath}`);
|
||||
|
||||
Reference in New Issue
Block a user