mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-09 01:49:40 -10:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f65f98bf65 | ||
|
|
cee2cc76c4 | ||
|
|
daa33b6301 | ||
|
|
ef52973536 |
No files matched your search
+10
-5
@@ -34,12 +34,19 @@ const (
|
||||
SliverClientDirName = ".sliver-client"
|
||||
|
||||
versionFileName = "version"
|
||||
envVarName = "SLIVER_CLIENT_ROOT_DIR"
|
||||
)
|
||||
|
||||
// GetRootAppDir - Get the Sliver app dir ~/.sliver-client/
|
||||
func GetRootAppDir() string {
|
||||
user, _ := user.Current()
|
||||
dir := filepath.Join(user.HomeDir, SliverClientDirName)
|
||||
value := os.Getenv(envVarName)
|
||||
var dir string
|
||||
if len(value) == 0 {
|
||||
user, _ := user.Current()
|
||||
dir = filepath.Join(user.HomeDir, SliverClientDirName)
|
||||
} else {
|
||||
dir = value
|
||||
}
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
@@ -116,7 +123,5 @@ under certain conditions; type 'licenses' for details.`)
|
||||
}
|
||||
saveAssetVersion(appDir)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(appDir, settingsFileName)); os.IsNotExist(err) {
|
||||
SaveSettings(nil)
|
||||
}
|
||||
_, _ = LoadSettings()
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func legacyBackupPath(legacyPath string) string {
|
||||
dir := filepath.Dir(legacyPath)
|
||||
base := filepath.Base(legacyPath)
|
||||
name := strings.TrimSuffix(base, filepath.Ext(base))
|
||||
return filepath.Join(dir, "."+name+".json-old")
|
||||
}
|
||||
|
||||
func renameLegacyConfig(legacyPath string) error {
|
||||
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
backupPath := legacyBackupPath(legacyPath)
|
||||
if _, err := os.Stat(backupPath); err == nil {
|
||||
if err := os.Remove(backupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.Rename(legacyPath, backupPath)
|
||||
}
|
||||
+39
-16
@@ -22,35 +22,58 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
settingsFileName = "tui-settings.json"
|
||||
settingsFileName = "tui-settings.yaml"
|
||||
settingsLegacyFileName = "tui-settings.json"
|
||||
)
|
||||
|
||||
// ClientSettings - Client JSON config
|
||||
type ClientSettings struct {
|
||||
TableStyle string `json:"tables"`
|
||||
AutoAdult bool `json:"autoadult"`
|
||||
BeaconAutoResults bool `json:"beacon_autoresults"`
|
||||
SmallTermWidth int `json:"small_term_width"`
|
||||
AlwaysOverflow bool `json:"always_overflow"`
|
||||
VimMode bool `json:"vim_mode"`
|
||||
UserConnect bool `json:"user_connect"`
|
||||
ConsoleLogs bool `json:"console_logs"`
|
||||
TableStyle string `json:"tables" yaml:"tables"`
|
||||
AutoAdult bool `json:"autoadult" yaml:"autoadult"`
|
||||
BeaconAutoResults bool `json:"beacon_autoresults" yaml:"beacon_autoresults"`
|
||||
SmallTermWidth int `json:"small_term_width" yaml:"small_term_width"`
|
||||
AlwaysOverflow bool `json:"always_overflow" yaml:"always_overflow"`
|
||||
VimMode bool `json:"vim_mode" yaml:"vim_mode"`
|
||||
UserConnect bool `json:"user_connect" yaml:"user_connect"`
|
||||
ConsoleLogs bool `json:"console_logs" yaml:"console_logs"`
|
||||
}
|
||||
|
||||
// LoadSettings - Load the client settings from disk
|
||||
func LoadSettings() (*ClientSettings, error) {
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
data, err := os.ReadFile(filepath.Join(rootDir, settingsFileName))
|
||||
if err != nil {
|
||||
settingsPath := filepath.Join(rootDir, settingsFileName)
|
||||
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
|
||||
settings := defaultSettings()
|
||||
migratedLegacy := false
|
||||
|
||||
data, err := os.ReadFile(settingsPath)
|
||||
if err == nil {
|
||||
if err = yaml.Unmarshal(data, settings); err != nil {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return defaultSettings(), err
|
||||
} else if data, err = os.ReadFile(legacyPath); err == nil {
|
||||
if err = json.Unmarshal(data, settings); err != nil {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
migratedLegacy = true
|
||||
} else if !os.IsNotExist(err) {
|
||||
return defaultSettings(), err
|
||||
}
|
||||
settings := defaultSettings()
|
||||
err = json.Unmarshal(data, settings)
|
||||
if err != nil {
|
||||
return defaultSettings(), err
|
||||
|
||||
if err := SaveSettings(settings); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
if migratedLegacy {
|
||||
if err := renameLegacyConfig(legacyPath); err != nil {
|
||||
return settings, err
|
||||
}
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
@@ -73,7 +96,7 @@ func SaveSettings(settings *ClientSettings) error {
|
||||
if settings == nil {
|
||||
settings = defaultSettings()
|
||||
}
|
||||
data, err := json.MarshalIndent(settings, "", " ")
|
||||
data, err := yaml.Marshal(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadSettingsParsesYAML(t *testing.T) {
|
||||
t.Setenv(envVarName, t.TempDir())
|
||||
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
settingsPath := filepath.Join(rootDir, settingsFileName)
|
||||
|
||||
data := []byte(`tables: "Compact"
|
||||
autoadult: true
|
||||
beacon_autoresults: false
|
||||
small_term_width: 120
|
||||
always_overflow: true
|
||||
vim_mode: true
|
||||
user_connect: true
|
||||
console_logs: false
|
||||
`)
|
||||
if err := os.WriteFile(settingsPath, data, 0o600); err != nil {
|
||||
t.Fatalf("failed to write yaml settings: %v", err)
|
||||
}
|
||||
|
||||
settings, err := LoadSettings()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if settings.TableStyle != "Compact" {
|
||||
t.Fatalf("expected TableStyle %q, got %q", "Compact", settings.TableStyle)
|
||||
}
|
||||
if !settings.AutoAdult {
|
||||
t.Fatalf("expected AutoAdult true")
|
||||
}
|
||||
if settings.BeaconAutoResults {
|
||||
t.Fatalf("expected BeaconAutoResults false")
|
||||
}
|
||||
if settings.SmallTermWidth != 120 {
|
||||
t.Fatalf("expected SmallTermWidth %d, got %d", 120, settings.SmallTermWidth)
|
||||
}
|
||||
if !settings.AlwaysOverflow {
|
||||
t.Fatalf("expected AlwaysOverflow true")
|
||||
}
|
||||
if !settings.VimMode {
|
||||
t.Fatalf("expected VimMode true")
|
||||
}
|
||||
if !settings.UserConnect {
|
||||
t.Fatalf("expected UserConnect true")
|
||||
}
|
||||
if settings.ConsoleLogs {
|
||||
t.Fatalf("expected ConsoleLogs false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSettingsMigratesLegacyJSON(t *testing.T) {
|
||||
t.Setenv(envVarName, t.TempDir())
|
||||
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
legacyPath := filepath.Join(rootDir, settingsLegacyFileName)
|
||||
|
||||
legacy := &ClientSettings{
|
||||
TableStyle: "Legacy",
|
||||
AutoAdult: true,
|
||||
BeaconAutoResults: false,
|
||||
SmallTermWidth: 200,
|
||||
AlwaysOverflow: true,
|
||||
VimMode: true,
|
||||
UserConnect: true,
|
||||
ConsoleLogs: false,
|
||||
}
|
||||
data, err := json.MarshalIndent(legacy, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal legacy settings: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(legacyPath, data, 0o600); err != nil {
|
||||
t.Fatalf("failed to write legacy settings: %v", err)
|
||||
}
|
||||
|
||||
settings, err := LoadSettings()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if settings.TableStyle != "Legacy" {
|
||||
t.Fatalf("expected TableStyle %q, got %q", "Legacy", settings.TableStyle)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||
t.Fatalf("expected yaml settings to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected legacy settings to be renamed: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
|
||||
t.Fatalf("expected legacy backup to exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSettingsWritesDefault(t *testing.T) {
|
||||
t.Setenv(envVarName, t.TempDir())
|
||||
|
||||
settings, err := LoadSettings()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if settings.TableStyle != "SliverDefault" {
|
||||
t.Fatalf("expected default TableStyle %q, got %q", "SliverDefault", settings.TableStyle)
|
||||
}
|
||||
rootDir, _ := filepath.Abs(GetRootAppDir())
|
||||
if _, err := os.Stat(filepath.Join(rootDir, settingsFileName)); err != nil {
|
||||
t.Fatalf("expected default settings to be written: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
@@ -117,7 +119,7 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
|
||||
}
|
||||
|
||||
if contentType == "" {
|
||||
contentType = sniffContentType(file)
|
||||
contentType = sniffContentType(file, contentPath)
|
||||
}
|
||||
|
||||
web.Contents[webpath] = &clientpb.WebContent{
|
||||
@@ -135,13 +137,19 @@ func confirmAddDirectory() bool {
|
||||
return confirm
|
||||
}
|
||||
|
||||
func sniffContentType(out *os.File) string {
|
||||
func sniffContentType(out *os.File, contentPath string) string {
|
||||
ext := strings.ToLower(filepath.Ext(contentPath))
|
||||
if ext != "" {
|
||||
contentType := mime.TypeByExtension(ext)
|
||||
if contentType != "" {
|
||||
return contentType
|
||||
}
|
||||
}
|
||||
out.Seek(0, io.SeekStart)
|
||||
buffer := make([]byte, fileSampleSize)
|
||||
_, err := out.Read(buffer)
|
||||
if err != nil {
|
||||
return defaultMimeType
|
||||
}
|
||||
contentType := http.DetectContentType(buffer)
|
||||
return contentType
|
||||
return http.DetectContentType(buffer)
|
||||
}
|
||||
@@ -136,6 +136,7 @@ var rootCmd = &cobra.Command{
|
||||
cryptography.AgeServerKeyPair()
|
||||
cryptography.MinisignServerPrivateKey()
|
||||
c2.SetupDefaultC2Profiles()
|
||||
_, _ = configs.LoadCrackConfig()
|
||||
|
||||
serverConfig := configs.GetServerConfig()
|
||||
listenerJobs, err := db.ListenerJobs()
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/c2"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/bishopfox/sliver/server/console"
|
||||
"github.com/bishopfox/sliver/server/cryptography"
|
||||
"github.com/bishopfox/sliver/server/daemon"
|
||||
@@ -63,6 +64,7 @@ var daemonCmd = &cobra.Command{
|
||||
certs.SetupWGKeys()
|
||||
cryptography.AgeServerKeyPair()
|
||||
cryptography.MinisignServerPrivateKey()
|
||||
_, _ = configs.LoadCrackConfig()
|
||||
|
||||
listenerJobs, err := db.ListenerJobs()
|
||||
if err != nil {
|
||||
|
||||
+83
-25
@@ -26,17 +26,19 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
crackConfigFileName = "crack.json"
|
||||
crackConfigFileName = "crack.yaml"
|
||||
crackLegacyConfigFileName = "crack.json"
|
||||
)
|
||||
|
||||
var (
|
||||
crackConfigLog = log.NamedLogger("config", "crack")
|
||||
)
|
||||
|
||||
// getCrackConfigPath - File path to config.json
|
||||
// getCrackConfigPath - File path to config.yaml
|
||||
func getCrackConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
confPath := filepath.Join(appDir, "configs", crackConfigFileName)
|
||||
@@ -44,6 +46,41 @@ func getCrackConfigPath() string {
|
||||
return confPath
|
||||
}
|
||||
|
||||
func getCrackLegacyConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
return filepath.Join(appDir, "configs", crackLegacyConfigFileName)
|
||||
}
|
||||
|
||||
type crackConfigYAML struct {
|
||||
AutoFire bool `yaml:"AutoFire"`
|
||||
MaxFileSize int64 `yaml:"MaxFileSize"`
|
||||
ChunkSize int64 `yaml:"ChunkSize"`
|
||||
MaxDiskUsage int64 `yaml:"MaxDiskUsage"`
|
||||
}
|
||||
|
||||
func crackConfigFromYAML(data []byte) (*clientpb.CrackConfig, error) {
|
||||
var raw crackConfigYAML
|
||||
if err := yaml.Unmarshal(data, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &clientpb.CrackConfig{
|
||||
AutoFire: raw.AutoFire,
|
||||
MaxFileSize: raw.MaxFileSize,
|
||||
ChunkSize: raw.ChunkSize,
|
||||
MaxDiskUsage: raw.MaxDiskUsage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func crackConfigToYAML(config *clientpb.CrackConfig) ([]byte, error) {
|
||||
raw := crackConfigYAML{
|
||||
AutoFire: config.AutoFire,
|
||||
MaxFileSize: config.MaxFileSize,
|
||||
ChunkSize: config.ChunkSize,
|
||||
MaxDiskUsage: config.MaxDiskUsage,
|
||||
}
|
||||
return yaml.Marshal(raw)
|
||||
}
|
||||
|
||||
// Save - Save config file to disk
|
||||
func SaveCrackConfig(config *clientpb.CrackConfig) error {
|
||||
configPath := getCrackConfigPath()
|
||||
@@ -55,7 +92,7 @@ func SaveCrackConfig(config *clientpb.CrackConfig) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
data, err := crackConfigToYAML(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -70,6 +107,7 @@ func SaveCrackConfig(config *clientpb.CrackConfig) error {
|
||||
// LoadCrackConfig - Get config value
|
||||
func LoadCrackConfig() (*clientpb.CrackConfig, error) {
|
||||
configPath := getCrackConfigPath()
|
||||
legacyPath := getCrackLegacyConfigPath()
|
||||
const defaultMaxFileSize = 10 * 1024 * 1024 * 1024 // 10GB - max size of any one file
|
||||
const defaultMaxDiskUsage = 5 * defaultMaxFileSize // 50GB - total disk usage for all files
|
||||
const maxChunkSize = 1024 * 1024 * 1024 // 1GB - max size of any one chunk
|
||||
@@ -80,15 +118,28 @@ func LoadCrackConfig() (*clientpb.CrackConfig, error) {
|
||||
ChunkSize: defaultChunkSize,
|
||||
MaxDiskUsage: defaultMaxDiskUsage,
|
||||
}
|
||||
migratedLegacy := false
|
||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
crackConfigLog.Errorf("Failed to read crack config file %s", err)
|
||||
return config, err
|
||||
}
|
||||
err = json.Unmarshal(data, config)
|
||||
parsed, err := crackConfigFromYAML(data)
|
||||
if err != nil {
|
||||
crackConfigLog.Errorf("Failed to parse crack config file %s", err)
|
||||
return config, err
|
||||
}
|
||||
config = parsed
|
||||
} else if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
data, err := os.ReadFile(legacyPath)
|
||||
if err != nil {
|
||||
crackConfigLog.Errorf("Failed to read legacy crack config file %s", err)
|
||||
return config, err
|
||||
}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
crackConfigLog.Errorf("Failed to parse legacy crack config file %s", err)
|
||||
return &clientpb.CrackConfig{
|
||||
AutoFire: true,
|
||||
MaxFileSize: defaultMaxFileSize,
|
||||
@@ -96,33 +147,40 @@ func LoadCrackConfig() (*clientpb.CrackConfig, error) {
|
||||
MaxDiskUsage: defaultMaxDiskUsage,
|
||||
}, err
|
||||
}
|
||||
if config.ChunkSize < 1024 {
|
||||
crackConfigLog.Warnf("Invalid chunk size %d, using default %d", config.ChunkSize, defaultChunkSize)
|
||||
config.ChunkSize = defaultChunkSize
|
||||
}
|
||||
if config.MaxFileSize < 1024 {
|
||||
crackConfigLog.Warnf("Invalid max file size %d, using default %d", config.MaxFileSize, defaultMaxFileSize)
|
||||
config.MaxFileSize = defaultMaxFileSize
|
||||
}
|
||||
if maxChunkSize < config.ChunkSize {
|
||||
crackConfigLog.Warnf("Chunk size is too large, setting to %d", maxChunkSize)
|
||||
config.ChunkSize = maxChunkSize
|
||||
}
|
||||
if config.MaxFileSize < config.ChunkSize {
|
||||
crackConfigLog.Warnf("Chunk size is larger than max file size, setting to %d", config.MaxFileSize)
|
||||
config.ChunkSize = config.MaxFileSize
|
||||
}
|
||||
if config.MaxDiskUsage < config.MaxFileSize {
|
||||
crackConfigLog.Warnf("Max disk usage is smaller than max file size, setting to %d", config.MaxFileSize)
|
||||
config.MaxDiskUsage = config.MaxFileSize
|
||||
}
|
||||
return config, nil
|
||||
migratedLegacy = true
|
||||
crackConfigLog.Infof("Migrating legacy config %s to %s", legacyPath, configPath)
|
||||
} else {
|
||||
crackConfigLog.Debugf("Crack config file does not exist, using defaults")
|
||||
}
|
||||
if config.ChunkSize < 1024 {
|
||||
crackConfigLog.Warnf("Invalid chunk size %d, using default %d", config.ChunkSize, defaultChunkSize)
|
||||
config.ChunkSize = defaultChunkSize
|
||||
}
|
||||
if config.MaxFileSize < 1024 {
|
||||
crackConfigLog.Warnf("Invalid max file size %d, using default %d", config.MaxFileSize, defaultMaxFileSize)
|
||||
config.MaxFileSize = defaultMaxFileSize
|
||||
}
|
||||
if maxChunkSize < config.ChunkSize {
|
||||
crackConfigLog.Warnf("Chunk size is too large, setting to %d", maxChunkSize)
|
||||
config.ChunkSize = maxChunkSize
|
||||
}
|
||||
if config.MaxFileSize < config.ChunkSize {
|
||||
crackConfigLog.Warnf("Chunk size is larger than max file size, setting to %d", config.MaxFileSize)
|
||||
config.ChunkSize = config.MaxFileSize
|
||||
}
|
||||
if config.MaxDiskUsage < config.MaxFileSize {
|
||||
crackConfigLog.Warnf("Max disk usage is smaller than max file size, setting to %d", config.MaxFileSize)
|
||||
config.MaxDiskUsage = config.MaxFileSize
|
||||
}
|
||||
err := SaveCrackConfig(config) // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
crackConfigLog.Errorf("Failed to save default config %s", err)
|
||||
return config, err
|
||||
}
|
||||
if migratedLegacy {
|
||||
if err := renameLegacyConfig(legacyPath); err != nil {
|
||||
crackConfigLog.Errorf("Failed to rename legacy config %s", err)
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
)
|
||||
|
||||
func TestCrackConfigParsesYAML(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configPath := getCrackConfigPath()
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`AutoFire: false
|
||||
MaxFileSize: 2048
|
||||
ChunkSize: 1024
|
||||
MaxDiskUsage: 4096
|
||||
`)
|
||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write yaml config: %v", err)
|
||||
}
|
||||
|
||||
config, err := LoadCrackConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if config.AutoFire {
|
||||
t.Fatalf("expected AutoFire false")
|
||||
}
|
||||
if config.MaxFileSize != 2048 {
|
||||
t.Fatalf("expected MaxFileSize %d, got %d", 2048, config.MaxFileSize)
|
||||
}
|
||||
if config.ChunkSize != 1024 {
|
||||
t.Fatalf("expected ChunkSize %d, got %d", 1024, config.ChunkSize)
|
||||
}
|
||||
if config.MaxDiskUsage != 4096 {
|
||||
t.Fatalf("expected MaxDiskUsage %d, got %d", 4096, config.MaxDiskUsage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrackConfigWritesDefault(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
config, err := LoadCrackConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if config == nil {
|
||||
t.Fatalf("expected default crack config")
|
||||
}
|
||||
if _, err := os.Stat(getCrackConfigPath()); err != nil {
|
||||
t.Fatalf("expected default config file to exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrackConfigMigratesLegacyJSON(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configDir := filepath.Dir(getCrackConfigPath())
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
legacyPath := filepath.Join(configDir, crackLegacyConfigFileName)
|
||||
|
||||
legacy := &clientpb.CrackConfig{
|
||||
AutoFire: false,
|
||||
MaxFileSize: 4096,
|
||||
ChunkSize: 2048,
|
||||
MaxDiskUsage: 8192,
|
||||
}
|
||||
data, err := json.MarshalIndent(legacy, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal legacy config: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(legacyPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write legacy config: %v", err)
|
||||
}
|
||||
|
||||
config, err := LoadCrackConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if config.MaxFileSize != 4096 {
|
||||
t.Fatalf("expected MaxFileSize %d, got %d", 4096, config.MaxFileSize)
|
||||
}
|
||||
if _, err := os.Stat(getCrackConfigPath()); err != nil {
|
||||
t.Fatalf("expected migrated yaml config file to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected legacy config to be renamed: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
|
||||
t.Fatalf("expected legacy backup file to exist: %v", err)
|
||||
}
|
||||
}
|
||||
+43
-15
@@ -30,6 +30,7 @@ import (
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,7 +41,8 @@ const (
|
||||
// MySQL - MySQL protocol
|
||||
MySQL = "mysql"
|
||||
|
||||
databaseConfigFileName = "database.json"
|
||||
databaseConfigFileName = "database.yaml"
|
||||
databaseLegacyConfigFileName = "database.json"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -57,7 +59,7 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// GetDatabaseConfigPath - File path to config.json
|
||||
// GetDatabaseConfigPath - File path to config.yaml
|
||||
func GetDatabaseConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
databaseConfigPath := filepath.Join(appDir, "configs", databaseConfigFileName)
|
||||
@@ -65,22 +67,27 @@ func GetDatabaseConfigPath() string {
|
||||
return databaseConfigPath
|
||||
}
|
||||
|
||||
func getDatabaseLegacyConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
return filepath.Join(appDir, "configs", databaseLegacyConfigFileName)
|
||||
}
|
||||
|
||||
// DatabaseConfig - Server config
|
||||
type DatabaseConfig struct {
|
||||
Dialect string `json:"dialect"`
|
||||
Database string `json:"database"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
Dialect string `json:"dialect" yaml:"dialect"`
|
||||
Database string `json:"database" yaml:"database"`
|
||||
Username string `json:"username" yaml:"username"`
|
||||
Password string `json:"password" yaml:"password"`
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port uint16 `json:"port" yaml:"port"`
|
||||
|
||||
Params map[string]string `json:"params"`
|
||||
Pragmas map[string]string `json:"pragmas"`
|
||||
Params map[string]string `json:"params" yaml:"params"`
|
||||
Pragmas map[string]string `json:"pragmas" yaml:"pragmas"`
|
||||
|
||||
MaxIdleConns int `json:"max_idle_conns"`
|
||||
MaxOpenConns int `json:"max_open_conns"`
|
||||
MaxIdleConns int `json:"max_idle_conns" yaml:"max_idle_conns"`
|
||||
MaxOpenConns int `json:"max_open_conns" yaml:"max_open_conns"`
|
||||
|
||||
LogLevel string `json:"log_level"`
|
||||
LogLevel string `json:"log_level" yaml:"log_level"`
|
||||
}
|
||||
|
||||
// DSN - Get the db connections string
|
||||
@@ -170,7 +177,7 @@ func (c *DatabaseConfig) Save() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
data, err := json.MarshalIndent(c, "", " ")
|
||||
data, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,18 +192,33 @@ func (c *DatabaseConfig) Save() error {
|
||||
// GetDatabaseConfig - Get config value
|
||||
func GetDatabaseConfig() *DatabaseConfig {
|
||||
configPath := GetDatabaseConfigPath()
|
||||
legacyPath := getDatabaseLegacyConfigPath()
|
||||
config := getDefaultDatabaseConfig()
|
||||
migratedLegacy := false
|
||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to read config file %s", err)
|
||||
return config
|
||||
}
|
||||
err = json.Unmarshal(data, config)
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to parse config file %s", err)
|
||||
return config
|
||||
}
|
||||
} else if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
data, err := os.ReadFile(legacyPath)
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to read legacy config file %s", err)
|
||||
return config
|
||||
}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to parse legacy config file %s", err)
|
||||
return config
|
||||
}
|
||||
migratedLegacy = true
|
||||
databaseConfigLog.Infof("Migrating legacy config %s to %s", legacyPath, configPath)
|
||||
} else {
|
||||
databaseConfigLog.Warnf("Config file does not exist, using defaults")
|
||||
}
|
||||
@@ -213,6 +235,12 @@ func GetDatabaseConfig() *DatabaseConfig {
|
||||
err := config.Save() // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to save default config %s", err)
|
||||
return config
|
||||
}
|
||||
if migratedLegacy {
|
||||
if err := renameLegacyConfig(legacyPath); err != nil {
|
||||
databaseConfigLog.Errorf("Failed to rename legacy config %s", err)
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDatabaseConfigParsesYAML(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configPath := GetDatabaseConfigPath()
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`dialect: mysql
|
||||
database: "sliver"
|
||||
username: "tester"
|
||||
password: "secret"
|
||||
host: "127.0.0.1"
|
||||
port: 3306
|
||||
params:
|
||||
charset: "utf8mb4"
|
||||
pragmas: {}
|
||||
max_idle_conns: 7
|
||||
max_open_conns: 15
|
||||
log_level: "info"
|
||||
`)
|
||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write yaml config: %v", err)
|
||||
}
|
||||
|
||||
config := GetDatabaseConfig()
|
||||
if config.Dialect != MySQL {
|
||||
t.Fatalf("expected dialect %q, got %q", MySQL, config.Dialect)
|
||||
}
|
||||
if config.Database != "sliver" {
|
||||
t.Fatalf("expected database %q, got %q", "sliver", config.Database)
|
||||
}
|
||||
if config.Username != "tester" {
|
||||
t.Fatalf("expected username %q, got %q", "tester", config.Username)
|
||||
}
|
||||
if config.Password != "secret" {
|
||||
t.Fatalf("expected password %q, got %q", "secret", config.Password)
|
||||
}
|
||||
if config.Host != "127.0.0.1" {
|
||||
t.Fatalf("expected host %q, got %q", "127.0.0.1", config.Host)
|
||||
}
|
||||
if config.Port != 3306 {
|
||||
t.Fatalf("expected port %d, got %d", 3306, config.Port)
|
||||
}
|
||||
if config.Params["charset"] != "utf8mb4" {
|
||||
t.Fatalf("expected charset param %q, got %q", "utf8mb4", config.Params["charset"])
|
||||
}
|
||||
if config.MaxIdleConns != 7 {
|
||||
t.Fatalf("expected max_idle_conns %d, got %d", 7, config.MaxIdleConns)
|
||||
}
|
||||
if config.MaxOpenConns != 15 {
|
||||
t.Fatalf("expected max_open_conns %d, got %d", 15, config.MaxOpenConns)
|
||||
}
|
||||
if config.LogLevel != "info" {
|
||||
t.Fatalf("expected log_level %q, got %q", "info", config.LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatabaseConfigWritesDefault(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
config := GetDatabaseConfig()
|
||||
if config.Dialect != Sqlite {
|
||||
t.Fatalf("expected default dialect %q, got %q", Sqlite, config.Dialect)
|
||||
}
|
||||
if _, err := os.Stat(GetDatabaseConfigPath()); err != nil {
|
||||
t.Fatalf("expected default config file to exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatabaseConfigMigratesLegacyJSON(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configDir := filepath.Dir(GetDatabaseConfigPath())
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
legacyPath := filepath.Join(configDir, databaseLegacyConfigFileName)
|
||||
|
||||
legacy := &DatabaseConfig{
|
||||
Dialect: Postgres,
|
||||
Database: "sliver",
|
||||
Username: "legacy",
|
||||
Password: "pass",
|
||||
Host: "db.internal",
|
||||
Port: 5432,
|
||||
Params: map[string]string{"sslmode": "disable"},
|
||||
Pragmas: map[string]string{},
|
||||
MaxIdleConns: 4,
|
||||
MaxOpenConns: 9,
|
||||
LogLevel: "debug",
|
||||
}
|
||||
data, err := json.MarshalIndent(legacy, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal legacy config: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(legacyPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write legacy config: %v", err)
|
||||
}
|
||||
|
||||
config := GetDatabaseConfig()
|
||||
if config.Dialect != Postgres {
|
||||
t.Fatalf("expected dialect %q, got %q", Postgres, config.Dialect)
|
||||
}
|
||||
if config.Database != "sliver" {
|
||||
t.Fatalf("expected database %q, got %q", "sliver", config.Database)
|
||||
}
|
||||
if config.Params["sslmode"] != "disable" {
|
||||
t.Fatalf("expected sslmode param %q, got %q", "disable", config.Params["sslmode"])
|
||||
}
|
||||
if _, err := os.Stat(GetDatabaseConfigPath()); err != nil {
|
||||
t.Fatalf("expected migrated yaml config file to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected legacy config to be renamed: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
|
||||
t.Fatalf("expected legacy backup file to exist: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func legacyBackupPath(legacyPath string) string {
|
||||
dir := filepath.Dir(legacyPath)
|
||||
base := filepath.Base(legacyPath)
|
||||
name := strings.TrimSuffix(base, filepath.Ext(base))
|
||||
return filepath.Join(dir, "."+name+".json-old")
|
||||
}
|
||||
|
||||
func renameLegacyConfig(legacyPath string) error {
|
||||
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
backupPath := legacyBackupPath(legacyPath)
|
||||
if _, err := os.Stat(backupPath); err == nil {
|
||||
if err := os.Remove(backupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.Rename(legacyPath, backupPath)
|
||||
}
|
||||
+130
-62
@@ -24,20 +24,21 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
serverConfigFileName = "server.json"
|
||||
serverConfigFileName = "server.yaml"
|
||||
serverLegacyConfigFileName = "server.json"
|
||||
)
|
||||
|
||||
var (
|
||||
serverConfigLog = log.NamedLogger("config", "server")
|
||||
)
|
||||
|
||||
// GetServerConfigPath - File path to config.json
|
||||
// GetServerConfigPath - File path to config.yaml
|
||||
func GetServerConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
serverConfigPath := filepath.Join(appDir, "configs", serverConfigFileName)
|
||||
@@ -45,104 +46,150 @@ func GetServerConfigPath() string {
|
||||
return serverConfigPath
|
||||
}
|
||||
|
||||
func getServerLegacyConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
return filepath.Join(appDir, "configs", serverLegacyConfigFileName)
|
||||
}
|
||||
|
||||
// LogConfig - Server logging config
|
||||
type LogConfig struct {
|
||||
Level int `json:"level"`
|
||||
GRPCUnaryPayloads bool `json:"grpc_unary_payloads"`
|
||||
GRPCStreamPayloads bool `json:"grpc_stream_payloads"`
|
||||
TLSKeyLogger bool `json:"tls_key_logger"`
|
||||
Level int `json:"level" yaml:"level"`
|
||||
GRPCUnaryPayloads bool `json:"grpc_unary_payloads" yaml:"grpc_unary_payloads"`
|
||||
GRPCStreamPayloads bool `json:"grpc_stream_payloads" yaml:"grpc_stream_payloads"`
|
||||
TLSKeyLogger bool `json:"tls_key_logger" yaml:"tls_key_logger"`
|
||||
}
|
||||
|
||||
// DaemonConfig - Configure daemon mode
|
||||
type DaemonConfig struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Tailscale bool `json:"tailscale"`
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port int `json:"port" yaml:"port"`
|
||||
Tailscale bool `json:"tailscale" yaml:"tailscale"`
|
||||
}
|
||||
|
||||
// JobConfig - Restart Jobs on Load
|
||||
type JobConfig struct {
|
||||
Multiplayer []*MultiplayerJobConfig `json:"multiplayer"`
|
||||
MTLS []*MTLSJobConfig `json:"mtls,omitempty"`
|
||||
WG []*WGJobConfig `json:"wg,omitempty"`
|
||||
DNS []*DNSJobConfig `json:"dns,omitempty"`
|
||||
HTTP []*HTTPJobConfig `json:"http,omitempty"`
|
||||
Multiplayer []*MultiplayerJobConfig `json:"multiplayer" yaml:"multiplayer"`
|
||||
MTLS []*MTLSJobConfig `json:"mtls,omitempty" yaml:"mtls,omitempty"`
|
||||
WG []*WGJobConfig `json:"wg,omitempty" yaml:"wg,omitempty"`
|
||||
DNS []*DNSJobConfig `json:"dns,omitempty" yaml:"dns,omitempty"`
|
||||
HTTP []*HTTPJobConfig `json:"http,omitempty" yaml:"http,omitempty"`
|
||||
}
|
||||
|
||||
type MultiplayerJobConfig struct {
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
JobID string `json:"job_id"`
|
||||
Tailscale bool `json:"tailscale"`
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port uint16 `json:"port" yaml:"port"`
|
||||
JobID string `json:"job_id" yaml:"job_id"`
|
||||
Tailscale bool `json:"tailscale" yaml:"tailscale"`
|
||||
}
|
||||
|
||||
// MTLSJobConfig - Per-type job configs
|
||||
type MTLSJobConfig struct {
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
JobID string `json:"job_id"`
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port uint16 `json:"port" yaml:"port"`
|
||||
JobID string `json:"job_id" yaml:"job_id"`
|
||||
}
|
||||
|
||||
// WGJobConfig - Per-type job configs
|
||||
type WGJobConfig struct {
|
||||
Port uint16 `json:"port"`
|
||||
NPort uint16 `json:"nport"`
|
||||
KeyPort uint16 `json:"key_port"`
|
||||
JobID string `json:"job_id"`
|
||||
Port uint16 `json:"port" yaml:"port"`
|
||||
NPort uint16 `json:"nport" yaml:"nport"`
|
||||
KeyPort uint16 `json:"key_port" yaml:"key_port"`
|
||||
JobID string `json:"job_id" yaml:"job_id"`
|
||||
}
|
||||
|
||||
// DNSJobConfig - Persistent DNS job config
|
||||
type DNSJobConfig struct {
|
||||
Domains []string `json:"domains"`
|
||||
Canaries bool `json:"canaries"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
JobID string `json:"job_id"`
|
||||
EnforceOTP bool `json:"enforce_otp"`
|
||||
Domains []string `json:"domains" yaml:"domains"`
|
||||
Canaries bool `json:"canaries" yaml:"canaries"`
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port uint16 `json:"port" yaml:"port"`
|
||||
JobID string `json:"job_id" yaml:"job_id"`
|
||||
EnforceOTP bool `json:"enforce_otp" yaml:"enforce_otp"`
|
||||
}
|
||||
|
||||
// HTTPJobConfig - Persistent HTTP job config
|
||||
type HTTPJobConfig struct {
|
||||
Domain string `json:"domain"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
Secure bool `json:"secure"`
|
||||
Website string `json:"website"`
|
||||
Cert []byte `json:"cert"`
|
||||
Key []byte `json:"key"`
|
||||
ACME bool `json:"acme"`
|
||||
JobID string `json:"job_id"`
|
||||
EnforceOTP bool `json:"enforce_otp"`
|
||||
LongPollTimeout int64 `json:"long_poll_timeout"`
|
||||
LongPollJitter int64 `json:"long_poll_jitter"`
|
||||
RandomizeJARM bool `json:"randomize_jarm"`
|
||||
Domain string `json:"domain" yaml:"domain"`
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port uint16 `json:"port" yaml:"port"`
|
||||
Secure bool `json:"secure" yaml:"secure"`
|
||||
Website string `json:"website" yaml:"website"`
|
||||
Cert []byte `json:"cert" yaml:"cert"`
|
||||
Key []byte `json:"key" yaml:"key"`
|
||||
ACME bool `json:"acme" yaml:"acme"`
|
||||
JobID string `json:"job_id" yaml:"job_id"`
|
||||
EnforceOTP bool `json:"enforce_otp" yaml:"enforce_otp"`
|
||||
LongPollTimeout int64 `json:"long_poll_timeout" yaml:"long_poll_timeout"`
|
||||
LongPollJitter int64 `json:"long_poll_jitter" yaml:"long_poll_jitter"`
|
||||
RandomizeJARM bool `json:"randomize_jarm" yaml:"randomize_jarm"`
|
||||
}
|
||||
|
||||
// WatchTowerConfig - Watch Tower job config
|
||||
type WatchTowerConfig struct {
|
||||
VTApiKey string `json:"vt_api_key"`
|
||||
XForceApiKey string `json:"xforce_api_key"`
|
||||
XForceApiPassword string `json:"xforce_api_password"`
|
||||
VTApiKey string `json:"vt_api_key" yaml:"vt_api_key"`
|
||||
XForceApiKey string `json:"xforce_api_key" yaml:"xforce_api_key"`
|
||||
XForceApiPassword string `json:"xforce_api_password" yaml:"xforce_api_password"`
|
||||
}
|
||||
|
||||
// http server defaults for anonymous requests
|
||||
type HttpDefaultHeader struct {
|
||||
Method string `json:"method" yaml:"method"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Value string `json:"value" yaml:"value"`
|
||||
Probability int32 `json:"probability" yaml:"probability"`
|
||||
}
|
||||
|
||||
type httpDefaultHeaderLegacy struct {
|
||||
Method string `json:"method" yaml:"method"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Value string `json:"value" yaml:"value"`
|
||||
Probability int32 `json:"probability" yaml:"probability"`
|
||||
ID string `json:"id" yaml:"id"`
|
||||
HttpC2ServerConfigID *string `json:"httpc2serverconfigid" yaml:"httpc2serverconfigid"`
|
||||
HttpC2ImplantConfigID *string `json:"httpc2implantconfigid" yaml:"httpc2implantconfigid"`
|
||||
}
|
||||
|
||||
func (h *HttpDefaultHeader) UnmarshalJSON(data []byte) error {
|
||||
var raw httpDefaultHeaderLegacy
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return err
|
||||
}
|
||||
h.Method = raw.Method
|
||||
h.Name = raw.Name
|
||||
h.Value = raw.Value
|
||||
h.Probability = raw.Probability
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HttpDefaultHeader) UnmarshalYAML(node *yaml.Node) error {
|
||||
var raw httpDefaultHeaderLegacy
|
||||
if err := node.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
h.Method = raw.Method
|
||||
h.Name = raw.Name
|
||||
h.Value = raw.Value
|
||||
h.Probability = raw.Probability
|
||||
return nil
|
||||
}
|
||||
|
||||
type HttpDefaultConfig struct {
|
||||
Headers []models.HttpC2Header `json:"headers"`
|
||||
Headers []HttpDefaultHeader `json:"headers" yaml:"headers"`
|
||||
}
|
||||
|
||||
// ServerConfig - Server config
|
||||
type ServerConfig struct {
|
||||
DaemonMode bool `json:"daemon_mode"`
|
||||
DaemonConfig *DaemonConfig `json:"daemon"`
|
||||
Logs *LogConfig `json:"logs"`
|
||||
Watchtower *WatchTowerConfig `json:"watch_tower"`
|
||||
GoProxy string `json:"go_proxy"`
|
||||
HTTPDefaults *HttpDefaultConfig `json:"http_default"`
|
||||
DonutBypass int `json:"donut_bypass"` // 1=skip, 2=abort on fail, 3=continue on fail.
|
||||
DaemonMode bool `json:"daemon_mode" yaml:"daemon_mode"`
|
||||
DaemonConfig *DaemonConfig `json:"daemon" yaml:"daemon"`
|
||||
Logs *LogConfig `json:"logs" yaml:"logs"`
|
||||
Watchtower *WatchTowerConfig `json:"watch_tower" yaml:"watch_tower"`
|
||||
GoProxy string `json:"go_proxy" yaml:"go_proxy"`
|
||||
HTTPDefaults *HttpDefaultConfig `json:"http_default" yaml:"http_default"`
|
||||
DonutBypass int `json:"donut_bypass" yaml:"donut_bypass"` // 1=skip, 2=abort on fail, 3=continue on fail.
|
||||
|
||||
// 'GOOS/GOARCH' -> CC path
|
||||
CC map[string]string `json:"cc"`
|
||||
CXX map[string]string `json:"cxx"`
|
||||
CC map[string]string `json:"cc" yaml:"cc"`
|
||||
CXX map[string]string `json:"cxx" yaml:"cxx"`
|
||||
}
|
||||
|
||||
// Save - Save config file to disk
|
||||
@@ -156,7 +203,7 @@ func (c *ServerConfig) Save() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
data, err := json.MarshalIndent(c, "", " ")
|
||||
data, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -171,18 +218,33 @@ func (c *ServerConfig) Save() error {
|
||||
// GetServerConfig - Get config value
|
||||
func GetServerConfig() *ServerConfig {
|
||||
configPath := GetServerConfigPath()
|
||||
legacyPath := getServerLegacyConfigPath()
|
||||
config := getDefaultServerConfig()
|
||||
migratedLegacy := false
|
||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
serverConfigLog.Errorf("Failed to read config file %s", err)
|
||||
return config
|
||||
}
|
||||
err = json.Unmarshal(data, config)
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
serverConfigLog.Errorf("Failed to parse config file %s", err)
|
||||
return config
|
||||
}
|
||||
} else if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
data, err := os.ReadFile(legacyPath)
|
||||
if err != nil {
|
||||
serverConfigLog.Errorf("Failed to read legacy config file %s", err)
|
||||
return config
|
||||
}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
serverConfigLog.Errorf("Failed to parse legacy config file %s", err)
|
||||
return config
|
||||
}
|
||||
migratedLegacy = true
|
||||
serverConfigLog.Infof("Migrating legacy config %s to %s", legacyPath, configPath)
|
||||
} else {
|
||||
serverConfigLog.Warnf("Config file does not exist, using defaults")
|
||||
}
|
||||
@@ -202,6 +264,12 @@ func GetServerConfig() *ServerConfig {
|
||||
err := config.Save() // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
serverConfigLog.Errorf("Failed to save default config %s", err)
|
||||
return config
|
||||
}
|
||||
if migratedLegacy {
|
||||
if err := renameLegacyConfig(legacyPath); err != nil {
|
||||
serverConfigLog.Errorf("Failed to rename legacy config %s", err)
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
@@ -219,8 +287,8 @@ func getDefaultServerConfig() *ServerConfig {
|
||||
GRPCStreamPayloads: false,
|
||||
},
|
||||
HTTPDefaults: &HttpDefaultConfig{
|
||||
Headers: []models.HttpC2Header{
|
||||
models.HttpC2Header{
|
||||
Headers: []HttpDefaultHeader{
|
||||
{
|
||||
Method: "GET",
|
||||
Name: "Cache-Control",
|
||||
Value: "no-store, no-cache, must-revalidate",
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServerConfigParsesYAML(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configPath := GetServerConfigPath()
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`daemon_mode: true
|
||||
daemon:
|
||||
host: "127.0.0.1"
|
||||
port: 4444
|
||||
tailscale: true
|
||||
logs:
|
||||
level: 5
|
||||
grpc_unary_payloads: true
|
||||
grpc_stream_payloads: true
|
||||
tls_key_logger: true
|
||||
watch_tower:
|
||||
vt_api_key: "vt"
|
||||
xforce_api_key: "xforce"
|
||||
xforce_api_password: "secret"
|
||||
go_proxy: "https://proxy.example"
|
||||
http_default:
|
||||
headers:
|
||||
- method: "POST"
|
||||
name: "X-Test"
|
||||
value: "abc"
|
||||
probability: 50
|
||||
donut_bypass: 2
|
||||
cc:
|
||||
linux/amd64: "/usr/bin/cc"
|
||||
cxx:
|
||||
linux/amd64: "/usr/bin/c++"
|
||||
`)
|
||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write yaml config: %v", err)
|
||||
}
|
||||
|
||||
config := GetServerConfig()
|
||||
if !config.DaemonMode {
|
||||
t.Fatalf("expected daemon_mode true")
|
||||
}
|
||||
if config.DaemonConfig == nil || config.DaemonConfig.Port != 4444 {
|
||||
t.Fatalf("expected daemon port %d, got %v", 4444, config.DaemonConfig)
|
||||
}
|
||||
if config.Logs == nil || config.Logs.Level != 5 {
|
||||
t.Fatalf("expected logs level %d, got %v", 5, config.Logs)
|
||||
}
|
||||
if config.Logs == nil || !config.Logs.TLSKeyLogger {
|
||||
t.Fatalf("expected tls_key_logger true")
|
||||
}
|
||||
if config.Watchtower == nil || config.Watchtower.VTApiKey != "vt" {
|
||||
t.Fatalf("expected watch_tower vt_api_key %q, got %v", "vt", config.Watchtower)
|
||||
}
|
||||
if config.GoProxy != "https://proxy.example" {
|
||||
t.Fatalf("expected go_proxy %q, got %q", "https://proxy.example", config.GoProxy)
|
||||
}
|
||||
if config.HTTPDefaults == nil || len(config.HTTPDefaults.Headers) != 1 {
|
||||
t.Fatalf("expected 1 http default header, got %v", config.HTTPDefaults)
|
||||
}
|
||||
header := config.HTTPDefaults.Headers[0]
|
||||
if header.Method != "POST" || header.Name != "X-Test" || header.Value != "abc" || header.Probability != 50 {
|
||||
t.Fatalf("unexpected header values: %#v", header)
|
||||
}
|
||||
if config.DonutBypass != 2 {
|
||||
t.Fatalf("expected donut_bypass %d, got %d", 2, config.DonutBypass)
|
||||
}
|
||||
if config.CC["linux/amd64"] != "/usr/bin/cc" {
|
||||
t.Fatalf("expected cc override %q, got %q", "/usr/bin/cc", config.CC["linux/amd64"])
|
||||
}
|
||||
if config.CXX["linux/amd64"] != "/usr/bin/c++" {
|
||||
t.Fatalf("expected cxx override %q, got %q", "/usr/bin/c++", config.CXX["linux/amd64"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConfigWritesDefault(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
config := GetServerConfig()
|
||||
if config.DaemonConfig == nil {
|
||||
t.Fatalf("expected default daemon config")
|
||||
}
|
||||
if _, err := os.Stat(GetServerConfigPath()); err != nil {
|
||||
t.Fatalf("expected default config file to exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerConfigMigratesLegacyJSON(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configDir := filepath.Dir(GetServerConfigPath())
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
legacyPath := filepath.Join(configDir, serverLegacyConfigFileName)
|
||||
|
||||
type legacyHeader struct {
|
||||
ID string `json:"id"`
|
||||
HttpC2ServerConfigID *string `json:"httpc2serverconfigid"`
|
||||
HttpC2ImplantConfigID *string `json:"httpc2implantconfigid"`
|
||||
Method string `json:"method"`
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
Probability int32 `json:"probability"`
|
||||
}
|
||||
type legacyHTTPDefaults struct {
|
||||
Headers []legacyHeader `json:"headers"`
|
||||
}
|
||||
type legacyServerConfig struct {
|
||||
DaemonMode bool `json:"daemon_mode"`
|
||||
DaemonConfig *DaemonConfig `json:"daemon"`
|
||||
Logs *LogConfig `json:"logs"`
|
||||
Watchtower *WatchTowerConfig `json:"watch_tower"`
|
||||
GoProxy string `json:"go_proxy"`
|
||||
HTTPDefaults *legacyHTTPDefaults `json:"http_default"`
|
||||
DonutBypass int `json:"donut_bypass"`
|
||||
CC map[string]string `json:"cc"`
|
||||
CXX map[string]string `json:"cxx"`
|
||||
}
|
||||
legacy := &legacyServerConfig{
|
||||
DaemonMode: true,
|
||||
DaemonConfig: &DaemonConfig{
|
||||
Host: "127.0.0.1",
|
||||
Port: 31338,
|
||||
Tailscale: false,
|
||||
},
|
||||
Logs: &LogConfig{
|
||||
Level: 4,
|
||||
GRPCUnaryPayloads: true,
|
||||
GRPCStreamPayloads: true,
|
||||
TLSKeyLogger: true,
|
||||
},
|
||||
Watchtower: &WatchTowerConfig{
|
||||
VTApiKey: "vt-legacy",
|
||||
XForceApiKey: "xforce-legacy",
|
||||
XForceApiPassword: "secret",
|
||||
},
|
||||
GoProxy: "https://proxy.legacy",
|
||||
HTTPDefaults: &legacyHTTPDefaults{
|
||||
Headers: []legacyHeader{
|
||||
{
|
||||
ID: "00000000-0000-0000-0000-000000000000",
|
||||
Method: "GET",
|
||||
Name: "Cache-Control",
|
||||
Value: "no-cache",
|
||||
Probability: 42,
|
||||
},
|
||||
},
|
||||
},
|
||||
DonutBypass: 2,
|
||||
CC: map[string]string{"linux/amd64": "/usr/bin/cc"},
|
||||
CXX: map[string]string{"linux/amd64": "/usr/bin/c++"},
|
||||
}
|
||||
data, err := json.MarshalIndent(legacy, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal legacy config: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(legacyPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write legacy config: %v", err)
|
||||
}
|
||||
|
||||
config := GetServerConfig()
|
||||
if !config.DaemonMode || config.DaemonConfig == nil || config.DaemonConfig.Port != 31338 {
|
||||
t.Fatalf("expected legacy daemon config to load")
|
||||
}
|
||||
if config.Watchtower == nil || config.Watchtower.VTApiKey != "vt-legacy" {
|
||||
t.Fatalf("expected legacy watch_tower to load")
|
||||
}
|
||||
if config.GoProxy != "https://proxy.legacy" {
|
||||
t.Fatalf("expected legacy go_proxy %q, got %q", "https://proxy.legacy", config.GoProxy)
|
||||
}
|
||||
if config.HTTPDefaults == nil || len(config.HTTPDefaults.Headers) != 1 {
|
||||
t.Fatalf("expected legacy http defaults to load")
|
||||
}
|
||||
if _, err := os.Stat(GetServerConfigPath()); err != nil {
|
||||
t.Fatalf("expected migrated yaml config file to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected legacy config to be renamed: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
|
||||
t.Fatalf("expected legacy backup file to exist: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user