Skip to content

Commit

Permalink
show system, show config and show backup
Browse files Browse the repository at this point in the history
  • Loading branch information
mihakralj committed Aug 20, 2023
1 parent e40ca63 commit 1a0781c
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 148 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# run make help for options

# Variables
VERSION=0.3.0
VERSION=0.4.0
GO=go
BUILD_DIR=build
BINARY_NAME=opnsense
Expand Down
70 changes: 44 additions & 26 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
# OPNsense CLI
# OPNsense Command Line Interface (CLI)

The **opnsense** CLI is a command-line utility designed to manage, configure, and monitor OPNsense firewall systems. It provides administrators and power users an alternative way to interact with the firewall system without relying on the graphical user interface (GUI). The CLI facilitates both local and remote management via an SSH tunnel, making it versatile for a range of deployment scenarios. Whether it's executing direct configurations, orchestrating complex automation tasks, or troubleshooting on-the-fly, opnsense CLI streamlines these operations through FreeBSD shell commands, employing the same commands used in the Web GUI. Designed with transparency and verifiability in mind, the CLI's shell commands are traceable and each change to the firewall is seeking interactive confirmation (unless supressed with --force flag). Opnsense CLI supports various operating systems including Windows, macOS, Linux, and OpenBSD, allowing creation of administration scripts on either locally on OPNsense system or remotely from admin workstation.
*OPNsense CLI* is a command-line utility for FreeBSD, Linux, MacOS and Windows that empowers administrators and power users to manage, configure, and monitor OPNsense firewall systems. The CLI provides an alternative method to browser-based GUI to interact with the firewall system.

The opnsense CLI can be installed on Windows, macOS, Linux, and OpenBSD, and can target an OPNsense system either locally or remotely.
## Features and Benefits
- *Versatility*: Can operate both locally and remotely (via SSH),and is suitable for various deployment scenarios.
- *Transparency and Control*: All opnsense-cli Commands are bash scripts (and not API calls), with interactive confirmation for changes (bypassable with the --force flag).
- *Cross-Platform Support*: Works with macOS, Windows, Linux, and OpenBSD.
- *Streamlined Operations*: Facilitates repeatable configurations, troubleshooting and complex automations.

## Why Use opnsense CLI?

The OPNsense GUI offers fail-safe but limited configuration capabilities, while the FreeBSD terminal provides direct access to all functionalities of OPNsense but with a greater risk of errors. The `opnsense-cli` utility bridges this gap by:
Administrators typically only two options how to manage the firewall: either through browser-based GUI, or using FreeBSD commands in bash. The opnsense CLI utility provides the middle option by using command line to quickly perform common tasks:

- Allowing a quick view of basic firewall vitals.
- Providing command-line access to a local or remote OPNsense firewall.
- Enabling staged and controlled changes to `conf/config.xml` with rollback options.
- Allowing controlled execution of OPNsense `configctl` commands, calling the same pre-configured commands as the GUI.
- A quick view of firewall settings such as configuration, backups, system vitals.
- Controlled changes of `conf/config.xml`, including staging and rollback options.
- Controlled execution of any OPNsense command available through `configctl`.

## Usage

`opnsense [flags] command [parameters]`

### (Planned) Commands
### Commands

- **`show system <xpath>`**: Retrieves system information from the firewall.
- **`show config <xpath>`**: Displays hierarchical segments of `config.xml`.
Expand All @@ -44,20 +47,35 @@ The OPNsense GUI offers fail-safe but limited configuration capabilities, while

## Installation

BSD12/13 install of opnsense.txz package (into _usr/local/bin_):
`sudo pkg add https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense.txz`

Signed macos pkg (into _/usr/local/bin_):
`wget https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense-macos.pkg`
`sudo installer -pkg opnsense-macos.pkg -target /`

Direct download of signed macos opnsense binary:
`wget https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense-macos -O opnsense`
`sudo chmod +x opnsense-macos`

Ubuntu/Debian opnsense.deb install (into _usr/local/bin_):
`wget https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense.deb`
`dpkg -i opnsense.deb`

Windows opnsense.exe:
` Invoke-WebRequest -Uri https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense.exe -OutFile opnsense.exe`
### FreeBSD 12/13

installs to `usr/local/bin`:
```bash
sudo pkg add https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense.txz
```
### Debian/Ubuntu

installs to `usr/local/bin`:
```bash
curl -O https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense.deb
dpkg -i opnsense.deb
```

### macOS
Package install (installs to `/usr/local/bin`):
```bash
curl -O https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense.pkg
sudo installer -pkg opnsense.pkg -target /
```

signed binary:
```bash
curl -O https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense -O opnsense
sudo chmod +x opnsense
```

### Windows
executable `opnsense.exe`:
```powershell
Invoke-WebRequest -Uri https://github.com/mihakralj/opnsense-cli/releases/download/beta/opnsense.exe -OutFile opnsense.exe
```
70 changes: 33 additions & 37 deletions cmd/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,58 +16,54 @@ import (
// backupCmd represents the backup command
var backupCmd = &cobra.Command{
Use: "backup",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Short: "Lists available backup configurations or a specific backup",
Long: `The 'backup' command allows you to view the available backup configurations in the OPNsense system or retrieve details of a specific backup. It's an essential tool for managing and understanding the saved configurations within your firewall system.
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Example usage:
- show backup: Lists all available backup configurations.
- show backup <config>: Displays details of a specific backup configuration identified by <config>.`,
Run: func(cmd *cobra.Command, args []string) {

// grab config.xml
configdoc := etree.NewDocument()
bash := fmt.Sprintf("cat %s", configfile)
config, err := internal.ExecuteCmd(bash, host)
backupdir := "/conf/backup/"
path := "backups"
internal.Checkos()
backupdoc := etree.NewDocument()
bash := fmt.Sprintf(`count=$(find %s -type f | wc -l | sed -e 's/^[[:space:]]*//') && echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<backups count=\"$count\">" &&
find /conf/backup -type f -exec sh -c 'echo $(stat -f "%%m" "$1") $(basename "$1") $(stat -f "%%z" "$1") $(md5sum "$1" | cut -c -6)' sh {} \; | sort -nr -k1 |
awk '{
node = gensub(/[^a-zA-Z0-9_]/, "_", "g", $2);
date = strftime("%%Y-%%m-%%dT%%H:%%M:%%S", $1)
print " <" $2 " date=\"" date "\" size=\"" $3 "\" md5=\"" $4 "\" />";}
END { print "</backups>"; }'`, backupdir)

backups, err := internal.ExecuteCmd(bash, host)
if err != nil {
internal.Log(1, "execution error: %s", err.Error())
}
err = configdoc.ReadFromString(config)
//fmt.Println(backups)
err = backupdoc.ReadFromString(backups)
if err != nil {
internal.Log(1, "%s is not an XML", configfile)
internal.Log(1, "did not receive XML")
}
configout := internal.ConfigToXML(configdoc, "")

// generate backup name
backupdir := "/conf/backup/"
backupfile := generateBackupFilename()

// generate hash
backupout := ""
if xmlFlag {
backupout = internal.ConfigToXML(backupdoc, path)
} else if jsonFlag {
backupout = internal.ConfigToJSON(backupdoc, path)
} else if yamlFlag {
backupout = internal.ConfigToJSON(backupdoc, path)
} else {
backupout = internal.ConfigToTTY(backupdoc, path)
}

// check for existence of backup folder
fmt.Println(backupout)

// pour backup file into the backup folder
bash = fmt.Sprintf("mkdir -p %s&&cat >%s%s <<EOF\n%s\nEOF", backupdir, backupdir, backupfile, configout)
ret, err := internal.ExecuteCmd(bash, host)
if err != nil {
internal.Log(1, "execution error: %s", err.Error())
}
fmt.Println(ret)
},
}

func init() {
rootCmd.AddCommand(backupCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// backupCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// backupCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
showCmd.AddCommand(backupCmd)
}

func generateBackupFilename() string {
Expand Down
92 changes: 92 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
Copyright © 2023 MihaK [email protected]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
"fmt"
"regexp"
"strings"

"github.com/beevik/etree"
"github.com/mihakralj/opnsense/internal"
"github.com/spf13/cobra"
)

// configCmd represents the show command
var configCmd = &cobra.Command{
Use: "config",
Short: "Displays information about configuration stored in config.xml",
Long: `The show command displays configuration elements in config.xml, including interfaces, routes, firewall rules, and other system settings.
Use this command to view the current system configuration and troubleshoot issues.`,
Run: func(cmd *cobra.Command, args []string) {

path := "opnsense"
if len(args) >= 1 {
trimmedArg := strings.Trim(args[0], "/")
if matched, _ := regexp.MatchString(`\[0\]`, trimmedArg); matched {
internal.Log(1, "XPath indexing of elements starts with 1, not 0")
}
if trimmedArg != "" {
path = trimmedArg
}
parts := strings.Split(path, "/")
if parts[0] != "opnsense" {
path = "opnsense/" + path
}
}
bash := ""
//internal.Checkos()
configdoc := etree.NewDocument()
bash = fmt.Sprintf("cat %s", configfile)
config, err := internal.ExecuteCmd(bash, host)
if err != nil {
internal.Log(1, "execution error: %s", err.Error())
}
err = configdoc.ReadFromString(config)
if err != nil {
internal.Log(1, "%s is not an XML", configfile)
}

stagingdoc := etree.NewDocument()
bash = fmt.Sprintf("if [ -f %s ]; then cat %s; else cat %s; fi", stagingfile, stagingfile, configfile)
staging, err := internal.ExecuteCmd(bash, host)
if err != nil {
internal.Log(1, "execution error: %s", err.Error())
}
err = stagingdoc.ReadFromString(staging)
if err != nil {
internal.Log(1, "%s is not an XML", stagingfile)
}

configout := ""
if xmlFlag {
configout = internal.ConfigToXML(configdoc, path)
} else if jsonFlag {
configout = internal.ConfigToJSON(configdoc, path)
} else if yamlFlag {
configout = internal.ConfigToJSON(configdoc, path)
} else {
configout = internal.ConfigToTTY(configdoc, path)
}

fmt.Println(configout)

},
}

func init() {
showCmd.AddCommand(configCmd)
}
83 changes: 18 additions & 65 deletions cmd/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -17,80 +17,33 @@ package cmd

import (
"fmt"
"regexp"
"strings"

"github.com/beevik/etree"
"github.com/mihakralj/opnsense/internal"
"github.com/spf13/cobra"
)

// showCmd represents the show command
var showCmd = &cobra.Command{
Use: "show",
Short: "Displays information about configuration stored in config.xml",
Long: `The show command displays configuration elements in config.xml, including interfaces, routes, firewall rules, and other system settings.
Use this command to view the current system configuration and troubleshoot issues.`,
Use: "show [node]",
Short: "Display information related to OPNsense system",
Long: `The 'show' command retrieves various details about the OPNsense system:
- show system <xpath>: Retrieves system information from the firewall.
- show config <xpath>: Displays hierarchical segments of config.xml.
- show backup <config>: Lists available backup configs or a specific backup.`,
Run: func(cmd *cobra.Command, args []string) {

path := "opnsense"
if len(args) >= 1 {
trimmedArg := strings.Trim(args[0], "/")
if matched, _ := regexp.MatchString(`\[0\]`, trimmedArg); matched {
internal.Log(1, "XPath indexing of elements starts with 1, not 0")
}
if trimmedArg != "" {
path = trimmedArg
}
parts := strings.Split(path, "/")
if parts[0] != "opnsense" {
path = "opnsense/" + path
}
}
bash := ""
//internal.Checkos()
configdoc := etree.NewDocument()
bash = fmt.Sprintf("cat %s", configfile)
config, err := internal.ExecuteCmd(bash, host)
if err != nil {
internal.Log(1, "execution error: %s", err.Error())
}
err = configdoc.ReadFromString(config)
if err != nil {
internal.Log(1, "%s is not an XML", configfile)
}

stagingdoc := etree.NewDocument()
bash = fmt.Sprintf("if [ -f %s ]; then cat %s; else cat %s; fi", stagingfile, stagingfile, configfile)
staging, err := internal.ExecuteCmd(bash, host)
if err != nil {
internal.Log(1, "execution error: %s", err.Error())
}
err = stagingdoc.ReadFromString(staging)
if err != nil {
internal.Log(1, "%s is not an XML", stagingfile)
}

if false {
fmt.Println(stagingdoc)
}

configout := ""
if xmlFlag {
configout = internal.ConfigToXML(configdoc, path)
} else if jsonFlag {
configout = internal.ConfigToJSON(configdoc, path)
} else if yamlFlag {
configout = internal.ConfigToJSON(configdoc, path)
} else {
configout = internal.ConfigToTTY(configdoc, path)
}

fmt.Println(configout)

fmt.Println("Specify a subcommand such as 'system', 'config', or 'backup'")
},
}

func init() {
rootCmd.AddCommand(showCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// showCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// showCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
Loading

0 comments on commit 1a0781c

Please sign in to comment.