3 Commits

Author SHA1 Message Date
f8282c3676 new/clear-command (#8)
Reviewed-on: #8
Co-authored-by: jake <jake.young.dev@gmail.com>
Co-committed-by: jake <jake.young.dev@gmail.com>
2025-05-11 04:27:47 +00:00
386a766185 Update .gitea/workflows/security.yaml (#7)
All checks were successful
code scans / scans (push) Successful in 1m15s
Reviewed-on: #7
2025-05-10 05:37:49 +00:00
26c50085d6 new/pipeline (#6)
All checks were successful
code scans / scans (push) Successful in 1m27s
Reviewed-on: #6
Co-authored-by: jake <jake.young.dev@gmail.com>
Co-committed-by: jake <jake.young.dev@gmail.com>
2025-04-24 18:22:16 +00:00
9 changed files with 96 additions and 13 deletions

View File

@@ -0,0 +1,11 @@
name: "code scans"
on: pull_request
jobs:
scans:
runs-on: test
steps:
- uses: actions/checkout@v4
- name: "dependency scan and static code analysis"
uses: https://code.jakeyoungdev.com/actions/donotpassgo@v1.0.0

View File

@@ -1,12 +1,18 @@
# mctl # mctl
mctl is a terminal-friendly remote console client mctl is a terminal-friendly remote console client
## Index
1. [Installation](#installation)
2. [Setup](#setup)
3. [Documentation](#documentation)
4. [Security](#security)
5. [Development](#development)
## Installation ## Installation
Install mctl using golang Install mctl using golang
```bash ```bash
go install code.jakeyoungdev.com/jake/mctl@main #it is recommended to use a tagged version go install code.jakeyoungdev.com/jake/mctl@main #it is recommended to use a tagged version
``` ```
<br />
## Setup ## Setup
### Configuring mctl ### Configuring mctl
@@ -75,6 +81,13 @@ Commands can be deleted with:
mctl delete <name> mctl delete <name>
``` ```
### Clear configuration file
To clear all fields from the configuration file use:
```bash
#CAUTION: If the config file is cleared all data previously saved will be lost forever
mctl clear
```
## Documentation ## Documentation
### Commands ### Commands
|Command|Description| |Command|Description|
@@ -85,6 +98,7 @@ mctl delete <name>
|view \<name>|displays saved command| |view \<name>|displays saved command|
|delete \<name>|deletes saved command| |delete \<name>|deletes saved command|
|run \<name> args...|runs saved command filling placeholders with supplied args| |run \<name> args...|runs saved command filling placeholders with supplied args|
|clear|clears config file|
### Flags ### Flags
#### config #### config
@@ -97,7 +111,9 @@ mctl delete <name>
All configuration data will be kept in the home directory and any sensitive data is encrypted for added security All configuration data will be kept in the home directory and any sensitive data is encrypted for added security
## 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 ## Development
this repo is currently in heavy development and may encounter breaking changes, use a tag to prevent any surprises this repo is currently in development and may encounter breaking changes, use a tag to prevent any surprises

43
cmd/clear.go Normal file
View File

@@ -0,0 +1,43 @@
/*
Copyright © 2025 Jake jake.young.dev@gmail.com
*/
package cmd
import (
"fmt"
"os"
"code.jakeyoungdev.com/jake/mctl/models"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// clearCmd represents the clear command
var clearCmd = &cobra.Command{
Use: "clear",
Short: "Clear config file",
Long: `Clears all configuration values for mctl, all server configuration will be lost`,
Run: func(cmd *cobra.Command, args []string) {
home, err := os.UserHomeDir()
cobra.CheckErr(err)
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".mctl")
err = viper.ReadInConfig()
if err == nil {
//clear values if file exists
for _, v := range models.ConfigFields {
viper.Set(v, "")
}
err := viper.WriteConfig()
cobra.CheckErr(err)
fmt.Println("Config file cleared, use 'config' command to re-populate it")
}
},
}
func init() {
rootCmd.AddCommand(clearCmd)
}

View File

@@ -48,7 +48,8 @@ var configCmd = &cobra.Command{
viper.Set("server", cfgserver) viper.Set("server", cfgserver)
viper.Set("password", string(ciphert)) viper.Set("password", string(ciphert))
viper.Set("port", cfgport) viper.Set("port", cfgport)
viper.WriteConfig() err = viper.WriteConfig()
cobra.CheckErr(err)
fmt.Println() fmt.Println()
fmt.Println("Config file updated!") fmt.Println("Config file updated!")
}, },
@@ -57,9 +58,11 @@ var configCmd = &cobra.Command{
func init() { func init() {
initConfig() initConfig()
configCmd.Flags().StringVarP(&cfgserver, "server", "s", "", "server address") configCmd.Flags().StringVarP(&cfgserver, "server", "s", "", "server address")
configCmd.MarkFlagRequired("server") err := configCmd.MarkFlagRequired("server")
cobra.CheckErr(err)
configCmd.Flags().IntVarP(&cfgport, "port", "p", 0, "server rcon port") configCmd.Flags().IntVarP(&cfgport, "port", "p", 0, "server rcon port")
configCmd.MarkFlagRequired("port") err = configCmd.MarkFlagRequired("port")
cobra.CheckErr(err)
rootCmd.AddCommand(configCmd) rootCmd.AddCommand(configCmd)
} }
@@ -72,9 +75,9 @@ func initConfig() {
viper.SetConfigType("yaml") viper.SetConfigType("yaml")
viper.SetConfigName(".mctl") viper.SetConfigName(".mctl")
viper.AutomaticEnv() viper.AutomaticEnv()
viper.ReadInConfig() err = viper.ReadInConfig()
if err := viper.ReadInConfig(); err != nil { if err != nil {
//file does not exist, create it //file does not exist, create it
viper.Set("server", cfgserver) viper.Set("server", cfgserver)
viper.Set("password", "") viper.Set("password", "")
@@ -92,6 +95,7 @@ func initConfig() {
//write config //write config
viper.Set("customcmd", cmdMap) viper.Set("customcmd", cmdMap)
viper.Set("device", string(uu)) viper.Set("device", string(uu))
viper.SafeWriteConfig() err = viper.SafeWriteConfig()
cobra.CheckErr(err)
} }
} }

View File

@@ -21,7 +21,8 @@ var deleteCmd = &cobra.Command{
cmdMap := viper.Get("customcmd").(map[string]any) cmdMap := viper.Get("customcmd").(map[string]any)
delete(cmdMap, args[0]) delete(cmdMap, args[0])
viper.Set("customcmd", cmdMap) viper.Set("customcmd", cmdMap)
viper.WriteConfig() err := viper.WriteConfig()
cobra.CheckErr(err)
} }
}, },
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {

View File

@@ -36,7 +36,8 @@ var saveCmd = &cobra.Command{
} }
cmdMap[args[0]] = txt cmdMap[args[0]] = txt
viper.Set("customcmd", cmdMap) viper.Set("customcmd", cmdMap)
viper.WriteConfig() err := viper.WriteConfig()
cobra.CheckErr(err)
fmt.Println("\nSaved!") fmt.Println("\nSaved!")
} }
} }

View File

@@ -21,7 +21,10 @@ func EncryptPassword(b []byte) ([]byte, error) {
return nil, err return nil, err
} }
ct := aesg.Seal(nil, []byte(nonce), []byte(b), nil) //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 return ct, nil
} }

2
go.mod
View File

@@ -1,6 +1,6 @@
module code.jakeyoungdev.com/jake/mctl module code.jakeyoungdev.com/jake/mctl
go 1.24.0 go 1.24.2
require ( require (
github.com/jake-young-dev/mcr v1.3.1 github.com/jake-young-dev/mcr v1.3.1

4
models/data.go Normal file
View File

@@ -0,0 +1,4 @@
package models
//list of all fields kept in config file
var ConfigFields = [6]string{"customcmd", "device", "nonce", "port", "server", "password"}