Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
26c50085d6 | |||
13d3b2cef3 | |||
4100762986 | |||
a3527d3388 | |||
35fb9fc270 | |||
6fe91f816b | |||
f8a9528e0f | |||
a9c6400761 |
25
.gitea/workflows/security.yaml
Normal file
25
.gitea/workflows/security.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
name: "code scans"
|
||||
on: [push, pull_request] #runs on pushes to any branch
|
||||
|
||||
jobs:
|
||||
scans:
|
||||
runs-on: smoke-test
|
||||
steps:
|
||||
- name: "clone code"
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: "install go"
|
||||
uses: https://code.jakeyoungdev.com/actions/install-go@v0.1.3
|
||||
with:
|
||||
commands: |
|
||||
golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
- name: "dependency and stdlib scan"
|
||||
uses: https://code.jakeyoungdev.com/actions/report-vulns@master
|
||||
with:
|
||||
manager: go
|
||||
|
||||
- name: "static code analysis"
|
||||
uses: securego/gosec@master
|
||||
with:
|
||||
args: ./...
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -24,4 +24,4 @@ go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
todo.txt
|
||||
|
63
README.md
63
README.md
@ -1,15 +1,16 @@
|
||||
# mctl
|
||||
mctl is a terminal-friendly remote connection client
|
||||
mctl is a terminal-friendly remote console client
|
||||
|
||||
## Installation
|
||||
Install mctl using golang
|
||||
```bash
|
||||
go install code.jakeyoungdev.com/jake/mctl@main #it is recommended to use a tagged version
|
||||
```
|
||||
<br />
|
||||
|
||||
## Setup
|
||||
### Configuring mctl
|
||||
mctl requires a one-time setup via the 'config' command, password is entered securely from the terminal
|
||||
mctl requires a one-time setup via the 'config' command before interacting with any servers, password is entered securely from the terminal and encrypted
|
||||
```bash
|
||||
mctl config -s <serveraddress> -p <rconport>
|
||||
```
|
||||
@ -22,7 +23,7 @@ mctl login #makes auth request to server with saved password
|
||||
```
|
||||
|
||||
### Sending commands
|
||||
If login is successful the app will enter the command loop, which allows commands to be sent directly to the server until 'mctl' is sent. Commands are sent as-is to the server, there is no validation of command syntax within mctl
|
||||
If login is successful the app will enter the command loop, which allows commands to be sent directly to the server. Commands are sent as-is to the server, there is no validation of command syntax within mctl
|
||||
```
|
||||
Logging into X.X.X.X on port 61695
|
||||
Connected! Type 'mctl' to close
|
||||
@ -31,12 +32,59 @@ RCON@X.X.X.X /> list
|
||||
There are 0 of a max of 20 players online:
|
||||
```
|
||||
|
||||
### Saving commands
|
||||
Commands can be saved under an alias for quick execution later, saved commands can contain placeholders '%s' that can be populated at runtime to allow for commands with unique runtime args to still be saved, see [example](#saving-and-running-example) for more:
|
||||
```bash
|
||||
mctl save <name>
|
||||
```
|
||||
|
||||
### Viewing commands
|
||||
Saved commands can be viewed with:
|
||||
```bash
|
||||
mctl view <name>
|
||||
```
|
||||
All saved commands can be viewed with:
|
||||
```bash
|
||||
mctl view all
|
||||
```
|
||||
|
||||
### Running saved commands
|
||||
Commands that have been saved can be run with:
|
||||
```bash
|
||||
mctl run <name>
|
||||
```
|
||||
If the saved command contains placeholders, the necessary arguments must be supplied:
|
||||
```bash
|
||||
mctl run <name> args...
|
||||
```
|
||||
|
||||
### Saving and running example
|
||||
```bash
|
||||
#saving command named "test" to run "tp %s 0 0 0"
|
||||
mctl save test
|
||||
Command: tp %s 0 0 0
|
||||
|
||||
#run command on user "jake"
|
||||
mctl run test jake
|
||||
#will run: tp jake 0 0 0 on remote server
|
||||
```
|
||||
|
||||
### Delete saved command
|
||||
Commands can be deleted with:
|
||||
```bash
|
||||
mctl delete <name>
|
||||
```
|
||||
|
||||
## Documentation
|
||||
### Commands
|
||||
|Command|Description|
|
||||
|---|---|
|
||||
|config|used to update the config file|
|
||||
|login|makes connection request to the server using saved configuration and enters command loop|
|
||||
|save \<name>|saves specific command for reuse|
|
||||
|view \<name>|displays saved command|
|
||||
|delete \<name>|deletes saved command|
|
||||
|run \<name> args...|runs saved command filling placeholders with supplied args|
|
||||
|
||||
### Flags
|
||||
#### config
|
||||
@ -45,14 +93,13 @@ There are 0 of a max of 20 players online:
|
||||
|port|p|yes|RCon port|
|
||||
|server|s|yes|RCon address|
|
||||
|
||||
#### login
|
||||
no flags
|
||||
|
||||
### Configuration file
|
||||
All configuration data will be kept in /home/.mctl.yaml or C:\\Users\\username\\.mctl.yaml, passwords are encrypted for an added layer of security
|
||||
All configuration data will be kept in the home directory and any sensitive data is encrypted for added security
|
||||
|
||||
## Security
|
||||
RCon is an inherently insecure protocol, passwords are sent in plaintext and, if possible, the port should not be exposed to the internet. It is best to keep these connections local or over a VPN
|
||||
RCon is an inherently insecure protocol, passwords are sent in plaintext and, if possible, the port should not be exposed to the internet. It is best to keep these connections local or over a VPN.
|
||||
|
||||
mctl utilizes [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) and [gosec](https://github.com/securego/gosec) in workflows to ensure quality, secure code is being pushed. These workflow steps must pass before a PR will be accepted
|
||||
|
||||
## Development
|
||||
this repo is currently in heavy development and may encounter breaking changes, use a tag to prevent any surprises
|
59
client/mcr.go
Normal file
59
client/mcr.go
Normal file
@ -0,0 +1,59 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.jakeyoungdev.com/jake/mctl/cryptography"
|
||||
"github.com/jake-young-dev/mcr"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
/*
|
||||
This is a simple wrapper for the MCR client to provide easy use of mcr without having to manually
|
||||
decrypt the password/hit viper each time.
|
||||
*/
|
||||
|
||||
type Client struct {
|
||||
cli *mcr.Client
|
||||
}
|
||||
|
||||
type IClient interface {
|
||||
Close()
|
||||
Command(cmd string) (string, error)
|
||||
}
|
||||
|
||||
// creates a new mcr client using saved credentials and decrypted password
|
||||
func New() (*Client, error) {
|
||||
//grab saved credentials
|
||||
server := viper.Get("server").(string)
|
||||
password := viper.Get("password").(string)
|
||||
port := viper.Get("port").(int)
|
||||
fmt.Printf("Logging into %s on port %d\n", server, port)
|
||||
|
||||
//decrypt password
|
||||
pt, err := cryptography.DecryptPassword([]byte(password))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//connect to game server
|
||||
cli := mcr.NewClient(server, mcr.WithPort(port))
|
||||
err = cli.Connect(string(pt))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{
|
||||
cli: cli,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// closes client connection
|
||||
func (c *Client) Close() error {
|
||||
return c.cli.Close()
|
||||
}
|
||||
|
||||
// sends command to server, only exists to prevent exposing cli field
|
||||
func (c *Client) Command(cmd string) (string, error) {
|
||||
return c.cli.Command(cmd)
|
||||
}
|
@ -4,27 +4,27 @@ Copyright © 2025 Jake jake.young.dev@gmail.com
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"code.jakeyoungdev.com/jake/mctl/cryptography"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
server string
|
||||
port int
|
||||
cfgserver string
|
||||
cfgport int
|
||||
)
|
||||
|
||||
// configCmd represents the config command
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Create and populate config file",
|
||||
Use: "config",
|
||||
Example: "mctl config -s x.x.x.x -p 61695",
|
||||
Short: "Create and populate config file",
|
||||
Long: `Creates the .mctl file in the user home directory
|
||||
populating it with the server address, rcon password, and
|
||||
rcon port to be pulled when using Login command`,
|
||||
@ -34,25 +34,22 @@ var configCmd = &cobra.Command{
|
||||
ps, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
cobra.CheckErr(err)
|
||||
|
||||
//setup aes encrypter
|
||||
block, err := aes.NewCipher([]byte(viper.Get("device").(string)))
|
||||
cobra.CheckErr(err)
|
||||
//generate and apply random nonce
|
||||
nonce := make([]byte, 12)
|
||||
_, err = io.ReadFull(rand.Reader, nonce)
|
||||
cobra.CheckErr(err)
|
||||
aesg, err := cipher.NewGCM(block)
|
||||
viper.Set("nonce", string(nonce))
|
||||
|
||||
//encrypt password
|
||||
ciphert, err := cryptography.EncryptPassword(ps)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
//encrypt rcon password
|
||||
ciphert := aesg.Seal(nil, nonce, ps, nil)
|
||||
|
||||
//update config file with new values
|
||||
viper.Set("server", server)
|
||||
viper.Set("server", cfgserver)
|
||||
viper.Set("password", string(ciphert))
|
||||
viper.Set("port", port)
|
||||
viper.Set("nonce", string(nonce))
|
||||
viper.WriteConfig()
|
||||
viper.Set("port", cfgport)
|
||||
err = viper.WriteConfig()
|
||||
cobra.CheckErr(err)
|
||||
fmt.Println()
|
||||
fmt.Println("Config file updated!")
|
||||
},
|
||||
@ -60,13 +57,16 @@ var configCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
initConfig()
|
||||
configCmd.Flags().StringVarP(&server, "server", "s", "", "server address")
|
||||
configCmd.MarkFlagRequired("server")
|
||||
configCmd.Flags().IntVarP(&port, "port", "p", 0, "server rcon port")
|
||||
configCmd.MarkFlagRequired("port")
|
||||
configCmd.Flags().StringVarP(&cfgserver, "server", "s", "", "server address")
|
||||
err := configCmd.MarkFlagRequired("server")
|
||||
cobra.CheckErr(err)
|
||||
configCmd.Flags().IntVarP(&cfgport, "port", "p", 0, "server rcon port")
|
||||
err = configCmd.MarkFlagRequired("port")
|
||||
cobra.CheckErr(err)
|
||||
rootCmd.AddCommand(configCmd)
|
||||
}
|
||||
|
||||
// init config sets viper config and checks for config file, creating it if it doesn't exist
|
||||
func initConfig() {
|
||||
home, err := os.UserHomeDir()
|
||||
cobra.CheckErr(err)
|
||||
@ -74,15 +74,15 @@ func initConfig() {
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetConfigName(".mctl")
|
||||
|
||||
viper.AutomaticEnv()
|
||||
viper.ReadInConfig()
|
||||
err = viper.ReadInConfig()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
//file does not exist, create it
|
||||
viper.Set("server", server)
|
||||
viper.Set("server", cfgserver)
|
||||
viper.Set("password", "")
|
||||
viper.Set("port", port)
|
||||
viper.Set("port", cfgport)
|
||||
viper.Set("nonce", "")
|
||||
|
||||
//generate psuedo-random key
|
||||
@ -90,7 +90,13 @@ func initConfig() {
|
||||
_, err := rand.Read(uu)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
//create custom command map
|
||||
cmdMap := make(map[string]any, 0)
|
||||
|
||||
//write config
|
||||
viper.Set("customcmd", cmdMap)
|
||||
viper.Set("device", string(uu))
|
||||
viper.SafeWriteConfig()
|
||||
err = viper.SafeWriteConfig()
|
||||
cobra.CheckErr(err)
|
||||
}
|
||||
}
|
||||
|
39
cmd/delete.go
Normal file
39
cmd/delete.go
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright © 2025 Jake jake.young.dev@gmail.com
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// deleteCmd represents the delete command
|
||||
var deleteCmd = &cobra.Command{
|
||||
Use: "delete <name>",
|
||||
Example: "mctl delete newcmd",
|
||||
Short: "Delete a saved command",
|
||||
Long: `Deletes a command stored using the save command`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if viper.IsSet("customcmd") {
|
||||
cmdMap := viper.Get("customcmd").(map[string]any)
|
||||
delete(cmdMap, args[0])
|
||||
viper.Set("customcmd", cmdMap)
|
||||
err := viper.WriteConfig()
|
||||
cobra.CheckErr(err)
|
||||
}
|
||||
},
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("name argument is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(deleteCmd)
|
||||
}
|
43
cmd/login.go
43
cmd/login.go
@ -5,45 +5,27 @@ package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jake-young-dev/mcr"
|
||||
"code.jakeyoungdev.com/jake/mctl/client"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// loginCmd represents the login command
|
||||
var loginCmd = &cobra.Command{
|
||||
Use: "login",
|
||||
Short: "Login to server and send commands",
|
||||
Use: "login",
|
||||
Example: "mctl login",
|
||||
SilenceUsage: true,
|
||||
Short: "Login to server and send commands",
|
||||
Long: `Login to server using saved config and enter command loop
|
||||
sending commands to server and printing the response.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
//grab saved credentials
|
||||
server := viper.Get("server")
|
||||
password := viper.Get("password")
|
||||
port := viper.Get("port")
|
||||
fmt.Printf("Logging into %s on port %d\n", server, port)
|
||||
|
||||
//setup decrypter
|
||||
nonce := viper.Get("nonce")
|
||||
block, err := aes.NewCipher([]byte(viper.Get("device").(string)))
|
||||
cobra.CheckErr(err)
|
||||
aesg, err := cipher.NewGCM(block)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
//decrypt password
|
||||
pwd := []byte(password.(string))
|
||||
nn := []byte(nonce.(string))
|
||||
pt, err := aesg.Open(nil, nn, pwd, nil)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
//connect to game server
|
||||
cli := mcr.NewClient(server.(string), mcr.WithPort(port.(int)))
|
||||
err = cli.Connect(string(pt))
|
||||
server := viper.Get("server").(string)
|
||||
cli, err := client.New()
|
||||
cobra.CheckErr(err)
|
||||
defer cli.Close()
|
||||
|
||||
@ -61,6 +43,7 @@ var loginCmd = &cobra.Command{
|
||||
continue
|
||||
}
|
||||
|
||||
//mctl exits command terminal
|
||||
if runningCmd == "mctl" {
|
||||
break
|
||||
}
|
||||
@ -73,6 +56,14 @@ var loginCmd = &cobra.Command{
|
||||
|
||||
fmt.Printf("Disconnected from %s\n", server)
|
||||
},
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
//ensure config command has been run
|
||||
if !viper.IsSet("server") || !viper.IsSet("password") || !viper.IsSet("port") {
|
||||
return errors.New("the 'config' command must be run before you can interact with servers")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -14,7 +14,7 @@ var rootCmd = &cobra.Command{
|
||||
Use: "mctl",
|
||||
Short: "A remote console client",
|
||||
Long: `mctl is a terminal-friendly remote console client made to manage game servers.`,
|
||||
Version: "v0.1.1",
|
||||
Version: "v0.3.4",
|
||||
// Run: func(cmd *cobra.Command, args []string) { },
|
||||
}
|
||||
|
||||
@ -28,6 +28,5 @@ func Execute() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
//dont show completion subcommand in help message, makes the syntax confusing
|
||||
rootCmd.Root().CompletionOptions.DisableDefaultCmd = true
|
||||
|
||||
}
|
||||
|
82
cmd/run.go
Normal file
82
cmd/run.go
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright © 2025 Jake jake.young.dev@gmail.com
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.jakeyoungdev.com/jake/mctl/client"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// runCmd represents the run command
|
||||
var runCmd = &cobra.Command{
|
||||
Use: "run <name> args...",
|
||||
Example: "mctl run savedcmd 63 jake",
|
||||
SilenceUsage: true,
|
||||
Short: "Runs a previously saved command with supplied arguments on remote server",
|
||||
Long: `Loads a saved command, injects the supplied arguments into the command, and sends the command to the remove server
|
||||
printing the response`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cm := viper.Get("customcmd").(map[string]any)
|
||||
//is this an existing command
|
||||
cmdRun, ok := cm[args[0]]
|
||||
if !ok {
|
||||
fmt.Printf("command %s not found", args[0])
|
||||
return
|
||||
}
|
||||
|
||||
//convert arguments to interface
|
||||
var nargs []any
|
||||
for _, a := range args[1:] {
|
||||
nargs = append(nargs, a)
|
||||
}
|
||||
|
||||
//inject arguments
|
||||
fixed := fmt.Sprintf(cmdRun.(string), nargs...)
|
||||
fmt.Printf("Running saved command %s\n", fixed)
|
||||
|
||||
//create client and send command
|
||||
cli, err := client.New()
|
||||
cobra.CheckErr(err)
|
||||
defer cli.Close()
|
||||
|
||||
res, err := cli.Command(fixed)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
fmt.Println(res)
|
||||
},
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
//ensure config command has been run
|
||||
if !viper.IsSet("server") || !viper.IsSet("password") || !viper.IsSet("port") {
|
||||
return errors.New("the 'config' command must be run before you can interact with servers")
|
||||
}
|
||||
|
||||
if !viper.IsSet("customcmd") {
|
||||
return errors.New("no saved commands to run")
|
||||
}
|
||||
|
||||
//ensure we have a command name
|
||||
al := len(args)
|
||||
if al == 0 {
|
||||
return errors.New("name argument is required")
|
||||
}
|
||||
|
||||
cmdMap := viper.Get("customcmd").(map[string]any)
|
||||
count := strings.Count(cmdMap[args[0]].(string), "%s")
|
||||
|
||||
//make sure enough arguments are sent to fill command placeholders
|
||||
if al < count+1 {
|
||||
return fmt.Errorf("not enough arguments to populate command. Supplied: %d, Needed: %d", al-1, count)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(runCmd)
|
||||
}
|
56
cmd/save.go
Normal file
56
cmd/save.go
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright © 2025 Jake jake.young.dev@gmail.com
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// saveCmd represents the save command
|
||||
var saveCmd = &cobra.Command{
|
||||
Use: "save <name>",
|
||||
Example: "mctl save newcmd",
|
||||
Short: "Saves a server command under an alias for quick execution",
|
||||
Long: `Saves supplied command using alias <name> to allow the command to be executed using the run command. The %s placeholder can be
|
||||
used as a wildcard to be injected when running the command`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Use %s as a wildcard in your command\n", "%s") //this is ugly, have to use printf to stop issues with compiler
|
||||
fmt.Printf("Command: ")
|
||||
sc := bufio.NewScanner(os.Stdin)
|
||||
if sc.Scan() {
|
||||
txt := sc.Text()
|
||||
if txt != "" {
|
||||
var cmdMap map[string]any
|
||||
cm := viper.Get("customcmd")
|
||||
if cmdMap == nil {
|
||||
cmdMap = make(map[string]any, 0)
|
||||
} else {
|
||||
cmdMap = cm.(map[string]any)
|
||||
}
|
||||
cmdMap[args[0]] = txt
|
||||
viper.Set("customcmd", cmdMap)
|
||||
err := viper.WriteConfig()
|
||||
cobra.CheckErr(err)
|
||||
fmt.Println("\nSaved!")
|
||||
}
|
||||
}
|
||||
},
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("name argument is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(saveCmd)
|
||||
}
|
60
cmd/view.go
Normal file
60
cmd/view.go
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright © 2025 Jake jake.young.dev@gmail.com
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// viewCmd represents the view command
|
||||
var viewCmd = &cobra.Command{
|
||||
Use: "view <name>",
|
||||
Example: "mctl view test",
|
||||
Short: "View saved commands",
|
||||
Long: `Load command using the supplied name and displays it in the terminal, 'all' will list every saved command`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var cm map[string]any
|
||||
cmdMap := viper.Get("customcmd")
|
||||
if cmdMap == nil {
|
||||
fmt.Println("no custom commands found")
|
||||
return
|
||||
}
|
||||
|
||||
cm = cmdMap.(map[string]any)
|
||||
|
||||
if strings.EqualFold(args[0], "all") {
|
||||
//show all commands
|
||||
fmt.Println("\nCommands: ")
|
||||
for k, v := range cm {
|
||||
fmt.Printf("%s - %s\n", k, v)
|
||||
}
|
||||
fmt.Println()
|
||||
return
|
||||
}
|
||||
|
||||
custom, ok := cm[args[0]]
|
||||
if !ok {
|
||||
fmt.Println("command not found")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Command: %s\n", custom.(string))
|
||||
},
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return errors.New("name argument is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(viewCmd)
|
||||
}
|
52
cryptography/aes.go
Normal file
52
cryptography/aes.go
Normal file
@ -0,0 +1,52 @@
|
||||
package cryptography
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func EncryptPassword(b []byte) ([]byte, error) {
|
||||
nonce := viper.Get("nonce").(string)
|
||||
dev := viper.Get("device").(string)
|
||||
|
||||
block, err := aes.NewCipher([]byte(dev))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aesg, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//adding #nosec trigger here since gosec interprets this as a hardcoded nonce value. The nonce is calculated using crypto/rand when the
|
||||
//config command is ran and is pulled from memory when used any times after, for now we must prevent the scan from catching here until gosec
|
||||
//is updated to account for this properly
|
||||
ct := aesg.Seal(nil, []byte(nonce), []byte(b), nil) // #nosec
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
func DecryptPassword(b []byte) (string, error) {
|
||||
nonce := viper.Get("nonce").(string)
|
||||
password := viper.Get("password").(string)
|
||||
dev := viper.Get("device").(string)
|
||||
|
||||
block, err := aes.NewCipher([]byte(dev))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
aesg, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
op, err := aesg.Open(nil, []byte(nonce), []byte(password), nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(op), nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user