Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions appserver/static/javascript/views/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,35 @@ define(["react", "splunkjs/splunk"], function(react, splunk_js_sdk){
class SetupPage extends react.Component {
constructor(props) {
super(props);

this.state = {
password: ''
password: '',
batching: false,
batch_size: 10,
};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

async componentDidMount() {
try {
const settings = await Setup.fetchSettings(splunk_js_sdk);
if (settings) {
this.setState({
batching: settings.batching ?? false,
batch_size: settings.batch_size ?? 10,
});
}
} catch (error) {
console.error("Failed to load existing CrowdSec settings:", error);
}
}

handleChange(event) {
this.setState({ ...this.state, [event.target.name]: event.target.value})
const { name, type, checked, value } = event.target;
const normalizedValue = name === "batch_size" ? parseInt(value, 10) || 10 : value;

this.setState({ ...this.state, [name]: type === "checkbox" ? checked : normalizedValue })
}

async handleSubmit(event) {
Expand All @@ -31,7 +49,20 @@ define(["react", "splunkjs/splunk"], function(react, splunk_js_sdk){
e("form", { onSubmit: this.handleSubmit }, [
e("label", null, [
" ",
e("input", { type: "text", name: "password", value: this.state.password, onChange: this.handleChange })
e("input", { type: "text", name: "password", value: this.state.password, placeholder: "Leave empty to keep existing key", onChange: this.handleChange })
]),
e("label", null, [
" Enable batching",
e("input", { type: "checkbox", name: "batching", checked: this.state.batching, onChange: this.handleChange })
]),
e("label", null, [
" Batch size",
e("select", { name: "batch_size", value: this.state.batch_size, onChange: this.handleChange, disabled: !this.state.batching }, [
e("option", { value: 10 }, "10"),
e("option", { value: 20 }, "20"),
e("option", { value: 50 }, "50"),
e("option", { value: 100 }, "100")
])
]),
e("input", { type: "submit", value: "Submit" })
])
Expand Down
185 changes: 136 additions & 49 deletions appserver/static/javascript/views/store_secret.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
"use strict";

import * as Config from './setup_configuration.js'
import * as SplunkHelpers from './splunk_helpers.js'
import { promisify } from './util.js'

const APP_NAME = "crowdsec-splunk-app";
const APPLICATION_NAMESPACE = {
owner: "nobody",
app: APP_NAME,
sharing: "app",
};
const SETTINGS_CONF = "crowdsec_settings";
const SETTINGS_STANZA = "settings";

function extractSplunkErrorMessage(error) {
try {
Expand All @@ -19,70 +30,146 @@ function extractSplunkErrorMessage(error) {


export async function perform(splunk_js_sdk, setup_options) {
var app_name = "crowdsec-splunk-app";

var application_name_space = {
owner: "nobody",
app: app_name,
sharing: "app",
};

try {
const service = Config.create_splunk_js_sdk_service(
splunk_js_sdk,
application_name_space,
)
;
splunk_js_sdk,
APPLICATION_NAMESPACE,
);

let {password, ...properties} = setup_options;
let { password, ...properties } = setup_options;

var storagePasswords = service.storagePasswords();
// Fetch the storagePasswords to ensure we have the latest state
await storagePasswords.fetch();
const passwords = await storagePasswords.list();
if (password && password.trim().length > 0) {
var storagePasswords = service.storagePasswords();
// Fetch the storagePasswords to ensure we have the latest state
await storagePasswords.fetch();
const passwords = await storagePasswords.list();

// Search for existing entry
const existing = passwords.find(p =>
p.name === "crowdsec-splunk-app_realm:api_key:"
);
// Search for existing entry
const existing = passwords.find(p =>
p.name === "crowdsec-splunk-app_realm:api_key:"
);

if (existing) {
console.log("Api key exists. Updating existing entry...");
const qualifiedPath = existing.qualifiedPath;
const endpoint = new splunk_js_sdk.Service.Endpoint(service, qualifiedPath);
// Edit the password using .post()
await new Promise((resolve, reject) => {
// @see https://docs.splunk.com/DocumentationStatic/JavaScriptSDK/2.0.0/splunkjs.Service.StoragePasswords.html#splunkjs.Service.StoragePasswords^post
endpoint.post("", {password: password}, (err, response) => {
if (err) {
console.error("Error updating APi key:", err);
reject(err);
} else {
console.log("API key updated successfully");
resolve(response);
}
if (existing) {
console.log("Api key exists. Updating existing entry...");
const qualifiedPath = existing.qualifiedPath;
const endpoint = new splunk_js_sdk.Service.Endpoint(service, qualifiedPath);
// Edit the password using .post()
await new Promise((resolve, reject) => {
// @see https://docs.splunk.com/DocumentationStatic/JavaScriptSDK/2.0.0/splunkjs.Service.StoragePasswords.html#splunkjs.Service.StoragePasswords^post
endpoint.post("", { password: password }, (err, response) => {
if (err) {
console.error("Error updating APi key:", err);
reject(err);
} else {
console.log("API key updated successfully");
resolve(response);
}
});
});
});
} else {
// @see https://docs.splunk.com/DocumentationStatic/JavaScriptSDK/2.0.0/splunkjs.Service.StoragePasswords.html#splunkjs.Service.StoragePasswords^create
await storagePasswords.create({
} else {
// @see https://docs.splunk.com/DocumentationStatic/JavaScriptSDK/2.0.0/splunkjs.Service.StoragePasswords.html#splunkjs.Service.StoragePasswords^create
await storagePasswords.create({
name: "api_key",
realm: "crowdsec-splunk-app_realm",
password: password
},
function (err) {
if (err) {
console.error("Error storing API key:", err);
throw err;
}
});
console.log("API key stored successfully:");
function (err) {
if (err) {
console.error("Error storing API key:", err);
throw err;
}
});
console.log("API key stored successfully");
}
} else {
console.log("No API key supplied. Existing key will be kept as-is.");
}

await persistAdditionalSettings(service, properties);

await Config.complete_setup(service);
await Config.reload_splunk_app(service, app_name);
Config.redirect_to_splunk_app_homepage(app_name);
await Config.reload_splunk_app(service, APP_NAME);
Config.redirect_to_splunk_app_homepage(APP_NAME);
} catch (error) {
console.error('Error:', error);
alert('Error:' + extractSplunkErrorMessage(error));
}
}

export async function fetchSettings(splunk_js_sdk) {
try {
const service = Config.create_splunk_js_sdk_service(
splunk_js_sdk,
APPLICATION_NAMESPACE,
);
return await readExistingSettings(service);
} catch (error) {
console.warn("Unable to create Splunk service for fetching settings:", error);
return getDefaultSettings();
}
}

function getDefaultSettings() {
return {
batching: false,
batch_size: 10,
};
}

async function persistAdditionalSettings(service, properties) {
if (!properties) {
return;
}

const settingsPayload = {};

if (typeof properties.batching === "boolean") {
settingsPayload.batching = properties.batching ? "true" : "false";
}

if (typeof properties.batch_size !== "undefined" && properties.batch_size !== null) {
settingsPayload.batch_size = String(properties.batch_size);
}

if (Object.keys(settingsPayload).length === 0) {
return;
}

await SplunkHelpers.update_configuration_file(
service,
SETTINGS_CONF,
SETTINGS_STANZA,
settingsPayload,
);
}

async function readExistingSettings(service) {
const defaults = getDefaultSettings();

try {
let configurations = service.configurations({});
configurations = await promisify(configurations.fetch)();

const configFile = configurations.item(SETTINGS_CONF);
if (!configFile) {
return defaults;
}

await promisify(configFile.fetch)();
const stanza = configFile.item(SETTINGS_STANZA);
if (!stanza) {
return defaults;
}

await promisify(stanza.fetch)();
const props = stanza.properties();
console.log("Existing CrowdSec settings:", props);
return {
batching: props.batching ? props.batching === "1" : defaults.batching,
batch_size: props.batch_size ? (parseInt(props.batch_size, 10) || defaults.batch_size) : defaults.batch_size,
};
} catch (error) {
console.warn("Unable to load existing CrowdSec settings:", error);
return defaults;
}
}
2 changes: 0 additions & 2 deletions appserver/static/javascript/views/util.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
function promisify(fn) {
console.log("promisify: Don't use this in production! Use a proper promisify library instead.")

// return a new promisified function
return (...args) => {
return new Promise((resolve, reject) => {
Expand Down
Loading