evectl 0.1.1

EveCtl with Suricata and EveBox
Documentation
// SPDX-FileCopyrightText: (C) 2023 Jason Ish <jason@codemonkey.net>
// SPDX-License-Identifier: MIT

use crate::prelude::*;

use crate::{config::EveBoxServerConfig, context::Context, term};

#[derive(Clone)]
enum Options {
    EnableToggle,
    ToggleTls,
    ToggleAuth,
    ResetPassword,
    EnableRemote,
    DisableRemote,
    ToggleElasticsearch,
    UseExternalElasticsearch,
    ElasticsearchUrl,
    Return,
}

pub(crate) fn menu(context: &mut Context) -> Result<()> {
    //let config = &context.config.evebox_server;
    loop {
        term::clear();

        let mut selections = crate::prompt::Selections::with_index();

        if context.config.evebox_server.enabled {
            let database = if context.config.evebox_server.use_external_elasticsearch {
                "external elasticsearch"
            } else if context.config.elasticsearch.enabled {
                "managed elasticsearch"
            } else {
                "embedded sqlite"
            };
            selections.push(
                Options::EnableToggle,
                format!("Disable EveBox Server [enabled, {}]", database),
            );
        } else {
            selections.push(Options::EnableToggle, "Enable EveBox Server [disabled]");
        }

        if context.config.evebox_server.allow_remote {
            selections.push(Options::DisableRemote, "Disable Remote Access [enabled]");
        } else {
            selections.push(Options::EnableRemote, "Enable Remote Access [disabled]");
        }
        selections.push(
            Options::ToggleTls,
            format!(
                "Toggle TLS [{}]",
                if context.config.evebox_server.no_tls {
                    "disabled"
                } else {
                    "enabled"
                }
            ),
        );
        selections.push(
            Options::ToggleAuth,
            format!(
                "Toggle authentication [{}]",
                if context.config.evebox_server.no_auth {
                    "disabled"
                } else {
                    "enabled"
                }
            ),
        );

        selections.push(
            Options::ToggleElasticsearch,
            format!(
                "Use Managed Elasticsearch [{}]",
                if context.config.elasticsearch.enabled {
                    "true"
                } else {
                    "false"
                }
            ),
        );

        selections.push(
            Options::UseExternalElasticsearch,
            format!("Use External Elasticsearch [{}]", {
                if context.config.evebox_server.use_external_elasticsearch {
                    "true"
                } else {
                    "false"
                }
            }),
        );

        if context.config.evebox_server.use_external_elasticsearch {
            selections.push(
                Options::ElasticsearchUrl,
                format!(
                    "External Elasticsearch URL: [{}]",
                    context
                        .config
                        .evebox_server
                        .elasticsearch_client
                        .url
                        .as_deref()
                        .unwrap_or("not set")
                ),
            );
        }

        selections.push(Options::ResetPassword, "Reset Admin Password");
        selections.push(Options::Return, "Return");

        if let Ok(selection) =
            inquire::Select::new("EveCtl: Configure EveBox Server", selections.to_vec())
                .with_page_size(16)
                .prompt()
        {
            match selection.tag {
                Options::EnableToggle => {
                    context.config.evebox_server.enabled = !context.config.evebox_server.enabled;
                }
                Options::ToggleTls => toggle_tls(&mut context.config.evebox_server),
                Options::ToggleAuth => toggle_auth(&mut context.config.evebox_server),
                Options::ResetPassword => crate::evebox::server::reset_password(context),
                Options::EnableRemote => enable_remote_access(context),
                Options::DisableRemote => disable_remote_access(context),
                Options::ToggleElasticsearch => toggle_elasticsearch(context)?,
                Options::UseExternalElasticsearch => use_external_elasticsearch(context)?,
                Options::ElasticsearchUrl => {
                    set_elasticsearch_url(context)?;
                }
                Options::Return => break,
            }
        } else {
            break;
        }
    }

    Ok(())
}

fn toggle_elasticsearch(context: &mut Context) -> Result<()> {
    if context.config.evebox_server.use_external_elasticsearch {
        warn!("Using managed Elasticsearch will disable use of the external Elasticsearch server.");
        if !inquire::Confirm::new("Continue?")
            .with_default(true)
            .prompt()?
        {
            return Ok(());
        }
    }
    context.config.elasticsearch.enabled = !context.config.elasticsearch.enabled;
    if context.config.elasticsearch.enabled {
        context.config.evebox_server.use_external_elasticsearch = false;
    }
    Ok(())
}

fn use_external_elasticsearch(context: &mut Context) -> Result<()> {
    if context.config.evebox_server.use_external_elasticsearch {
        context.config.evebox_server.use_external_elasticsearch = false;
    } else {
        if context.config.elasticsearch.enabled {
            warn!("Using external Elasticsearch will disable use of the managed Elasticsearch server.");
            if !inquire::Confirm::new("Continue?")
                .with_default(true)
                .prompt()?
            {
                return Ok(());
            }
        }
        context.config.evebox_server.use_external_elasticsearch = true;
        set_elasticsearch_url(context)?;
    }
    Ok(())
}

fn toggle_tls(config: &mut EveBoxServerConfig) {
    if config.no_tls {
        config.no_tls = false;
    } else {
        if config.allow_remote {
            match inquire::Confirm::new(
                "Remote access is enabled, are you sure you want to disable TLS",
            )
            .with_default(false)
            .prompt()
            {
                Ok(true) => {}
                Ok(false) | Err(_) => return,
            }
        }
        config.no_tls = true;
    }
}

fn toggle_auth(config: &mut EveBoxServerConfig) {
    if config.no_auth {
        config.no_tls = false;
    } else {
        if config.allow_remote {
            match inquire::Confirm::new(
                "Remote access is enabled, are you sure you want to disable authentication",
            )
            .with_default(false)
            .prompt()
            {
                Ok(true) => {}
                Ok(false) | Err(_) => return,
            }
        }
        config.no_auth = true;
    }
}

fn enable_remote_access(context: &mut Context) {
    if context.config.evebox_server.no_tls {
        warn!("Enabling TLS");
        context.config.evebox_server.no_tls = false;
    }
    if context.config.evebox_server.no_auth {
        warn!("Enabling authentication");
        context.config.evebox_server.no_auth = false;
    }
    context.config.evebox_server.allow_remote = true;

    if let Ok(true) = inquire::Confirm::new("Do you wish to reset the admin password")
        .with_default(true)
        .prompt()
    {
        crate::evebox::server::reset_password(context);
    }
}

fn disable_remote_access(context: &mut Context) {
    context.config.evebox_server.allow_remote = false;
}

fn set_elasticsearch_url(context: &mut Context) -> Result<()> {
    let mut url = context
        .config
        .evebox_server
        .elasticsearch_client
        .url
        .clone()
        .unwrap_or_default();
    let mut index = context
        .config
        .evebox_server
        .elasticsearch_client
        .index
        .clone()
        .unwrap_or_else(|| "evebox".to_string());
    let mut username = context
        .config
        .evebox_server
        .elasticsearch_client
        .username
        .clone()
        .unwrap_or_default();
    let mut password = context
        .config
        .evebox_server
        .elasticsearch_client
        .password
        .clone()
        .unwrap_or_default();

    loop {
        url = inquire::Text::new("Enter the Elasticsearch URL:")
            .with_placeholder("http://elasticsearch:9200")
            .with_default(&url)
            .prompt()?;
        if url.is_empty() {
            return Ok(());
        }
        index = inquire::Text::new("Enter the Elasticsearch index:")
            .with_default(&index)
            .prompt()?;
        username = inquire::Text::new("Enter the Elasticsearch username:")
            .with_default(&username)
            .with_help_message("Username is optional; ESC to clear current username.")
            .prompt_skippable()?
            .unwrap_or_default();
        password = inquire::Text::new("Enter the Elasticsearch password:")
            .with_default(&password)
            .with_help_message(
                "Password is option; ESC to clear current password. Note: password not masked.",
            )
            .prompt_skippable()?
            .unwrap_or_default();
        let disable_certificate_validation = if url.starts_with("https://") {
            inquire::Confirm::new("Disable certificate validation?")
                .with_default(false)
                .prompt()?
        } else {
            false
        };

        let client = reqwest::blocking::Client::builder()
            .danger_accept_invalid_certs(disable_certificate_validation)
            .build()?;
        let mut request = client.get(&url);
        if !username.is_empty() {
            let password = if password.is_empty() {
                None
            } else {
                Some(password.clone())
            };
            request = request.basic_auth(&username, password);
        }
        let success = match request.send() {
            Ok(response) => {
                let status = response.status();
                let success = status.is_success();
                let body = response.text().ok();
                if success {
                    info!(
                        "Connected successfully to Elasticsearch: body={}",
                        body.unwrap_or_default()
                    );
                    true
                } else {
                    error!(
                        "Failed to connect to Elasticsearch: {}: body={}",
                        status,
                        body.unwrap_or_default()
                    );
                    false
                }
            }
            Err(err) => {
                error!("Failed to connect to Elasticsearch: {}", err);
                false
            }
        };

        if !success {
            if inquire::Confirm::new("Retry?")
                .with_default(true)
                .prompt_skippable()?
                .unwrap_or_default()
            {
                continue;
            } else {
                return Ok(());
            }
        } else {
            context.config.evebox_server.elasticsearch_client.url = Some(url);
            context.config.evebox_server.elasticsearch_client.index = Some(index);
            context.config.evebox_server.elasticsearch_client.username = if username.is_empty() {
                None
            } else {
                Some(username)
            };
            context.config.evebox_server.elasticsearch_client.password = if password.is_empty() {
                None
            } else {
                Some(password)
            };
            context
                .config
                .evebox_server
                .elasticsearch_client
                .disable_certificate_validation = disable_certificate_validation;
            crate::prompt::enter();
            break;
        }
    }

    context.config.save()?;

    Ok(())
}