mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
54
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebaf07ff1f | ||
|
|
fc95d3e354 | ||
|
|
88dca9337b | ||
|
|
44d9f8d5aa | ||
|
|
f76e9e367d | ||
|
|
69fa4143f6 | ||
|
|
dcbda0c64e | ||
|
|
3db48408af | ||
|
|
3c8edb7294 | ||
|
|
2e2206e0ae | ||
|
|
a43648a24a | ||
|
|
f65f98bf65 | ||
|
|
26d14e15ad | ||
|
|
cee2cc76c4 | ||
|
|
daa33b6301 | ||
|
|
ef52973536 | ||
|
|
6a7bd0803a | ||
|
|
6c70c1c1e6 | ||
|
|
ddc222b8a2 | ||
|
|
5506a582ac | ||
|
|
63d872c98b | ||
|
|
ef9f5dde0d | ||
|
|
e35b7fdb97 | ||
|
|
efbbf382eb | ||
|
|
0b8d00900d | ||
|
|
9077596f25 | ||
|
|
0a8104a32e | ||
|
|
15a0ba4c1e | ||
|
|
d452c8a676 | ||
|
|
fb74b491fa | ||
|
|
2af6e11adc | ||
|
|
a3a98858fd | ||
|
|
d0652f2fd3 | ||
|
|
936f067363 | ||
|
|
041c3c7048 | ||
|
|
b48f74af58 | ||
|
|
6ec8849b20 | ||
|
|
db7cf1ed62 | ||
|
|
16e1e77068 | ||
|
|
1cfa694aa2 | ||
|
|
5898877136 | ||
|
|
a7d8cac565 | ||
|
|
0e73afca47 | ||
|
|
432941d694 | ||
|
|
ac521bf53c | ||
|
|
9720c9f14f | ||
|
|
4a224fdb9c | ||
|
|
671eb4d777 | ||
|
|
16cf816610 | ||
|
|
879f576c6e | ||
|
|
2df78cdbf5 | ||
|
|
a05ffc0ae1 | ||
|
|
6b4b218f09 | ||
|
|
4d0d46afd6 |
No files matched your search
@@ -29,11 +29,14 @@ MIN_SUPPORTED_GO_MAJOR_VERSION = 1
|
||||
MIN_SUPPORTED_GO_MINOR_VERSION = 24
|
||||
GO_VERSION_VALIDATION_ERR_MSG = Golang version is not supported, please update to at least $(MIN_SUPPORTED_GO_MAJOR_VERSION).$(MIN_SUPPORTED_GO_MINOR_VERSION)
|
||||
|
||||
SLIVER_PUBLIC_KEY ?= RWTZPg959v3b7tLG7VzKHRB1/QT+d3c71Uzetfa44qAoX5rH7mGoQTTR
|
||||
ARMORY_PUBLIC_KEY ?= RWSBpxpRWDrD7Fe+VvRE3c2VEDC2NK80rlNCj+BX0gz44Xw07r6KQD9L
|
||||
ARMORY_REPO_URL ?= https://api.github.com/repos/sliverarmory/armory/releases
|
||||
CLIENT_ASSETS_PKG = github.com/bishopfox/sliver/client/assets
|
||||
SLIVER_UPDATE_PKG = github.com/bishopfox/sliver/client/command/update
|
||||
|
||||
LDFLAGS = -ldflags "-s -w \
|
||||
-X $(SLIVER_UPDATE_PKG).SliverPublicKey=$(SLIVER_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryPublicKey=$(ARMORY_PUBLIC_KEY) \
|
||||
-X $(CLIENT_ASSETS_PKG).DefaultArmoryRepoURL=$(ARMORY_REPO_URL)"
|
||||
|
||||
|
||||
@@ -39,10 +39,6 @@ Please checkout the [wiki](https://sliver.sh/), or start a [GitHub discussion](h
|
||||
|
||||
See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
|
||||
### Feedback
|
||||
|
||||
Please take a moment and fill out [our survey](https://forms.gle/SwVsHFNh24ChG58C6).
|
||||
|
||||
### License - GPLv3
|
||||
|
||||
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
|
||||
+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)
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
killFlag, _ := cmd.Flags().GetString("kill")
|
||||
killAll, _ := cmd.Flags().GetBool("kill-all")
|
||||
interact, _ := cmd.Flags().GetString("interact")
|
||||
|
||||
// Handle kill
|
||||
if killFlag != "" {
|
||||
@@ -72,6 +73,18 @@ func BeaconsCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintInfof("Killed %s (%s)\n", beacon.Name, beacon.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if interact != "" {
|
||||
beacon, err := GetBeacon(con, interact)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
con.ActiveTarget.Set(nil, beacon)
|
||||
con.PrintInfof("Active beacon %s (%s)\n", beacon.Name, strings.Split(beacon.ID, "-")[0])
|
||||
return
|
||||
}
|
||||
|
||||
filter, _ := cmd.Flags().GetString("filter")
|
||||
var filterRegex *regexp.Regexp
|
||||
if filterRe, _ := cmd.Flags().GetString("filter-re"); filterRe != "" {
|
||||
|
||||
@@ -33,6 +33,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("beacons", false, beaconsCmd, func(f *pflag.FlagSet) {
|
||||
f.StringP("kill", "k", "", "kill the designated beacon")
|
||||
f.BoolP("kill-all", "K", false, "kill all beacons")
|
||||
f.StringP("interact", "i", "", "interact with a beacon")
|
||||
f.BoolP("force", "F", false, "force killing the beacon")
|
||||
|
||||
f.StringP("filter", "f", "", "filter beacons by substring")
|
||||
@@ -40,7 +41,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
})
|
||||
flags.BindFlagCompletions(beaconsCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["kill"] = BeaconIDCompleter(con)
|
||||
(*comp)["interact"] = BeaconIDCompleter(con)
|
||||
})
|
||||
registerBeaconIDFlagCompletion(beaconsCmd, "interact", con)
|
||||
beaconsRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove a beacon",
|
||||
@@ -81,22 +84,74 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
// BeaconIDCompleter completes beacon IDs
|
||||
func BeaconIDCompleter(con *console.SliverClient) carapace.Action {
|
||||
callback := func(_ carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
for _, b := range beacons.Beacons {
|
||||
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, b.ID[:8])
|
||||
results = append(results, desc)
|
||||
}
|
||||
}
|
||||
return carapace.ActionValuesDescribed(results...).Tag("beacons")
|
||||
return carapace.ActionValuesDescribed(beaconCompletionPairs(con)...).Tag("beacons")
|
||||
}
|
||||
|
||||
return carapace.ActionCallback(callback)
|
||||
}
|
||||
|
||||
func registerBeaconIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
values := describedValuesToTabs(beaconCompletionPairs(con))
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func beaconCompletionPairs(con *console.SliverClient) []string {
|
||||
results := make([]string, 0)
|
||||
if con == nil || con.Rpc == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
for _, b := range beacons.Beacons {
|
||||
link := fmt.Sprintf("[%s <- %s]", b.ActiveC2, b.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", b.Name, b.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", b.Username, b.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, b.ID[:8], desc)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func describedValuesToTabs(values []string) []string {
|
||||
tabbed := make([]string, 0, len(values)/2)
|
||||
for i := 0; i+1 < len(values); i += 2 {
|
||||
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
|
||||
}
|
||||
return tabbed
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
@@ -6,5 +6,7 @@ Implements the 'certificates' command group for the Sliver client console.
|
||||
|
||||
## Go Files
|
||||
|
||||
- `authorities.go` – Retrieves certificate authority metadata from the server and prints it in a table.
|
||||
- `completions.go` – Provides tab completion helpers for certificate common names.
|
||||
- `certificates.go` – Retrieves certificate metadata from the server, formats expiration status, and prints results to the console.
|
||||
- `commands.go` – Registers the certificates command and binds the flags needed to query certificate inventories.
|
||||
@@ -0,0 +1,90 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func CertificateAuthoritiesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
authorityInfo, err := con.Rpc.GetCertificateAuthorityInfo(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
con.PrintErrorf("could not get certificate authority information from database: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
printCertificateAuthorityInfo(con, authorityInfo.Info)
|
||||
}
|
||||
|
||||
func printCertificateAuthorityInfo(con *console.SliverClient, authData []*clientpb.CertificateAuthorityData) {
|
||||
width, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
width = 999
|
||||
}
|
||||
|
||||
if len(authData) == 0 {
|
||||
con.PrintWarnf("There are no certificate authorities in the database.\n")
|
||||
return
|
||||
}
|
||||
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
wideTermWidth := con.Settings.SmallTermWidth < width
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Common Name",
|
||||
"Creation Time",
|
||||
"Authority Type",
|
||||
"Key Algorithm",
|
||||
"Validity Start",
|
||||
"Expires",
|
||||
})
|
||||
} else {
|
||||
tw.AppendHeader(table.Row{
|
||||
"ID",
|
||||
"Common Name",
|
||||
"Expires",
|
||||
})
|
||||
}
|
||||
|
||||
for _, authority := range authData {
|
||||
rowColor := console.Normal
|
||||
|
||||
expiry, err := time.Parse(timeFormat, authority.ValidityExpiry)
|
||||
if err == nil {
|
||||
rowColor = checkCertExpiry(expiry)
|
||||
}
|
||||
|
||||
if wideTermWidth {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CreationTime),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.Type),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.KeyAlgorithm),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityStart),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
||||
})
|
||||
} else {
|
||||
tw.AppendRow(table.Row{
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ID),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.CN),
|
||||
fmt.Sprintf(rowColor+"%s"+console.Normal, authority.ValidityExpiry),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
tw.SortBy([]table.SortBy{{Name: "Expires", Mode: table.Dsc}})
|
||||
|
||||
con.Printf("%s\n", tw.Render())
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
@@ -44,6 +45,19 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.BoolP("server", "s", false, "Show server certificates")
|
||||
f.StringP("cn", "c", "", "Show certificate information for a provided common name")
|
||||
})
|
||||
flags.BindFlagCompletions(certificatesCmd, func(comp *carapace.ActionMap) {
|
||||
(*comp)["cn"] = CertificateCommonNameCompleter(con)
|
||||
})
|
||||
registerCertificateCNFlagCompletion(certificatesCmd, "cn", con)
|
||||
|
||||
authoritiesCmd := &cobra.Command{
|
||||
Use: "authorities",
|
||||
Short: "List certificate authorities",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
CertificateAuthoritiesCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
certificatesCmd.AddCommand(authoritiesCmd)
|
||||
|
||||
return []*cobra.Command{
|
||||
certificatesCmd,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package certificates
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CertificateCommonNameCompleter completes common names for stored certificates.
|
||||
func CertificateCommonNameCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return carapace.ActionMessage("not connected")
|
||||
}
|
||||
|
||||
values, err := certificateCommonNameValues(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("failed to list certificates %s", err)
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return carapace.ActionMessage("no certificates")
|
||||
}
|
||||
|
||||
return carapace.ActionValues(values...).Tag("common name").Usage("certificate common name")
|
||||
})
|
||||
}
|
||||
|
||||
func registerCertificateCNFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
values, err := certificateCommonNameValues(con)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func certificateCommonNameValues(con *console.SliverClient) ([]string, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, nil
|
||||
}
|
||||
certInfo, err := con.Rpc.GetCertificateInfo(context.Background(), &clientpb.CertificatesReq{
|
||||
CategoryFilters: AllTransports | AllRoles,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unique := map[string]struct{}{}
|
||||
for _, cert := range certInfo.Info {
|
||||
if cert.CN == "" {
|
||||
continue
|
||||
}
|
||||
unique[cert.CN] = struct{}{}
|
||||
}
|
||||
|
||||
values := make([]string, 0, len(unique))
|
||||
for name := range unique {
|
||||
values = append(values, name)
|
||||
}
|
||||
sort.Strings(values)
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Sliver Name",
|
||||
"Implant Name",
|
||||
"Domain",
|
||||
"Triggered",
|
||||
"First Trigger",
|
||||
|
||||
@@ -357,6 +357,7 @@ func coreImplantFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
(*comp)["traffic-encoders"] = TrafficEncodersCompleter(con).UniqueList(",")
|
||||
(*comp)["c2profile"] = HTTPC2Completer(con)
|
||||
})
|
||||
registerImplantTargetFlagCompletions(cmd, con)
|
||||
}
|
||||
|
||||
// coreBeaconFlags binds all flags specific to beacon implants (profiles or compiled).
|
||||
|
||||
@@ -22,7 +22,8 @@ var (
|
||||
// GenerateBeaconCmd - The main command used to generate implant binaries
|
||||
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunGenerateBeaconForm(cmd, con, args) {
|
||||
result, err := forms.GenerateBeaconForm()
|
||||
compiler, _ := compilerTargets(con)
|
||||
result, err := forms.GenerateBeaconForm(compiler)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
|
||||
@@ -91,7 +91,8 @@ var (
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunGenerateForm(cmd, con, args) {
|
||||
result, err := forms.GenerateForm()
|
||||
compiler, _ := compilerTargets(con)
|
||||
result, err := forms.GenerateForm(compiler)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
@@ -118,14 +119,15 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
} else {
|
||||
_, err := externalBuild(name, config, save, con)
|
||||
if err != nil {
|
||||
if err == ErrNoExternalBuilder {
|
||||
switch err {
|
||||
case ErrNoExternalBuilder:
|
||||
con.PrintErrorf("There are no external builders currently connected to the server\n")
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else if err == ErrNoValidBuilders {
|
||||
case ErrNoValidBuilders:
|
||||
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
|
||||
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", config.Format, config.GOOS, config.GOARCH))
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else {
|
||||
default:
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -3,11 +3,13 @@ package generate
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// GetSliverBinary - Get the binary of an implant based on it's profile.
|
||||
@@ -51,71 +53,112 @@ func GetSliverBinary(profile *clientpb.ImplantProfile, con *console.SliverClient
|
||||
return data, err
|
||||
}
|
||||
|
||||
func compilerTargets(con *console.SliverClient) (*clientpb.Compiler, error) {
|
||||
if con == nil || con.Rpc == nil {
|
||||
return nil, fmt.Errorf("no compiler info")
|
||||
}
|
||||
return con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
|
||||
}
|
||||
|
||||
func appendUnique(values []string, value string) []string {
|
||||
for _, existing := range values {
|
||||
if existing == value {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return append(values, value)
|
||||
}
|
||||
|
||||
func compilerArchValues(compiler *clientpb.Compiler) []string {
|
||||
results := []string{}
|
||||
|
||||
for _, target := range compiler.Targets {
|
||||
results = appendUnique(results, target.GOARCH)
|
||||
}
|
||||
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
results = appendUnique(results, target.GOARCH)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func compilerOSValues(compiler *clientpb.Compiler) []string {
|
||||
results := []string{}
|
||||
|
||||
for _, target := range compiler.Targets {
|
||||
results = appendUnique(results, target.GOOS)
|
||||
}
|
||||
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
results = appendUnique(results, target.GOOS)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func filterByPrefix(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
if strings.HasPrefix(value, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
func registerTargetFlagCompletion(cmd *cobra.Command, name string, valuesFn func(*clientpb.Compiler) []string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
compiler, err := compilerTargets(con)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
values := valuesFn(compiler)
|
||||
return filterByPrefix(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func registerImplantTargetFlagCompletions(cmd *cobra.Command, con *console.SliverClient) {
|
||||
registerTargetFlagCompletion(cmd, "os", compilerOSValues, con)
|
||||
registerTargetFlagCompletion(cmd, "arch", compilerArchValues, con)
|
||||
}
|
||||
|
||||
// FormatCompleter completes builds' architectures.
|
||||
func ArchCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
|
||||
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
|
||||
compiler, err := compilerTargets(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("No compiler info: %s", err.Error())
|
||||
}
|
||||
|
||||
var results []string
|
||||
|
||||
nextTarget:
|
||||
for _, target := range compiler.Targets {
|
||||
for _, res := range results {
|
||||
if res == target.GOARCH {
|
||||
continue nextTarget
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOARCH)
|
||||
}
|
||||
|
||||
nextUnsupported:
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
for _, res := range results {
|
||||
if res == target.GOARCH {
|
||||
continue nextUnsupported
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOARCH)
|
||||
}
|
||||
|
||||
return carapace.ActionValues(results...).Tag("architectures")
|
||||
return carapace.ActionValues(compilerArchValues(compiler)...).Tag("architectures")
|
||||
})
|
||||
}
|
||||
|
||||
// FormatCompleter completes build operating systems.
|
||||
func OSCompleter(con *console.SliverClient) carapace.Action {
|
||||
return carapace.ActionCallback(func(_ carapace.Context) carapace.Action {
|
||||
compiler, err := con.Rpc.GetCompiler(context.Background(), &commonpb.Empty{})
|
||||
compiler, err := compilerTargets(con)
|
||||
if err != nil {
|
||||
return carapace.ActionMessage("No compiler info: %s", err.Error())
|
||||
}
|
||||
|
||||
var results []string
|
||||
|
||||
nextTarget:
|
||||
for _, target := range compiler.Targets {
|
||||
for _, res := range results {
|
||||
if res == target.GOOS {
|
||||
continue nextTarget
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOOS)
|
||||
}
|
||||
|
||||
nextUnsupported:
|
||||
for _, target := range compiler.UnsupportedTargets {
|
||||
for _, res := range results {
|
||||
if res == target.GOOS {
|
||||
continue nextUnsupported
|
||||
}
|
||||
}
|
||||
results = append(results, target.GOOS)
|
||||
}
|
||||
|
||||
return carapace.ActionValues(results...).Tag("operating systems")
|
||||
return carapace.ActionValues(compilerOSValues(compiler)...).Tag("operating systems")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
|
||||
config.Format,
|
||||
strings.Join(c2URLs, "\n"),
|
||||
fmt.Sprintf("%v", config.Debug),
|
||||
fmt.Sprintf(config.HTTPC2ConfigName),
|
||||
fmt.Sprintf("%s", config.HTTPC2ConfigName),
|
||||
fmt.Sprintf("%v", builds.ResourceIDs[sliverName].Value),
|
||||
fmt.Sprintf("%v", builds.Staged[sliverName]),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
slivermcp "github.com/bishopfox/sliver/client/mcp"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -26,6 +27,12 @@ func McpStartCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
ServerVersion: version,
|
||||
}.WithDefaults()
|
||||
|
||||
msg := `Do you know what prompt injection is and are you an adult?`
|
||||
if !settings.IsUserAnAdultWithPrompt(con, msg) {
|
||||
con.PrintErrorf("Failed to start MCP server, the user is not qualified to use feature\n")
|
||||
return
|
||||
}
|
||||
|
||||
if err := slivermcp.Start(cfg, con.Rpc); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
|
||||
@@ -43,6 +43,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
(*comp)["interact"] = SessionIDCompleter(con)
|
||||
(*comp)["kill"] = SessionIDCompleter(con)
|
||||
})
|
||||
registerSessionIDFlagCompletion(sessionsCmd, "interact", con)
|
||||
|
||||
sessionsPruneCmd := &cobra.Command{
|
||||
Use: consts.PruneStr,
|
||||
@@ -63,26 +64,78 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
// SessionIDCompleter completes session IDs.
|
||||
func SessionIDCompleter(con *console.SliverClient) carapace.Action {
|
||||
callback := func(_ carapace.Context) carapace.Action {
|
||||
results := make([]string, 0)
|
||||
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
for _, s := range sessions.Sessions {
|
||||
link := fmt.Sprintf("[%s <- %s]", s.ActiveC2, s.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", s.Name, s.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", s.Username, s.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, s.ID[:8])
|
||||
results = append(results, desc)
|
||||
}
|
||||
}
|
||||
return carapace.ActionValuesDescribed(results...).Tag("sessions")
|
||||
return carapace.ActionValuesDescribed(sessionCompletionPairs(con)...).Tag("sessions")
|
||||
}
|
||||
|
||||
return carapace.ActionCallback(callback)
|
||||
}
|
||||
|
||||
func registerSessionIDFlagCompletion(cmd *cobra.Command, name string, con *console.SliverClient) {
|
||||
if cmd == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := cmd.GetFlagCompletionFunc(name); ok {
|
||||
return
|
||||
}
|
||||
if cmd.Flags().Lookup(name) == nil && cmd.PersistentFlags().Lookup(name) == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.RegisterFlagCompletionFunc(name, func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
values := describedValuesToTabs(sessionCompletionPairs(con))
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
})
|
||||
}
|
||||
|
||||
func sessionCompletionPairs(con *console.SliverClient) []string {
|
||||
results := make([]string, 0)
|
||||
if con == nil || con.Rpc == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return results
|
||||
}
|
||||
|
||||
for _, s := range sessions.Sessions {
|
||||
link := fmt.Sprintf("[%s <- %s]", s.ActiveC2, s.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", s.Name, s.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", s.Username, s.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, s.ID[:8], desc)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func describedValuesToTabs(values []string) []string {
|
||||
tabbed := make([]string, 0, len(values)/2)
|
||||
for i := 0; i+1 < len(values); i += 2 {
|
||||
tabbed = append(tabbed, fmt.Sprintf("%s\t%s", values[i], values[i+1]))
|
||||
}
|
||||
return tabbed
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
// SliverCommands returns all session control commands for the active target.
|
||||
func SliverCommands(con *console.SliverClient) []*cobra.Command {
|
||||
backgroundCmd := &cobra.Command{
|
||||
|
||||
@@ -41,11 +41,16 @@ func SettingsAutoAdultCmd(cmd *cobra.Command, con *console.SliverClient, args []
|
||||
|
||||
// IsUserAnAdult - This should be called for any dangerous (OPSEC-wise) functions.
|
||||
func IsUserAnAdult(con *console.SliverClient) bool {
|
||||
return IsUserAnAdultWithPrompt(con, "This action is bad OPSEC, are you an adult?")
|
||||
}
|
||||
|
||||
// IsUserAnAdultWithPrompt prompts with a custom message before continuing.
|
||||
func IsUserAnAdultWithPrompt(con *console.SliverClient, prompt string) bool {
|
||||
if GetAutoAdult(con) {
|
||||
return true
|
||||
}
|
||||
confirm := false
|
||||
_ = forms.Confirm("This action is bad OPSEC, are you an adult?", &confirm)
|
||||
_ = forms.Confirm(prompt, &confirm)
|
||||
return confirm
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,9 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
flags.Bind("update", false, updateCmd, func(f *pflag.FlagSet) {
|
||||
f.BoolP("prereleases", "P", false, "include pre-released (unstable) versions")
|
||||
f.StringP("proxy", "p", "", "specify a proxy url (e.g. http://localhost:8080)")
|
||||
f.StringP("save", "s", "", "save downloaded files to specific directory (default user home dir)")
|
||||
f.StringP("save", "s", "", "save downloaded files to specific directory (default downloads dir)")
|
||||
f.BoolP("insecure", "I", false, "skip tls certificate validation")
|
||||
f.Bool("force", false, "skip version comparison and download the latest release")
|
||||
f.IntP("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
flags.BindFlagCompletions(updateCmd, func(comp *carapace.ActionMap) {
|
||||
|
||||
+408
-36
@@ -21,6 +21,7 @@ package update
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -40,8 +41,19 @@ import (
|
||||
"github.com/bishopfox/sliver/client/version"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/minisign"
|
||||
"github.com/cheggaaa/pb/v3"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
SliverPublicKey string
|
||||
)
|
||||
|
||||
const (
|
||||
downloadAttempts = 3
|
||||
downloadBackoff = 750 * time.Millisecond
|
||||
)
|
||||
|
||||
// UpdateCmd - Check for updates.
|
||||
@@ -94,7 +106,13 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
|
||||
con.Printf("\nChecking for updates ... ")
|
||||
prereleases, _ := cmd.Flags().GetBool("prereleases")
|
||||
release, err := version.CheckForUpdates(client, prereleases)
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
var release *version.Release
|
||||
if force {
|
||||
release, err = version.LatestRelease(client, prereleases)
|
||||
} else {
|
||||
release, err = version.CheckForUpdates(client, prereleases)
|
||||
}
|
||||
con.Printf("done!\n\n")
|
||||
if err != nil {
|
||||
con.PrintErrorf("Update check failed %s\n", err)
|
||||
@@ -107,9 +125,13 @@ func UpdateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
updateAvailable(con, client, release, saveTo)
|
||||
updateAvailable(con, client, release, saveTo, force)
|
||||
} else {
|
||||
con.PrintInfof("No new releases.\n")
|
||||
if force {
|
||||
con.PrintInfof("No releases found.\n")
|
||||
} else {
|
||||
con.PrintInfof("No new releases.\n")
|
||||
}
|
||||
}
|
||||
now := time.Now()
|
||||
lastCheck := fmt.Appendf(nil, "%d", now.Unix())
|
||||
@@ -191,84 +213,434 @@ func findAssetFor(prefix string, suffixes []string, assets []version.Asset) *ver
|
||||
}
|
||||
|
||||
func serverAssetForGOOS(assets []version.Asset) *version.Asset {
|
||||
suffixes := []string{fmt.Sprintf("_%s.zip", runtime.GOOS), runtime.GOOS}
|
||||
if runtime.GOOS == "darwin" {
|
||||
suffixes = []string{"_macos.zip", "_macos"}
|
||||
if runtime.GOARCH == "arm64" {
|
||||
suffixes = []string{"_macos-arm64.zip", "_macos-arm64"}
|
||||
}
|
||||
}
|
||||
suffixes := assetSuffixes(runtime.GOOS, runtime.GOARCH)
|
||||
prefix := "sliver-server"
|
||||
return findAssetFor(prefix, suffixes, assets)
|
||||
}
|
||||
|
||||
func clientAssetForGOOS(assets []version.Asset) *version.Asset {
|
||||
suffixes := []string{fmt.Sprintf("_%s.zip", runtime.GOOS), runtime.GOOS}
|
||||
if runtime.GOOS == "darwin" {
|
||||
suffixes = []string{"_macos.zip", "_macos"}
|
||||
if runtime.GOARCH == "arm64" {
|
||||
suffixes = []string{"_macos-arm64.zip", "_macos-arm64"}
|
||||
}
|
||||
}
|
||||
suffixes := assetSuffixes(runtime.GOOS, runtime.GOARCH)
|
||||
prefix := "sliver-client"
|
||||
return findAssetFor(prefix, suffixes, assets)
|
||||
}
|
||||
|
||||
func updateAvailable(con *console.SliverClient, client *http.Client, release *version.Release, saveTo string) {
|
||||
func assetSuffixes(goos, goarch string) []string {
|
||||
suffixes := make([]string, 0, 8)
|
||||
seen := make(map[string]struct{}, 8)
|
||||
add := func(suffix string) {
|
||||
if suffix == "" {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[suffix]; ok {
|
||||
return
|
||||
}
|
||||
seen[suffix] = struct{}{}
|
||||
suffixes = append(suffixes, suffix)
|
||||
}
|
||||
|
||||
add(fmt.Sprintf("_%s-%s.zip", goos, goarch))
|
||||
add(fmt.Sprintf("_%s-%s", goos, goarch))
|
||||
add(fmt.Sprintf("_%s.zip", goos))
|
||||
add(fmt.Sprintf("_%s", goos))
|
||||
|
||||
if goos == "darwin" {
|
||||
add(fmt.Sprintf("_macos-%s.zip", goarch))
|
||||
add(fmt.Sprintf("_macos-%s", goarch))
|
||||
add("_macos.zip")
|
||||
add("_macos")
|
||||
}
|
||||
|
||||
return suffixes
|
||||
}
|
||||
|
||||
func updateAvailable(con *console.SliverClient, client *http.Client, release *version.Release, saveTo string, force bool) {
|
||||
serverAsset := serverAssetForGOOS(release.Assets)
|
||||
clientAsset := clientAssetForGOOS(release.Assets)
|
||||
|
||||
con.Printf("New version available %s\n", release.TagName)
|
||||
msg := "New version available"
|
||||
if force {
|
||||
msg = "Latest release available"
|
||||
}
|
||||
con.Printf("%s %s\n", msg, release.TagName)
|
||||
if serverAsset != nil {
|
||||
con.Printf(" - Server: %s\n", util.ByteCountBinary(int64(serverAsset.Size)))
|
||||
}
|
||||
if clientAsset != nil {
|
||||
con.Printf(" - Client: %s\n", util.ByteCountBinary(int64(clientAsset.Size)))
|
||||
}
|
||||
if serverAsset == nil {
|
||||
con.PrintWarnf("No server asset available for %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
if clientAsset == nil {
|
||||
con.PrintWarnf("No client asset available for %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
con.Println()
|
||||
|
||||
confirm := false
|
||||
forms.Confirm("Download update?", &confirm)
|
||||
if confirm {
|
||||
con.Printf("Please wait ...")
|
||||
err := downloadAsset(client, serverAsset, saveTo)
|
||||
if serverAsset == nil && clientAsset == nil {
|
||||
con.PrintWarnf("No matching assets found for %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
return
|
||||
}
|
||||
publicKey, err := loadSliverPublicKey()
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
err = downloadAsset(client, clientAsset, saveTo)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
downloads := []struct {
|
||||
label string
|
||||
asset *version.Asset
|
||||
}{
|
||||
{label: "server", asset: serverAsset},
|
||||
{label: "client", asset: clientAsset},
|
||||
}
|
||||
for _, item := range downloads {
|
||||
if item.asset == nil {
|
||||
continue
|
||||
}
|
||||
con.Printf("Downloading %s update from %s ...\n", item.label, item.asset.BrowserDownloadURL)
|
||||
if err := downloadAssetWithSignature(con, client, item.asset, release.Assets, saveTo, publicKey); err != nil {
|
||||
con.PrintErrorf("Failed to download %s update: %s\n", item.label, err)
|
||||
return
|
||||
}
|
||||
con.Println(console.Clearln)
|
||||
}
|
||||
con.Println(console.Clearln)
|
||||
con.PrintInfof("Saved updates to: %s\n", saveTo)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadAsset(client *http.Client, asset *version.Asset, saveTo string) error {
|
||||
func downloadAssetWithSignature(con *console.SliverClient, client *http.Client, asset *version.Asset, assets []version.Asset, saveTo string, publicKey minisign.PublicKey) error {
|
||||
if asset == nil {
|
||||
return errors.New("no asset available for this platform")
|
||||
}
|
||||
downloadURL, err := url.Parse(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assetFileName := filepath.Base(downloadURL.Path)
|
||||
finalPath := filepath.Join(saveTo, assetFileName)
|
||||
|
||||
limit := int64(asset.Size)
|
||||
writer, err := os.Create(filepath.Join(saveTo, assetFileName))
|
||||
sigURL := asset.BrowserDownloadURL + ".minisig"
|
||||
var sigLimit int64
|
||||
if sigAsset := signatureAssetFor(assetFileName, assets); sigAsset != nil && sigAsset.BrowserDownloadURL != "" {
|
||||
sigURL = sigAsset.BrowserDownloadURL
|
||||
sigLimit = int64(sigAsset.Size)
|
||||
}
|
||||
|
||||
assetTempPath, err := downloadWithRetries(client, asset.BrowserDownloadURL, saveTo, int64(asset.Size), "asset")
|
||||
if err != nil {
|
||||
return fmt.Errorf("download asset: %w", err)
|
||||
}
|
||||
|
||||
sigData, err := downloadBytesWithRetries(client, sigURL, sigLimit, "signature")
|
||||
if err != nil {
|
||||
cleanupAssetFiles(assetTempPath, finalPath)
|
||||
return fmt.Errorf("download signature: %w", err)
|
||||
}
|
||||
|
||||
if err := verifyMinisignSignature(assetTempPath, sigData, assetFileName, publicKey); err != nil {
|
||||
cleanupAssetFiles(assetTempPath, finalPath)
|
||||
return err
|
||||
}
|
||||
defer writer.Close()
|
||||
con.PrintSuccessf("Signature verified for %s\n", finalPath)
|
||||
|
||||
resp, err := client.Get(asset.BrowserDownloadURL)
|
||||
if err != nil {
|
||||
if err := replaceFile(assetTempPath, finalPath); err != nil {
|
||||
os.Remove(assetTempPath)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func signatureAssetFor(assetFileName string, assets []version.Asset) *version.Asset {
|
||||
want := assetFileName + ".minisig"
|
||||
for _, asset := range assets {
|
||||
if asset.Name == want {
|
||||
return &asset
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadSliverPublicKey() (minisign.PublicKey, error) {
|
||||
if strings.TrimSpace(SliverPublicKey) == "" {
|
||||
return minisign.PublicKey{}, errors.New("minisign public key not set at build time")
|
||||
}
|
||||
var publicKey minisign.PublicKey
|
||||
if err := publicKey.UnmarshalText([]byte(SliverPublicKey)); err != nil {
|
||||
return minisign.PublicKey{}, fmt.Errorf("invalid minisign public key: %w", err)
|
||||
}
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
type httpStatusError struct {
|
||||
url string
|
||||
status int
|
||||
}
|
||||
|
||||
func (e httpStatusError) Error() string {
|
||||
return fmt.Sprintf("unexpected HTTP status %d for %s", e.status, e.url)
|
||||
}
|
||||
|
||||
func downloadWithRetries(client *http.Client, downloadURL, saveTo string, limit int64, label string) (string, error) {
|
||||
var lastErr error
|
||||
backoff := downloadBackoff
|
||||
for attempt := 1; attempt <= downloadAttempts; attempt++ {
|
||||
path, err := downloadOnce(client, downloadURL, saveTo, limit, label)
|
||||
if err == nil {
|
||||
return path, nil
|
||||
}
|
||||
lastErr = err
|
||||
if !isRetryableDownloadError(err) || attempt == downloadAttempts {
|
||||
break
|
||||
}
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
return "", lastErr
|
||||
}
|
||||
|
||||
func downloadBytesWithRetries(client *http.Client, downloadURL string, limit int64, label string) ([]byte, error) {
|
||||
var lastErr error
|
||||
backoff := downloadBackoff
|
||||
for attempt := 1; attempt <= downloadAttempts; attempt++ {
|
||||
data, err := downloadBytesOnce(client, downloadURL, limit, label)
|
||||
if err == nil {
|
||||
return data, nil
|
||||
}
|
||||
lastErr = err
|
||||
if !isRetryableDownloadError(err) || attempt == downloadAttempts {
|
||||
break
|
||||
}
|
||||
time.Sleep(backoff)
|
||||
backoff *= 2
|
||||
}
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
func downloadOnce(client *http.Client, downloadURL, saveTo string, limit int64, label string) (string, error) {
|
||||
parsedURL, err := url.Parse(downloadURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseName := filepath.Base(parsedURL.Path)
|
||||
tmpFile, err := os.CreateTemp(saveTo, baseName+".tmp-*")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
success := false
|
||||
defer func() {
|
||||
if !success {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFile.Name())
|
||||
}
|
||||
}()
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, downloadURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bar := pb.Full.Start64(limit)
|
||||
barReader := bar.NewProxyReader(resp.Body)
|
||||
io.Copy(writer, barReader)
|
||||
bar.Finish()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", httpStatusError{url: downloadURL, status: resp.StatusCode}
|
||||
}
|
||||
|
||||
if limit <= 0 && resp.ContentLength > 0 {
|
||||
limit = resp.ContentLength
|
||||
}
|
||||
|
||||
var reader io.Reader = resp.Body
|
||||
var bar *pb.ProgressBar
|
||||
if limit > 0 {
|
||||
bar = pb.Full.Start64(limit)
|
||||
bar.Set("prefix", fmt.Sprintf("%s: ", label))
|
||||
bar.Set(pb.Bytes, true)
|
||||
bar.Set(pb.CleanOnFinish, true)
|
||||
bar.Set(pb.ReturnSymbol, "\r")
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
bar.Set(pb.Terminal, true)
|
||||
}
|
||||
reader = bar.NewProxyReader(reader)
|
||||
}
|
||||
|
||||
n, copyErr := io.Copy(tmpFile, reader)
|
||||
if bar != nil {
|
||||
bar.Finish()
|
||||
}
|
||||
if copyErr != nil {
|
||||
return "", copyErr
|
||||
}
|
||||
|
||||
if limit > 0 && n != limit {
|
||||
return "", fmt.Errorf("downloaded %d bytes, expected %d", n, limit)
|
||||
}
|
||||
|
||||
if err := tmpFile.Sync(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := tmpFile.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
success = true
|
||||
return tmpFile.Name(), nil
|
||||
}
|
||||
|
||||
func downloadBytesOnce(client *http.Client, downloadURL string, limit int64, label string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, downloadURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, httpStatusError{url: downloadURL, status: resp.StatusCode}
|
||||
}
|
||||
|
||||
if limit <= 0 && resp.ContentLength > 0 {
|
||||
limit = resp.ContentLength
|
||||
}
|
||||
|
||||
var reader io.Reader = resp.Body
|
||||
var bar *pb.ProgressBar
|
||||
if limit > 0 {
|
||||
bar = pb.Full.Start64(limit)
|
||||
bar.Set("prefix", fmt.Sprintf("%s: ", label))
|
||||
bar.Set(pb.Bytes, true)
|
||||
bar.Set(pb.CleanOnFinish, true)
|
||||
bar.Set(pb.ReturnSymbol, "\r")
|
||||
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
bar.Set(pb.Terminal, true)
|
||||
}
|
||||
reader = bar.NewProxyReader(reader)
|
||||
}
|
||||
|
||||
data, readErr := readAllWithLimit(reader, limit)
|
||||
if bar != nil {
|
||||
bar.Finish()
|
||||
}
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func isRetryableDownloadError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
return true
|
||||
}
|
||||
var netErr net.Error
|
||||
if errors.As(err, &netErr) {
|
||||
return netErr.Timeout() || netErr.Temporary()
|
||||
}
|
||||
var statusErr httpStatusError
|
||||
if errors.As(err, &statusErr) {
|
||||
switch statusErr.status {
|
||||
case http.StatusTooManyRequests, http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func verifyMinisignSignature(artifactPath string, sigData []byte, expectedFile string, publicKey minisign.PublicKey) error {
|
||||
artifact, err := os.Open(artifactPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open artifact: %w", err)
|
||||
}
|
||||
defer artifact.Close()
|
||||
|
||||
reader := minisign.NewReader(artifact)
|
||||
if _, err := io.Copy(io.Discard, reader); err != nil {
|
||||
return fmt.Errorf("stream artifact: %w", err)
|
||||
}
|
||||
|
||||
if !reader.Verify(publicKey, sigData) {
|
||||
return errors.New("signature verification failed")
|
||||
}
|
||||
|
||||
if err := verifyTrustedComment(sigData, expectedFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAllWithLimit(reader io.Reader, limit int64) ([]byte, error) {
|
||||
if limit <= 0 {
|
||||
return io.ReadAll(reader)
|
||||
}
|
||||
|
||||
lr := &io.LimitedReader{R: reader, N: limit + 1}
|
||||
data, err := io.ReadAll(lr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if int64(len(data)) > limit {
|
||||
return nil, fmt.Errorf("downloaded %d bytes, expected %d", len(data), limit)
|
||||
}
|
||||
if int64(len(data)) != limit {
|
||||
return nil, fmt.Errorf("downloaded %d bytes, expected %d", len(data), limit)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func verifyTrustedComment(signature []byte, expectedFile string) error {
|
||||
var sig minisign.Signature
|
||||
if err := sig.UnmarshalText(signature); err != nil {
|
||||
return fmt.Errorf("parse signature: %w", err)
|
||||
}
|
||||
|
||||
fileField := trustedCommentFile(sig.TrustedComment)
|
||||
if fileField == "" {
|
||||
return fmt.Errorf("trusted comment missing file field: %q", sig.TrustedComment)
|
||||
}
|
||||
if fileField != expectedFile {
|
||||
return fmt.Errorf("trusted comment file mismatch: expected %q, got %q", expectedFile, fileField)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func trustedCommentFile(trustedComment string) string {
|
||||
for _, field := range strings.Fields(trustedComment) {
|
||||
if after, ok := strings.CutPrefix(field, "file:"); ok {
|
||||
return after
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func replaceFile(src, dst string) error {
|
||||
if err := os.Remove(dst); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return os.Rename(src, dst)
|
||||
}
|
||||
|
||||
func cleanupAssetFiles(tempPath, finalPath string) {
|
||||
if tempPath != "" {
|
||||
_ = os.Remove(tempPath)
|
||||
}
|
||||
if finalPath != "" {
|
||||
_ = os.Remove(finalPath)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
carapace.Gen(useCmd).PositionalCompletion(BeaconAndSessionIDCompleter(con))
|
||||
registerUseIDCompletion(useCmd, con, true, true)
|
||||
|
||||
useSessionCmd := &cobra.Command{
|
||||
Use: consts.SessionsStr,
|
||||
@@ -37,6 +38,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
},
|
||||
}
|
||||
carapace.Gen(useSessionCmd).PositionalCompletion(SessionIDCompleter(con))
|
||||
registerUseIDCompletion(useSessionCmd, con, true, false)
|
||||
useCmd.AddCommand(useSessionCmd)
|
||||
|
||||
useBeaconCmd := &cobra.Command{
|
||||
@@ -48,6 +50,7 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
},
|
||||
}
|
||||
carapace.Gen(useBeaconCmd).PositionalCompletion(beacons.BeaconIDCompleter(con))
|
||||
registerUseIDCompletion(useBeaconCmd, con, false, true)
|
||||
useCmd.AddCommand(useBeaconCmd)
|
||||
|
||||
return []*cobra.Command{useCmd}
|
||||
|
||||
@@ -39,6 +39,77 @@ import (
|
||||
|
||||
var ErrNoSelection = errors.New("no selection")
|
||||
|
||||
func registerUseIDCompletion(cmd *cobra.Command, con *console.SliverClient, includeSessions, includeBeacons bool) {
|
||||
if cmd == nil || cmd.ValidArgsFunction != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) > 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
values := useCompletionValues(con, includeSessions, includeBeacons)
|
||||
return filterCompletionValues(values, toComplete), cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func useCompletionValues(con *console.SliverClient, includeSessions, includeBeacons bool) []string {
|
||||
results := []string{}
|
||||
if con == nil || con.Rpc == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
if includeSessions {
|
||||
sessions, err := con.Rpc.GetSessions(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
for _, session := range sessions.Sessions {
|
||||
link := fmt.Sprintf("[%s <- %s]", session.ActiveC2, session.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", session.Name, session.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", session.Username, session.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, fmt.Sprintf("%s\t%s", session.ID[:8], desc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if includeBeacons {
|
||||
beacons, err := con.Rpc.GetBeacons(context.Background(), &commonpb.Empty{})
|
||||
if err == nil {
|
||||
for _, beacon := range beacons.Beacons {
|
||||
link := fmt.Sprintf("[%s <- %s]", beacon.ActiveC2, beacon.RemoteAddress)
|
||||
id := fmt.Sprintf("%s (%d)", beacon.Name, beacon.PID)
|
||||
userHost := fmt.Sprintf("%s@%s", beacon.Username, beacon.Hostname)
|
||||
desc := strings.Join([]string{id, userHost, link}, " ")
|
||||
|
||||
results = append(results, fmt.Sprintf("%s\t%s", beacon.ID[:8], desc))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func filterCompletionValues(values []string, prefix string) []string {
|
||||
if prefix == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
filtered := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
candidate := value
|
||||
if tab := strings.IndexByte(value, '\t'); tab >= 0 {
|
||||
candidate = value[:tab]
|
||||
}
|
||||
if strings.HasPrefix(candidate, prefix) {
|
||||
filtered = append(filtered, value)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
// UseCmd - Change the active session
|
||||
func UseCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
var session *clientpb.Session
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package websites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -26,6 +30,41 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
})
|
||||
carapace.Gen(websitesCmd).PositionalCompletion(WebsiteNameCompleter(con))
|
||||
|
||||
websitesListCmd := &cobra.Command{
|
||||
Use: consts.ListStr,
|
||||
Short: "List configured websites",
|
||||
Long: help.GetHelpFor([]string{consts.WebsitesStr, consts.ListStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListWebsites(cmd, con, args)
|
||||
},
|
||||
}
|
||||
websitesCmd.AddCommand(websitesListCmd)
|
||||
|
||||
websitesShowCmd := &cobra.Command{
|
||||
Use: "show [name]",
|
||||
Short: "Show contents of a website",
|
||||
Long: help.GetHelpFor([]string{consts.WebsitesStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListWebsiteContent(args[0], con)
|
||||
},
|
||||
}
|
||||
carapace.Gen(websitesShowCmd).PositionalCompletion(WebsiteNameCompleter(con))
|
||||
websitesShowCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
ws, err := con.Rpc.Websites(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
out := []string{}
|
||||
for _, w := range ws.Websites {
|
||||
if strings.HasPrefix(w.Name, toComplete) {
|
||||
out = append(out, w.Name)
|
||||
}
|
||||
}
|
||||
return out, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
websitesCmd.AddCommand(websitesShowCmd)
|
||||
|
||||
websitesRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove an entire website and all of its contents",
|
||||
|
||||
@@ -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,13 +119,14 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
|
||||
}
|
||||
|
||||
if contentType == "" {
|
||||
contentType = sniffContentType(file)
|
||||
contentType = sniffContentType(file, contentPath)
|
||||
}
|
||||
|
||||
web.Contents[webpath] = &clientpb.WebContent{
|
||||
Path: webpath,
|
||||
ContentType: contentType,
|
||||
Content: data,
|
||||
Path: webpath,
|
||||
ContentType: contentType,
|
||||
OriginalFile: filepath.Base(contentPath),
|
||||
Content: data,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -134,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)
|
||||
}
|
||||
@@ -21,7 +21,6 @@ package websites
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -58,11 +57,14 @@ func ListWebsites(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
con.PrintInfof("No websites\n")
|
||||
return
|
||||
}
|
||||
con.Println("Websites")
|
||||
con.Println(strings.Repeat("=", len("Websites")))
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"Name", "Objects"})
|
||||
for _, site := range websites.Websites {
|
||||
con.Printf("%s%s%s - %d page(s)\n", console.Bold, site.Name, console.Normal, len(site.Contents))
|
||||
tw.AppendRow(table.Row{site.Name, len(site.Contents)})
|
||||
}
|
||||
|
||||
con.Println(tw.Render())
|
||||
}
|
||||
|
||||
// ListWebsiteContent - List the static contents of a website.
|
||||
@@ -91,6 +93,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
|
||||
"Path",
|
||||
"Content-type",
|
||||
"Size",
|
||||
"Original File",
|
||||
"SHA256",
|
||||
})
|
||||
sortedContents := []*clientpb.WebContent{}
|
||||
for _, content := range web.Contents {
|
||||
@@ -104,6 +108,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
|
||||
content.Path,
|
||||
content.ContentType,
|
||||
content.Size,
|
||||
content.OriginalFile,
|
||||
content.Sha256,
|
||||
})
|
||||
}
|
||||
con.Println(tw.Render())
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/reeflective/console"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func (con *SliverClient) allowServerRootCommands(args []string) ([]string, error) {
|
||||
con.applyCommandFallback(args)
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (con *SliverClient) prepareCompletion(line []rune, cursor int) {
|
||||
if con == nil || con.App == nil {
|
||||
return
|
||||
}
|
||||
|
||||
menu := con.App.ActiveMenu()
|
||||
if menu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
con.setMenuCommandForCompletion(menu, line, cursor)
|
||||
}
|
||||
|
||||
func (con *SliverClient) applyCommandFallback(args []string) {
|
||||
if con == nil || con.App == nil {
|
||||
return
|
||||
}
|
||||
|
||||
con.setMenuCommand(con.App.ActiveMenu(), args)
|
||||
}
|
||||
|
||||
func (con *SliverClient) setMenuCommand(menu *console.Menu, args []string) {
|
||||
if menu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if menu.Name() != consts.ImplantMenu {
|
||||
if menu.Command == nil && con.serverCmds != nil {
|
||||
menu.Command = con.serverCmds()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if menu.Command == nil {
|
||||
if con.sliverCmds == nil {
|
||||
return
|
||||
}
|
||||
menu.Command = con.sliverCmds()
|
||||
}
|
||||
|
||||
if con.serverCmds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := firstNonFlagArg(args)
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if rootHasCommand(menu.Command, name) {
|
||||
return
|
||||
}
|
||||
|
||||
serverRoot := con.serverCmds()
|
||||
if serverRoot == nil || !rootHasCommand(serverRoot, name) {
|
||||
return
|
||||
}
|
||||
|
||||
menu.Command = serverRoot
|
||||
}
|
||||
|
||||
func (con *SliverClient) setMenuCommandForCompletion(menu *console.Menu, line []rune, cursor int) {
|
||||
if menu == nil || menu.Name() != consts.ImplantMenu {
|
||||
return
|
||||
}
|
||||
|
||||
if menu.Command == nil {
|
||||
if con.sliverCmds == nil {
|
||||
return
|
||||
}
|
||||
menu.Command = con.sliverCmds()
|
||||
}
|
||||
|
||||
if con.serverCmds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
token := firstTokenFromLine(line, cursor)
|
||||
if token == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if rootHasCommandPrefix(menu.Command, token) {
|
||||
return
|
||||
}
|
||||
|
||||
serverRoot := con.serverCmds()
|
||||
if serverRoot == nil || !rootHasCommandPrefix(serverRoot, token) {
|
||||
return
|
||||
}
|
||||
|
||||
menu.Command = serverRoot
|
||||
hideFilteredCommands(menu, menu.Command)
|
||||
}
|
||||
|
||||
func firstNonFlagArg(args []string) string {
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
if arg == "--" {
|
||||
if i+1 < len(args) {
|
||||
return args[i+1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
continue
|
||||
}
|
||||
return arg
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstTokenFromLine(line []rune, cursor int) string {
|
||||
if cursor > len(line) {
|
||||
cursor = len(line)
|
||||
}
|
||||
if cursor < 0 {
|
||||
cursor = 0
|
||||
}
|
||||
|
||||
pos := 0
|
||||
for pos < cursor && isSpace(line[pos]) {
|
||||
pos++
|
||||
}
|
||||
start := pos
|
||||
for pos < cursor && !isSpace(line[pos]) {
|
||||
pos++
|
||||
}
|
||||
if start == pos {
|
||||
return ""
|
||||
}
|
||||
return string(line[start:pos])
|
||||
}
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
func rootHasCommand(root *cobra.Command, name string) bool {
|
||||
if root == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
for _, cmd := range root.Commands() {
|
||||
if cmd.Name() == name || cmd.HasAlias(name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func rootHasCommandPrefix(root *cobra.Command, prefix string) bool {
|
||||
if root == nil || prefix == "" {
|
||||
return false
|
||||
}
|
||||
for _, cmd := range root.Commands() {
|
||||
if strings.HasPrefix(cmd.Name(), prefix) {
|
||||
return true
|
||||
}
|
||||
for _, alias := range cmd.Aliases {
|
||||
if strings.HasPrefix(alias, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hideFilteredCommands(menu *console.Menu, root *cobra.Command) {
|
||||
if menu == nil || root == nil {
|
||||
return
|
||||
}
|
||||
for _, cmd := range root.Commands() {
|
||||
if cmd.Hidden {
|
||||
continue
|
||||
}
|
||||
if filters := menu.ActiveFiltersFor(cmd); len(filters) > 0 {
|
||||
cmd.Hidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,8 @@ type SliverClient struct {
|
||||
Settings *assets.ClientSettings
|
||||
IsServer bool
|
||||
IsCLI bool
|
||||
serverCmds console.Commands
|
||||
sliverCmds console.Commands
|
||||
|
||||
jsonHandler slog.Handler
|
||||
printf func(format string, args ...any) (int, error)
|
||||
@@ -166,6 +168,8 @@ func NewConsole(isServer bool) *SliverClient {
|
||||
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
|
||||
con.Rpc = rpc
|
||||
con.IsCLI = !run
|
||||
con.serverCmds = serverCmds
|
||||
con.sliverCmds = sliverCmds
|
||||
|
||||
// The console application needs to query the terminal for cursor positions
|
||||
// when asynchronously printing logs (that is, when no command is running).
|
||||
@@ -184,6 +188,15 @@ func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, slive
|
||||
sliver := con.App.Menu(consts.ImplantMenu)
|
||||
sliver.SetCommands(sliverCmds)
|
||||
|
||||
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.allowServerRootCommands)
|
||||
if shell := con.App.Shell(); shell != nil && shell.Completer != nil {
|
||||
baseCompleter := shell.Completer
|
||||
shell.Completer = func(line []rune, cursor int) readline.Completions {
|
||||
con.prepareCompletion(line, cursor)
|
||||
return baseCompleter(line, cursor)
|
||||
}
|
||||
}
|
||||
|
||||
// Events
|
||||
go con.startEventLoop()
|
||||
go core.TunnelLoop(rpc)
|
||||
@@ -250,6 +263,19 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
|
||||
return fmt.Errorf("parse error: %w", err)
|
||||
}
|
||||
|
||||
if con.serverCmds == nil {
|
||||
con.serverCmds = serverCmds
|
||||
}
|
||||
if con.sliverCmds == nil {
|
||||
con.sliverCmds = sliverCmds
|
||||
}
|
||||
|
||||
menu := con.App.ActiveMenu()
|
||||
if menu != nil {
|
||||
// Reset per line to avoid stale roots when rc scripts switch menus.
|
||||
menu.Command = nil
|
||||
}
|
||||
|
||||
for _, hook := range con.App.PreCmdRunLineHooks {
|
||||
args, err = hook(args)
|
||||
if err != nil {
|
||||
@@ -261,11 +287,15 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
|
||||
return nil
|
||||
}
|
||||
|
||||
menu := con.App.ActiveMenu()
|
||||
if menu.Name() == consts.ImplantMenu {
|
||||
menu.Command = sliverCmds()
|
||||
} else {
|
||||
menu.Command = serverCmds()
|
||||
menu = con.App.ActiveMenu()
|
||||
if menu == nil {
|
||||
return fmt.Errorf("no active menu")
|
||||
}
|
||||
if menu.Command == nil {
|
||||
con.setMenuCommand(menu, args)
|
||||
}
|
||||
if menu.Command == nil {
|
||||
return fmt.Errorf("no commands available")
|
||||
}
|
||||
|
||||
target, _, _ := menu.Command.Find(args)
|
||||
|
||||
@@ -227,6 +227,15 @@ func (con *SliverClient) PrintSuccessf(format string, args ...any) {
|
||||
con.printf(Clearln+Success+format, args...)
|
||||
}
|
||||
|
||||
// PrintSuccess prints a success message with a default or provided message.
|
||||
func (con *SliverClient) PrintSuccess(args ...any) {
|
||||
if len(args) == 0 {
|
||||
con.PrintSuccessf("Success")
|
||||
return
|
||||
}
|
||||
con.PrintSuccessf("%s", fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// PrintWarnf a warning message immediately below the last line of output.
|
||||
func (con *SliverClient) PrintWarnf(format string, args ...any) {
|
||||
logger := slog.New(con.jsonHandler)
|
||||
|
||||
+30
-34
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
@@ -23,62 +24,57 @@ type GenerateFormResult struct {
|
||||
}
|
||||
|
||||
// GenerateForm prompts for core generate flags and returns the collected values.
|
||||
func GenerateForm() (*GenerateFormResult, error) {
|
||||
func GenerateForm(compiler *clientpb.Compiler) (*GenerateFormResult, error) {
|
||||
result := &GenerateFormResult{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
Format: "exe",
|
||||
C2Type: "mtls",
|
||||
}
|
||||
commonPlatformsOnly := true
|
||||
lastCommonPlatformsOnly := commonPlatformsOnly
|
||||
compilerTargets := compilerTargetList(compiler)
|
||||
targetBindings := &targetOptionBindings{
|
||||
Common: &commonPlatformsOnly,
|
||||
OS: &result.OS,
|
||||
Arch: &result.Arch,
|
||||
}
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Common Platforms Only").
|
||||
Value(&commonPlatformsOnly),
|
||||
huh.NewSelect[string]().
|
||||
Title("Target operating system").
|
||||
Options(
|
||||
huh.NewOption("Windows", "windows"),
|
||||
huh.NewOption("Linux", "linux"),
|
||||
huh.NewOption("macOS", "darwin"),
|
||||
).
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonOSOptions()
|
||||
}
|
||||
return osOptionsFromTargets(compilerTargets)
|
||||
}, targetBindings).
|
||||
Value(&result.OS),
|
||||
huh.NewSelect[string]().
|
||||
Title("CPU architecture").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
switch result.OS {
|
||||
case "darwin":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
}
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonArchOptions(result.OS)
|
||||
}
|
||||
}, &result.OS).
|
||||
return archOptionsFromTargets(compilerTargets, result.OS)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Arch),
|
||||
huh.NewSelect[string]().
|
||||
Title("Output format").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
options := []huh.Option[string]{
|
||||
huh.NewOption("Executable", "exe"),
|
||||
huh.NewOption("Shared library", "shared"),
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonFormatOptions(result.OS)
|
||||
}
|
||||
if result.OS == "windows" {
|
||||
options = append(options,
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Format),
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
@@ -26,62 +27,57 @@ type GenerateBeaconFormResult struct {
|
||||
}
|
||||
|
||||
// GenerateBeaconForm prompts for generate beacon flags and returns the collected values.
|
||||
func GenerateBeaconForm() (*GenerateBeaconFormResult, error) {
|
||||
func GenerateBeaconForm(compiler *clientpb.Compiler) (*GenerateBeaconFormResult, error) {
|
||||
result := &GenerateBeaconFormResult{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
Format: "exe",
|
||||
C2Type: "mtls",
|
||||
}
|
||||
commonPlatformsOnly := true
|
||||
lastCommonPlatformsOnly := commonPlatformsOnly
|
||||
compilerTargets := compilerTargetList(compiler)
|
||||
targetBindings := &targetOptionBindings{
|
||||
Common: &commonPlatformsOnly,
|
||||
OS: &result.OS,
|
||||
Arch: &result.Arch,
|
||||
}
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Common Platforms Only").
|
||||
Value(&commonPlatformsOnly),
|
||||
huh.NewSelect[string]().
|
||||
Title("Target operating system").
|
||||
Options(
|
||||
huh.NewOption("Windows", "windows"),
|
||||
huh.NewOption("Linux", "linux"),
|
||||
huh.NewOption("macOS", "darwin"),
|
||||
).
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonOSOptions()
|
||||
}
|
||||
return osOptionsFromTargets(compilerTargets)
|
||||
}, targetBindings).
|
||||
Value(&result.OS),
|
||||
huh.NewSelect[string]().
|
||||
Title("CPU architecture").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
switch result.OS {
|
||||
case "darwin":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
}
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonArchOptions(result.OS)
|
||||
}
|
||||
}, &result.OS).
|
||||
return archOptionsFromTargets(compilerTargets, result.OS)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Arch),
|
||||
huh.NewSelect[string]().
|
||||
Title("Output format").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
options := []huh.Option[string]{
|
||||
huh.NewOption("Executable", "exe"),
|
||||
huh.NewOption("Shared library", "shared"),
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonFormatOptions(result.OS)
|
||||
}
|
||||
if result.OS == "windows" {
|
||||
options = append(options,
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Format),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package forms
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
type formatOption struct {
|
||||
format clientpb.OutputFormat
|
||||
label string
|
||||
value string
|
||||
}
|
||||
|
||||
type targetOptionBindings struct {
|
||||
Common *bool
|
||||
OS *string
|
||||
Arch *string
|
||||
}
|
||||
|
||||
var formatOptionOrder = []formatOption{
|
||||
{format: clientpb.OutputFormat_EXECUTABLE, label: "Executable", value: "exe"},
|
||||
{format: clientpb.OutputFormat_SHARED_LIB, label: "Shared library", value: "shared"},
|
||||
{format: clientpb.OutputFormat_SERVICE, label: "Service", value: "service"},
|
||||
{format: clientpb.OutputFormat_SHELLCODE, label: "Shellcode", value: "shellcode"},
|
||||
}
|
||||
|
||||
func commonOSOptions() []huh.Option[string] {
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("Windows", "windows"),
|
||||
huh.NewOption("Linux", "linux"),
|
||||
huh.NewOption("macOS", "darwin"),
|
||||
}
|
||||
}
|
||||
|
||||
func commonArchOptions(goos string) []huh.Option[string] {
|
||||
switch goos {
|
||||
case "darwin":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
}
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func commonFormatOptions(goos string) []huh.Option[string] {
|
||||
options := []huh.Option[string]{
|
||||
huh.NewOption("Executable", "exe"),
|
||||
huh.NewOption("Shared library", "shared"),
|
||||
}
|
||||
if goos == "windows" {
|
||||
options = append(options,
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func firstOptionValue(options []huh.Option[string]) (string, bool) {
|
||||
if len(options) == 0 {
|
||||
return "", false
|
||||
}
|
||||
return options[0].Value, true
|
||||
}
|
||||
|
||||
func maybeResetTargets(commonPlatformsOnly bool, lastCommonPlatformsOnly *bool, compilerTargets []*clientpb.CompilerTarget, goos, goarch, format *string) {
|
||||
if lastCommonPlatformsOnly == nil || goos == nil || goarch == nil || format == nil {
|
||||
return
|
||||
}
|
||||
if commonPlatformsOnly == *lastCommonPlatformsOnly {
|
||||
return
|
||||
}
|
||||
*lastCommonPlatformsOnly = commonPlatformsOnly
|
||||
if commonPlatformsOnly {
|
||||
return
|
||||
}
|
||||
|
||||
osOptions := commonOSOptions()
|
||||
if len(compilerTargets) > 0 {
|
||||
osOptions = osOptionsFromTargets(compilerTargets)
|
||||
if len(osOptions) == 0 {
|
||||
osOptions = commonOSOptions()
|
||||
}
|
||||
}
|
||||
if value, ok := firstOptionValue(osOptions); ok {
|
||||
*goos = value
|
||||
}
|
||||
|
||||
archOptions := commonArchOptions(*goos)
|
||||
if len(compilerTargets) > 0 {
|
||||
archOptions = archOptionsFromTargets(compilerTargets, *goos)
|
||||
if len(archOptions) == 0 {
|
||||
archOptions = commonArchOptions(*goos)
|
||||
}
|
||||
}
|
||||
if value, ok := firstOptionValue(archOptions); ok {
|
||||
*goarch = value
|
||||
}
|
||||
|
||||
formatOptions := commonFormatOptions(*goos)
|
||||
if len(compilerTargets) > 0 {
|
||||
formatOptions = formatOptionsFromTargets(compilerTargets, *goos, *goarch)
|
||||
if len(formatOptions) == 0 {
|
||||
formatOptions = commonFormatOptions(*goos)
|
||||
}
|
||||
}
|
||||
if value, ok := firstOptionValue(formatOptions); ok {
|
||||
*format = value
|
||||
}
|
||||
}
|
||||
|
||||
func compilerTargetList(compiler *clientpb.Compiler) []*clientpb.CompilerTarget {
|
||||
if compiler == nil {
|
||||
return nil
|
||||
}
|
||||
targets := make([]*clientpb.CompilerTarget, 0, len(compiler.Targets)+len(compiler.UnsupportedTargets))
|
||||
targets = append(targets, compiler.Targets...)
|
||||
targets = append(targets, compiler.UnsupportedTargets...)
|
||||
return targets
|
||||
}
|
||||
|
||||
func osOptionsFromTargets(targets []*clientpb.CompilerTarget) []huh.Option[string] {
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]struct{}{}
|
||||
for _, target := range targets {
|
||||
if target.GOOS == "" {
|
||||
continue
|
||||
}
|
||||
seen[target.GOOS] = struct{}{}
|
||||
}
|
||||
values := make([]string, 0, len(seen))
|
||||
for value := range seen {
|
||||
values = append(values, value)
|
||||
}
|
||||
sort.Strings(values)
|
||||
|
||||
options := make([]huh.Option[string], 0, len(values))
|
||||
for _, value := range values {
|
||||
options = append(options, huh.NewOption(osLabel(value), value))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func archOptionsFromTargets(targets []*clientpb.CompilerTarget, goos string) []huh.Option[string] {
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]struct{}{}
|
||||
for _, target := range targets {
|
||||
if goos != "" && target.GOOS != goos {
|
||||
continue
|
||||
}
|
||||
if target.GOARCH == "" {
|
||||
continue
|
||||
}
|
||||
seen[target.GOARCH] = struct{}{}
|
||||
}
|
||||
values := make([]string, 0, len(seen))
|
||||
for value := range seen {
|
||||
values = append(values, value)
|
||||
}
|
||||
sort.Strings(values)
|
||||
|
||||
options := make([]huh.Option[string], 0, len(values))
|
||||
for _, value := range values {
|
||||
options = append(options, huh.NewOption(value, value))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func formatOptionsFromTargets(targets []*clientpb.CompilerTarget, goos, goarch string) []huh.Option[string] {
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
available := map[clientpb.OutputFormat]struct{}{}
|
||||
for _, target := range targets {
|
||||
if goos != "" && target.GOOS != goos {
|
||||
continue
|
||||
}
|
||||
if goarch != "" && target.GOARCH != goarch {
|
||||
continue
|
||||
}
|
||||
available[target.Format] = struct{}{}
|
||||
}
|
||||
|
||||
options := make([]huh.Option[string], 0, len(available))
|
||||
for _, option := range formatOptionOrder {
|
||||
if _, ok := available[option.format]; ok {
|
||||
options = append(options, huh.NewOption(option.label, option.value))
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func osLabel(value string) string {
|
||||
switch value {
|
||||
case "windows":
|
||||
return "Windows"
|
||||
case "linux":
|
||||
return "Linux"
|
||||
case "darwin":
|
||||
return "macOS"
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
+65
-20
@@ -63,6 +63,68 @@ func (r *Release) Published() (time.Time, error) {
|
||||
// occurs we don't really try to recover. If client is nil we just use the Go
|
||||
// default client with default settings.
|
||||
func CheckForUpdates(client *http.Client, prereleases bool) (*Release, error) {
|
||||
releases, err := fetchReleases(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(releases) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
current := canonicalSemver(Version)
|
||||
if current == "" {
|
||||
current = "v0.0.0"
|
||||
}
|
||||
for _, release := range releases {
|
||||
if release == nil {
|
||||
continue
|
||||
}
|
||||
if release.Prerelease && !prereleases {
|
||||
continue
|
||||
}
|
||||
releaseVersion := canonicalSemver(release.TagName)
|
||||
if releaseVersion == "" {
|
||||
continue
|
||||
}
|
||||
if semver.Compare(releaseVersion, current) > 0 {
|
||||
return release, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// LatestRelease - Returns the latest release without comparing to the current version.
|
||||
func LatestRelease(client *http.Client, prereleases bool) (*Release, error) {
|
||||
releases, err := fetchReleases(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(releases) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var latest *Release
|
||||
var latestVersion string
|
||||
for _, release := range releases {
|
||||
if release == nil {
|
||||
continue
|
||||
}
|
||||
if release.Prerelease && !prereleases {
|
||||
continue
|
||||
}
|
||||
releaseVersion := canonicalSemver(release.TagName)
|
||||
if releaseVersion == "" {
|
||||
continue
|
||||
}
|
||||
if latest == nil || semver.Compare(releaseVersion, latestVersion) > 0 {
|
||||
latest = release
|
||||
latestVersion = releaseVersion
|
||||
}
|
||||
}
|
||||
return latest, nil
|
||||
}
|
||||
|
||||
func fetchReleases(client *http.Client) ([]*Release, error) {
|
||||
skip := os.Getenv(skipCheckEnv)
|
||||
if skip != "" || GithubReleasesURL == "" {
|
||||
return nil, nil
|
||||
@@ -84,27 +146,10 @@ func CheckForUpdates(client *http.Client, prereleases bool) (*Release, error) {
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, errors.New("API returned non-200 status code")
|
||||
}
|
||||
releases := &[]*Release{}
|
||||
err = json.Unmarshal(body, releases)
|
||||
if err != nil {
|
||||
releases := []*Release{}
|
||||
if err := json.Unmarshal(body, &releases); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
current := canonicalSemver(Version)
|
||||
if current == "" {
|
||||
current = "v0.0.0"
|
||||
}
|
||||
for _, release := range *releases {
|
||||
if release.Prerelease && !prereleases {
|
||||
continue
|
||||
}
|
||||
releaseVersion := canonicalSemver(release.TagName)
|
||||
if releaseVersion == "" {
|
||||
continue
|
||||
}
|
||||
if semver.Compare(releaseVersion, current) > 0 {
|
||||
return release, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
return releases, nil
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export type TutorialCardCardProps = {
|
||||
|
||||
export default function TutorialCard(props: TutorialCardCardProps) {
|
||||
return (
|
||||
<Card isFooterBlurred>
|
||||
<Card isFooterBlurred className="relative z-0">
|
||||
<CardHeader className="absolute z-10 top-1 flex-col items-end">
|
||||
<p className="text-md text-white/70 uppercase font-bold">
|
||||
{props.name}
|
||||
@@ -41,7 +41,7 @@ export default function TutorialCard(props: TutorialCardCardProps) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CardFooter className="absolute bg-black/40 bottom-0 z-100 border-t-1 border-default-600 dark:border-default-100">
|
||||
<CardFooter className="absolute bottom-0 z-10 bg-black/40 border-t-1 border-default-600 dark:border-default-100">
|
||||
<div className="flex flex-row w-full items-center">
|
||||
<p
|
||||
className={
|
||||
|
||||
Generated
+396
-406
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,52 @@
|
||||
__IMPORTANT:__ MCP (Model Context Protocol) support is _experimental_ and NOT ALL FUNCTIONALITY IS SUPPORTED YET. Pull requests and testing feedback are welcome!
|
||||
|
||||
There are two main ways to connect an AI model to sliver using MCP: locally over stdio, or over a network using HTTP/SSE transports. The `sliver-client` supports both stdio and http/sse, but the `sliver-server` binary only supports HTTP/SSE. We recommend using the `sliver-client` and [Multiplayer Mode](/docs?name=Multi-player+Mode) to connect to a remote server with the stdio transport.
|
||||
|
||||
|
||||
## MCP Over STDIO
|
||||
|
||||
Example configurations for various AIs, you'll need to configure [Multiplayer Mode](/docs?name=Multi-player+Mode) first:
|
||||
|
||||
### OpenAI Codex
|
||||
|
||||
```
|
||||
[mcp_servers.sliver]
|
||||
args = ["mcp", "--config", "/path/to/multiplayer.cfg"]
|
||||
command = "/path/to/sliver-client"
|
||||
```
|
||||
|
||||
### Anthropic Claude Code
|
||||
|
||||
```
|
||||
claude mcp add sliver -- /path/to/sliver-client mcp --config /path/to/multiplayer.cfg
|
||||
```
|
||||
|
||||
## MCP Over HTTP/SSE
|
||||
|
||||
The MCP HTTP/SSE server can be started from either the `sliver-client` or `sliver-server`, however _where you run the command is where the server will start_ i.e., starting the HTTP/SSE MCP using `sliver-client` will turn that `sliver-client` binary into the MCP server (connected to whatever Sliver server you're currently connected to). Running the command from the `sliver-server` console will start a HTTP/SSE MCP server locally bound on the server.
|
||||
|
||||
```
|
||||
.------..------..------..------..------..------.
|
||||
|S.--. ||L.--. ||I.--. ||V.--. ||E.--. ||R.--. |
|
||||
| :/\: || :/\: || (\/) || :(): || (\/) || :(): |
|
||||
| :\/: || (__) || :\/: || ()() || :\/: || ()() |
|
||||
| '--'S|| '--'L|| '--'I|| '--'V|| '--'E|| '--'R|
|
||||
`------'`------'`------'`------'`------'`------'
|
||||
|
||||
All hackers gain skulk
|
||||
[*] Server v1.6.4 - 4d0d46afd6e1cbcaafde92687144cb28694a68ce
|
||||
[*] Welcome to the sliver shell, please type 'help' for options
|
||||
sliver > mcp
|
||||
|
||||
Status: stopped
|
||||
Transport: sse
|
||||
Listen: 127.0.0.1:8080
|
||||
Endpoint: http://127.0.0.1:8080/sse
|
||||
|
||||
sliver > mcp start --transport http
|
||||
|
||||
[*] Starting MCP server (http) on 127.0.0.1:8080
|
||||
[*] Endpoint: http://127.0.0.1:8080/mcp
|
||||
```
|
||||
|
||||
You can use `mcp stop` to stop the server, see `mcp start --help` for additional options when starting the MCP server. Authentication is not yet supported for MCP over HTTP/SSE.
|
||||
@@ -0,0 +1,607 @@
|
||||
### Notifications
|
||||
|
||||
#### Table of Contents
|
||||
|
||||
- [Overview](/docs?name=Notifications#overview)
|
||||
- [Configuration Overview](/docs?name=Notifications#configuration-overview)
|
||||
- [Global Examples](/docs?name=Notifications#global-examples)
|
||||
- [Service Examples](/docs?name=Notifications#service-examples)
|
||||
- [Amazon SES](/docs?name=Notifications#amazon-ses)
|
||||
- [Amazon SNS](/docs?name=Notifications#amazon-sns)
|
||||
- [Bark](/docs?name=Notifications#bark)
|
||||
- [DingTalk (DingDing)](/docs?name=Notifications#dingtalk-dingding)
|
||||
- [Discord](/docs?name=Notifications#discord)
|
||||
- [FCM](/docs?name=Notifications#fcm)
|
||||
- [Google Chat](/docs?name=Notifications#google-chat)
|
||||
- [HTTP Webhooks](/docs?name=Notifications#http-webhooks)
|
||||
- [Lark](/docs?name=Notifications#lark)
|
||||
- [Line](/docs?name=Notifications#line)
|
||||
- [Line Notify](/docs?name=Notifications#line-notify)
|
||||
- [Mail (SMTP)](/docs?name=Notifications#mail-smtp)
|
||||
- [Mailgun](/docs?name=Notifications#mailgun)
|
||||
- [Matrix](/docs?name=Notifications#matrix)
|
||||
- [Mattermost](/docs?name=Notifications#mattermost)
|
||||
- [Microsoft Teams](/docs?name=Notifications#microsoft-teams)
|
||||
- [PagerDuty](/docs?name=Notifications#pagerduty)
|
||||
- [Plivo](/docs?name=Notifications#plivo)
|
||||
- [Pushbullet](/docs?name=Notifications#pushbullet)
|
||||
- [Pushbullet SMS](/docs?name=Notifications#pushbullet-sms)
|
||||
- [Pushover](/docs?name=Notifications#pushover)
|
||||
- [Reddit](/docs?name=Notifications#reddit)
|
||||
- [Rocket.Chat](/docs?name=Notifications#rocketchat)
|
||||
- [SendGrid](/docs?name=Notifications#sendgrid)
|
||||
- [Slack](/docs?name=Notifications#slack)
|
||||
- [Syslog](/docs?name=Notifications#syslog)
|
||||
- [Telegram](/docs?name=Notifications#telegram)
|
||||
- [TextMagic](/docs?name=Notifications#textmagic)
|
||||
- [Twilio](/docs?name=Notifications#twilio)
|
||||
- [Twitter](/docs?name=Notifications#twitter)
|
||||
- [Viber](/docs?name=Notifications#viber)
|
||||
- [Web Push](/docs?name=Notifications#web-push)
|
||||
- [WeChat](/docs?name=Notifications#wechat)
|
||||
- [WhatsApp](/docs?name=Notifications#whatsapp)
|
||||
- [Custom Templates](/docs?name=Notifications#custom-templates)
|
||||
- [Notes](/docs?name=Notifications#notes)
|
||||
|
||||
#### Overview
|
||||
|
||||
Sliver can send server event notifications via the `github.com/nikoksr/notify` services. Notifications are configured in `server.yaml` under the `notifications` block (located at `~/.sliver/configs/server.yaml` by default).
|
||||
|
||||
Notifications are disabled by default. Enable them globally, then enable one or more services. You can optionally filter which events are sent globally or per-service.
|
||||
|
||||
#### Configuration Overview
|
||||
|
||||
- `notifications.enabled` - Master switch for notifications.
|
||||
- `notifications.events` - Optional list of event types to send. Empty means all events.
|
||||
- `notifications.services.<service>.enabled` - Enable a specific service.
|
||||
- `notifications.services.<service>.events` - Optional per-service event filter. If empty, the global list is used.
|
||||
- Any string field can reference an environment variable by using `$ENV_VAR` or `${ENV_VAR}`.
|
||||
|
||||
#### Global Examples
|
||||
|
||||
##### Example: Slack + Telegram with Event Filters
|
||||
|
||||
```yaml
|
||||
notifications:
|
||||
enabled: true
|
||||
events:
|
||||
- session-connected
|
||||
- session-disconnected
|
||||
services:
|
||||
slack:
|
||||
enabled: true
|
||||
api_token: "$SLIVER_SLACK_TOKEN"
|
||||
channels:
|
||||
- "C0123456789"
|
||||
telegram:
|
||||
enabled: true
|
||||
api_token: "$SLIVER_TELEGRAM_TOKEN"
|
||||
chat_ids:
|
||||
- "123456789"
|
||||
events:
|
||||
- session-connected
|
||||
```
|
||||
|
||||
##### Example: HTTP Webhooks + Syslog
|
||||
|
||||
```yaml
|
||||
notifications:
|
||||
enabled: true
|
||||
services:
|
||||
http:
|
||||
enabled: true
|
||||
urls:
|
||||
- "https://hooks.example.com/sliver"
|
||||
webhooks:
|
||||
- url: "https://hooks.example.com/sliver-auth"
|
||||
method: "POST"
|
||||
content_type: "application/json"
|
||||
headers:
|
||||
Authorization: "$SLIVER_WEBHOOK_TOKEN"
|
||||
syslog:
|
||||
enabled: true
|
||||
priority: "info"
|
||||
network: "udp"
|
||||
address: "127.0.0.1:514"
|
||||
tag: "sliver"
|
||||
```
|
||||
|
||||
#### Service Examples
|
||||
|
||||
Each example below shows the service-specific block under `notifications.services`. All examples assume `notifications.enabled: true`.
|
||||
|
||||
##### Amazon SES
|
||||
|
||||
```yaml
|
||||
services:
|
||||
amazon_ses:
|
||||
enabled: true
|
||||
access_key_id: "$AWS_ACCESS_KEY_ID"
|
||||
secret_key: "$AWS_SECRET_ACCESS_KEY"
|
||||
region: "us-east-1"
|
||||
sender_address: "[email protected]"
|
||||
receivers:
|
||||
- "[email protected]"
|
||||
```
|
||||
|
||||
##### Amazon SNS
|
||||
|
||||
```yaml
|
||||
services:
|
||||
amazon_sns:
|
||||
enabled: true
|
||||
access_key_id: "$AWS_ACCESS_KEY_ID"
|
||||
secret_key: "$AWS_SECRET_ACCESS_KEY"
|
||||
region: "us-east-1"
|
||||
receivers:
|
||||
- "https://sns.us-east-1.amazonaws.com/123456789012/my-topic"
|
||||
```
|
||||
|
||||
##### Bark
|
||||
|
||||
```yaml
|
||||
services:
|
||||
bark:
|
||||
enabled: true
|
||||
device_key: "$BARK_DEVICE_KEY"
|
||||
servers:
|
||||
- "https://api.day.app/"
|
||||
```
|
||||
|
||||
##### DingTalk (DingDing)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
dingding:
|
||||
enabled: true
|
||||
token: "$DINGDING_TOKEN"
|
||||
secret: "$DINGDING_SECRET"
|
||||
```
|
||||
|
||||
##### Discord
|
||||
|
||||
```yaml
|
||||
services:
|
||||
discord:
|
||||
enabled: true
|
||||
token: "$DISCORD_BOT_TOKEN"
|
||||
token_type: "bot" # bot (default) or oauth
|
||||
channels:
|
||||
- "123456789012345678"
|
||||
```
|
||||
|
||||
##### FCM
|
||||
|
||||
```yaml
|
||||
services:
|
||||
fcm:
|
||||
enabled: true
|
||||
credentials_file: "/path/to/firebase.json"
|
||||
project_id: "my-firebase-project"
|
||||
device_tokens:
|
||||
- "$FCM_DEVICE_TOKEN"
|
||||
```
|
||||
|
||||
##### Google Chat
|
||||
|
||||
```yaml
|
||||
services:
|
||||
google_chat:
|
||||
enabled: true
|
||||
credentials_file: "/path/to/google-chat.json"
|
||||
spaces:
|
||||
- "AAAA1234567"
|
||||
```
|
||||
|
||||
##### HTTP Webhooks
|
||||
|
||||
```yaml
|
||||
services:
|
||||
http:
|
||||
enabled: true
|
||||
urls:
|
||||
- "https://hooks.example.com/sliver"
|
||||
webhooks:
|
||||
- url: "https://hooks.example.com/sliver-auth"
|
||||
method: "POST"
|
||||
content_type: "application/json"
|
||||
headers:
|
||||
Authorization: "$SLIVER_WEBHOOK_TOKEN"
|
||||
```
|
||||
|
||||
##### Lark
|
||||
|
||||
```yaml
|
||||
services:
|
||||
lark:
|
||||
enabled: true
|
||||
webhook:
|
||||
url: "$LARK_WEBHOOK_URL"
|
||||
custom_app:
|
||||
app_id: "$LARK_APP_ID"
|
||||
app_secret: "$LARK_APP_SECRET"
|
||||
receivers:
|
||||
- type: "open_id"
|
||||
id: "ou_1234567890abcdef"
|
||||
```
|
||||
|
||||
##### Line
|
||||
|
||||
```yaml
|
||||
services:
|
||||
line:
|
||||
enabled: true
|
||||
channel_secret: "$LINE_CHANNEL_SECRET"
|
||||
channel_access_token: "$LINE_CHANNEL_ACCESS_TOKEN"
|
||||
receivers:
|
||||
- "U1234567890abcdef"
|
||||
```
|
||||
|
||||
##### Line Notify
|
||||
|
||||
```yaml
|
||||
services:
|
||||
line_notify:
|
||||
enabled: true
|
||||
receivers:
|
||||
- "$LINE_NOTIFY_TOKEN"
|
||||
```
|
||||
|
||||
##### Mail (SMTP)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
mail:
|
||||
enabled: true
|
||||
sender_address: "[email protected]"
|
||||
smtp_host: "smtp.example.com:587"
|
||||
smtp_identity: ""
|
||||
smtp_username: "$SMTP_USER"
|
||||
smtp_password: "$SMTP_PASSWORD"
|
||||
smtp_auth_host: "smtp.example.com"
|
||||
body_type: "plain" # plain or html
|
||||
receivers:
|
||||
- "[email protected]"
|
||||
```
|
||||
|
||||
##### Mailgun
|
||||
|
||||
```yaml
|
||||
services:
|
||||
mailgun:
|
||||
enabled: true
|
||||
domain: "mg.example.com"
|
||||
api_key: "$MAILGUN_API_KEY"
|
||||
sender_address: "[email protected]"
|
||||
receivers:
|
||||
- "[email protected]"
|
||||
```
|
||||
|
||||
##### Matrix
|
||||
|
||||
```yaml
|
||||
services:
|
||||
matrix:
|
||||
enabled: true
|
||||
user_id: "@bot:matrix.example"
|
||||
room_id: "!roomid:matrix.example"
|
||||
home_server: "https://matrix.example"
|
||||
access_token: "$MATRIX_ACCESS_TOKEN"
|
||||
```
|
||||
|
||||
##### Mattermost
|
||||
|
||||
```yaml
|
||||
services:
|
||||
mattermost:
|
||||
enabled: true
|
||||
url: "https://mattermost.example.com"
|
||||
login_id: "$MM_LOGIN_ID"
|
||||
password: "$MM_PASSWORD"
|
||||
channels:
|
||||
- "channel-id"
|
||||
```
|
||||
|
||||
##### Microsoft Teams
|
||||
|
||||
```yaml
|
||||
services:
|
||||
msteams:
|
||||
enabled: true
|
||||
webhooks:
|
||||
- "$MSTEAMS_WEBHOOK"
|
||||
wrap_text: true
|
||||
disable_webhook_validation: false
|
||||
user_agent: "sliver-notify"
|
||||
```
|
||||
|
||||
##### PagerDuty
|
||||
|
||||
```yaml
|
||||
services:
|
||||
pagerduty:
|
||||
enabled: true
|
||||
token: "$PAGERDUTY_TOKEN"
|
||||
from_address: "[email protected]"
|
||||
receivers:
|
||||
- "P123456"
|
||||
notification_type: "incident"
|
||||
urgency: "high"
|
||||
priority_id: "PQ123456"
|
||||
```
|
||||
|
||||
##### Plivo
|
||||
|
||||
```yaml
|
||||
services:
|
||||
plivo:
|
||||
enabled: true
|
||||
auth_id: "$PLIVO_AUTH_ID"
|
||||
auth_token: "$PLIVO_AUTH_TOKEN"
|
||||
source: "+15551234567"
|
||||
callback_url: "https://example.com/plivo"
|
||||
callback_method: "POST"
|
||||
receivers:
|
||||
- "+15557654321"
|
||||
```
|
||||
|
||||
##### Pushbullet
|
||||
|
||||
```yaml
|
||||
services:
|
||||
pushbullet:
|
||||
enabled: true
|
||||
api_token: "$PUSHBULLET_TOKEN"
|
||||
device_nicknames:
|
||||
- "MyPhone"
|
||||
```
|
||||
|
||||
##### Pushbullet SMS
|
||||
|
||||
```yaml
|
||||
services:
|
||||
pushbullet_sms:
|
||||
enabled: true
|
||||
api_token: "$PUSHBULLET_TOKEN"
|
||||
device_nickname: "MyPhone"
|
||||
phone_numbers:
|
||||
- "+15557654321"
|
||||
```
|
||||
|
||||
##### Pushover
|
||||
|
||||
```yaml
|
||||
services:
|
||||
pushover:
|
||||
enabled: true
|
||||
app_token: "$PUSHOVER_APP_TOKEN"
|
||||
recipients:
|
||||
- "$PUSHOVER_USER_KEY"
|
||||
```
|
||||
|
||||
##### Reddit
|
||||
|
||||
```yaml
|
||||
services:
|
||||
reddit:
|
||||
enabled: true
|
||||
client_id: "$REDDIT_CLIENT_ID"
|
||||
client_secret: "$REDDIT_CLIENT_SECRET"
|
||||
username: "$REDDIT_USERNAME"
|
||||
password: "$REDDIT_PASSWORD"
|
||||
recipients:
|
||||
- "target_user"
|
||||
```
|
||||
|
||||
##### Rocket.Chat
|
||||
|
||||
```yaml
|
||||
services:
|
||||
rocketchat:
|
||||
enabled: true
|
||||
server_url: "chat.example.com"
|
||||
scheme: "https"
|
||||
user_id: "$ROCKETCHAT_USER_ID"
|
||||
token: "$ROCKETCHAT_TOKEN"
|
||||
channels:
|
||||
- "#ops"
|
||||
```
|
||||
|
||||
##### SendGrid
|
||||
|
||||
```yaml
|
||||
services:
|
||||
sendgrid:
|
||||
enabled: true
|
||||
api_key: "$SENDGRID_API_KEY"
|
||||
sender_address: "[email protected]"
|
||||
sender_name: "Sliver"
|
||||
receivers:
|
||||
- "[email protected]"
|
||||
```
|
||||
|
||||
##### Slack
|
||||
|
||||
```yaml
|
||||
services:
|
||||
slack:
|
||||
enabled: true
|
||||
api_token: "$SLIVER_SLACK_TOKEN"
|
||||
channels:
|
||||
- "C0123456789"
|
||||
```
|
||||
|
||||
##### Syslog
|
||||
|
||||
```yaml
|
||||
services:
|
||||
syslog:
|
||||
enabled: true
|
||||
priority: "info"
|
||||
network: "udp"
|
||||
address: "127.0.0.1:514"
|
||||
tag: "sliver"
|
||||
```
|
||||
|
||||
##### Telegram
|
||||
|
||||
```yaml
|
||||
services:
|
||||
telegram:
|
||||
enabled: true
|
||||
api_token: "$SLIVER_TELEGRAM_TOKEN"
|
||||
chat_ids:
|
||||
- "123456789"
|
||||
parse_mode: "HTML"
|
||||
```
|
||||
|
||||
##### TextMagic
|
||||
|
||||
```yaml
|
||||
services:
|
||||
textmagic:
|
||||
enabled: true
|
||||
username: "$TEXTMAGIC_USER"
|
||||
api_key: "$TEXTMAGIC_API_KEY"
|
||||
phone_numbers:
|
||||
- "+15557654321"
|
||||
```
|
||||
|
||||
##### Twilio
|
||||
|
||||
```yaml
|
||||
services:
|
||||
twilio:
|
||||
enabled: true
|
||||
account_sid: "$TWILIO_ACCOUNT_SID"
|
||||
auth_token: "$TWILIO_AUTH_TOKEN"
|
||||
from_number: "+15551234567"
|
||||
phone_numbers:
|
||||
- "+15557654321"
|
||||
```
|
||||
|
||||
##### Twitter
|
||||
|
||||
```yaml
|
||||
services:
|
||||
twitter:
|
||||
enabled: true
|
||||
consumer_key: "$TWITTER_CONSUMER_KEY"
|
||||
consumer_secret: "$TWITTER_CONSUMER_SECRET"
|
||||
access_token: "$TWITTER_ACCESS_TOKEN"
|
||||
access_token_secret: "$TWITTER_ACCESS_TOKEN_SECRET"
|
||||
recipients:
|
||||
- "1234567890"
|
||||
```
|
||||
|
||||
##### Viber
|
||||
|
||||
```yaml
|
||||
services:
|
||||
viber:
|
||||
enabled: true
|
||||
app_key: "$VIBER_APP_KEY"
|
||||
sender_name: "Sliver"
|
||||
sender_avatar: "https://example.com/avatar.png"
|
||||
webhook_url: "https://example.com/viber-webhook"
|
||||
receivers:
|
||||
- "viber-user-id"
|
||||
```
|
||||
|
||||
##### Web Push
|
||||
|
||||
```yaml
|
||||
services:
|
||||
webpush:
|
||||
enabled: true
|
||||
vapid_public_key: "$WEBPUSH_PUBLIC_KEY"
|
||||
vapid_private_key: "$WEBPUSH_PRIVATE_KEY"
|
||||
subscriptions:
|
||||
- endpoint: "https://push.example.com/123"
|
||||
keys:
|
||||
p256dh: "$WEBPUSH_P256DH"
|
||||
auth: "$WEBPUSH_AUTH"
|
||||
```
|
||||
|
||||
##### WeChat
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wechat:
|
||||
enabled: true
|
||||
app_id: "$WECHAT_APP_ID"
|
||||
app_secret: "$WECHAT_APP_SECRET"
|
||||
token: "$WECHAT_TOKEN"
|
||||
encoding_aes_key: "$WECHAT_AES_KEY"
|
||||
receivers:
|
||||
- "wechat-user-id"
|
||||
```
|
||||
|
||||
##### WhatsApp
|
||||
|
||||
```yaml
|
||||
services:
|
||||
whatsapp:
|
||||
enabled: true
|
||||
receivers:
|
||||
- "whatsapp-contact"
|
||||
```
|
||||
|
||||
#### Custom Templates
|
||||
|
||||
You can render notification messages using Go templates with either `template/text` or `template/html`. Templates are loaded from:
|
||||
|
||||
```
|
||||
<app dir>/notifications/templates/
|
||||
```
|
||||
|
||||
By default this is `~/.sliver/notifications/templates/`. Only a filename is allowed (no path separators) and `..` is rejected to prevent path traversal.
|
||||
|
||||
Configure per-event templates under `notifications.templates`:
|
||||
|
||||
```yaml
|
||||
notifications:
|
||||
enabled: true
|
||||
templates:
|
||||
session-connected:
|
||||
type: "text" # text (default) or html
|
||||
template: "session.tmpl"
|
||||
session-disconnected:
|
||||
type: "html"
|
||||
template: "session.html"
|
||||
```
|
||||
|
||||
Template data available:
|
||||
|
||||
- `.EventType`
|
||||
- `.Session`
|
||||
- `.Beacon`
|
||||
- `.Job`
|
||||
- `.Client`
|
||||
- `.Error`
|
||||
- `.DefaultSubject`
|
||||
- `.DefaultMessage`
|
||||
- `.Timestamp` (UTC)
|
||||
|
||||
Example `session.tmpl` (text):
|
||||
|
||||
```txt
|
||||
{{ .DefaultSubject }}
|
||||
Event: {{ .EventType }}
|
||||
Host: {{ if .Session }}{{ .Session.Hostname }}{{ end }}
|
||||
User: {{ if .Session }}{{ .Session.Username }}{{ end }}
|
||||
```
|
||||
|
||||
Example `session.html` (html):
|
||||
|
||||
```html
|
||||
<h3>{{ .DefaultSubject }}</h3>
|
||||
<p>Event: {{ .EventType }}</p>
|
||||
<p>Host: {{ if .Session }}{{ .Session.Hostname }}{{ end }}</p>
|
||||
<p>User: {{ if .Session }}{{ .Session.Username }}{{ end }}</p>
|
||||
```
|
||||
|
||||
#### Notes
|
||||
|
||||
- Environment variable expansion only applies to string fields (API keys, tokens, URLs, etc.).
|
||||
- If a service configuration is incomplete, that service is skipped and the server continues without it.
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.
|
||||
|
||||
First time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
Sliver implants support two types of connections, sessions and beacons.
|
||||
|
||||
Sessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
When generating implants Sliver uses a C2Profile configuration to define how implant callbacks will look over HTTP/s. This allowed operators to control headers, cookies proxies, URL paths and so on. When the implant is generated the sliver server will then select portions of that configuration to generate the implant C2Profile.
|
||||
|
||||
For example if configured to use `/admin` and `/demo` as callback urls, it might use one, the other or both depending on your configuration allowing two implants using the same configuration to still seem slightly different from a network traffic perspective.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
When using Sliver during a live engagement, you’re going to need to use custom stagers, which are essentially a first binary or commandline that will retrieve and/or load Sliver into memory on your target system. Sliver can generate shellcode for your stager to execute by using the `profiles` command.
|
||||
|
||||
For this exercise, we will create a new beacon profile and prepare to stage it.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
Pivots allow routing implant traffic through other implants. This can be useful in environments that don’t have any outbound access, but are reachable from other parts of the network that you have access to.
|
||||
|
||||
Sliver supports two types of pivots: TCP, which can be used on all operating systems, and named pipes, which are Windows-only.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
Reactions are a basic way to automate tasks in the sliver console, they allow you to specify sliver commands to run on a list of events.
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# This course is intended for the 1.6 version of Sliver, which is not yet published
|
||||
|
||||
The Sliver armory is used to install and maintain third party extensions and aliases within sliver. The full list of available extensions can be found at https://github.com/sliverarmory/armory, keep in mind this is community maintained so not all modules are necessarily up to date.
|
||||
|
||||
You can download all configured extensions/aliases using the armory command.
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"tutorials":[{"name":"1 - Getting Started","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\n`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"# This course is intended for the 1.6 version of Sliver, which is not yet published\n\nSliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n Line truncated
|
||||
{"tutorials":[{"name":"1 - Getting Started","content":"`sliver-server` is the binary you want to use to run the Sliver C2 server, `sliver-client` is solely a client to connect to a Sliver C2 server. Sliver server also acts as a client on its own, so you don’t necessarily run sliver server and client separately.\n\nFirst time running Sliver will take a couple seconds as it's retrieving its dependencies. Consecutive executions will be much faster. Go ahead and launch the `sliver-server`.\n\n```asciinema\n{\"src\": \"/asciinema/startup.cast\", \"cols\": \"132\", \"rows\": \"28\", \"idleTimeLimit\": 8}\n```\n\nLet's take a couple minutes to discuss what Sliver actually is and how it's set up.\n\n\n\nNow that Sliver is running, lets generate and execute your first implant to try out some of the basic features of Sliver, for now we’re going to run everything on the local host.\n\nHere's what we're going to do: \n* Generate your implant using the `generate` command as shown below.\n* Start HTTP listener on port 80\n* Execute implant in a separate terminal\n\n```asciinema\n{\"src\": \"/asciinema/first-implant.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNow let’s select our implant and run our first command using the `use` command.\n\n```bash\n[server] sliver > use\n? Select a session or beacon: \nSESSION 1884a365 RELATED_EARDRUM [::1]:49153 test.local tester darwin/amd64\n[*] Active session RELATED_EARDRUM (1884a365-085f-4506-b28e-80c481730fd0)\n\n[server] sliver (RELATED_EARDRUM) > pwd\n\n[*] /Users/tester/tools\n```\n\nOnce you have reached this point, go ahead and explore some of the commands listed below. In each case, first check out the command's help using the **`-h`** flag then try it out!\n\n```bash\nExploring and interacting with the filesystem\n\nFilesystem\n cat Dump file to stdout\n cd Change directory\n cp Copy a file\n download Download a file\n grep Search for strings that match a regex within a file or directory\n head Grab the first number of bytes or lines from a file\n ls List current directory\n mkdir Make a directory\n mv Move or rename a file\n pwd Print working directory\n rm Remove a file or directory\n tail Grab the last number of bytes or lines from a file\n upload Upload a file\n```\n\n```asciinema\n{\"src\": \"/asciinema/filesystem.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nGetting some environmental information\n```bash\nInfo\n env List environment variables\n getgid Get session process GID\n getpid Get session pid\n getuid Get session process UID\n info Get session info\n ping Send round trip message to implant (does not use ICMP)\n whoami Get session user execution context\n```\nExecute a binary\n\n```asciinema\n{\"src\": \"/asciinema/execute.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nRunning an interactive shell\n\n```asciinema\n{\"src\": \"/asciinema/shell.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nNot all commands are supported for all operating systems or meant to be used on all operating systems, here are a few examples:\n\n```\nmount Get information on mounted filesystems (only for Windows and Linux)\nscreenshot Take a screenshot (only for Windows and Linux)\nmemfiles List current memfiles (Linux only)\n```\n\nThis is an open-source project if you see a bug please file an issue! Pull requests are always welcome."},{"name":"2 - Beacons vs Sessions","content":"Sliver implants support two types of connections, sessions and beacons.\n\nSessions use long-polling connections, which means they constantly maintain a single TCP connection open while communicating with the server. Beacons on the other hand call back periodically and will sleep when not active which can help keep their presence hidden. You could limit callbacks to once every 6 hours for example and only 'activate' the beacon when needed by switching its sleeping time to a shorter duration.\n\nTypically during an engagement you will want to deploy a beacon on the target system and switch to a session while doing more active enumeration.\n\nLet’s start with generating and deploying a beacon using `http`.\n\n```asciinema\n{\"src\": \"/asciinema/beacon_generation.cast\", \"cols\": \"132\", \"rows\": \"14\", \"idleTimeLimit\": 8}\n```\n\nYou can see the beacon callback times either in the `info` command or using `beacons watch`.\n\n```bash\n[server] sliver > beacons watch\n\n ID Name Transport Username Operating System Last Check-In Next Check-In\n========== =============== =========== ================= ==========Line truncated
|
||||
@@ -4,7 +4,9 @@ go 1.25.5
|
||||
|
||||
replace github.com/rsteube/carapace v0.36.3 => github.com/reeflective/carapace v0.46.3-0.20231214181515-27e49f3c3b69
|
||||
|
||||
replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
|
||||
replace github.com/reeflective/readline => github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a
|
||||
|
||||
replace github.com/reeflective/console => github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701
|
||||
|
||||
require (
|
||||
filippo.io/age v1.3.1
|
||||
@@ -13,6 +15,7 @@ require (
|
||||
github.com/Binject/go-donut v0.0.0-20210701074227-67a31e2d883e
|
||||
github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0
|
||||
github.com/Ne0nd0g/go-clr v1.0.3
|
||||
github.com/SherClockHolmes/webpush-go v1.4.0
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/charmbracelet/huh v0.8.0
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20251215014908-6f7d32faaff3
|
||||
@@ -36,10 +39,11 @@ require (
|
||||
github.com/miekg/dns v1.1.70
|
||||
github.com/moloch--/asciicast v0.1.1
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e
|
||||
github.com/moloch--/sgn v0.0.4
|
||||
github.com/moloch--/sgn v0.0.5
|
||||
github.com/ncruces/go-sqlite3 v0.30.4
|
||||
github.com/nikoksr/notify v1.5.0
|
||||
github.com/reeflective/console v0.1.25
|
||||
github.com/reeflective/readline v1.1.3
|
||||
github.com/reeflective/readline v1.1.4
|
||||
github.com/rsteube/carapace v0.50.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.10.2
|
||||
@@ -51,7 +55,7 @@ require (
|
||||
github.com/xlab/treeprint v1.2.0
|
||||
github.com/yiya1989/sshkrb5 v0.0.1
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9
|
||||
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9
|
||||
golang.org/x/mod v0.31.0
|
||||
golang.org/x/net v0.48.0
|
||||
golang.org/x/sys v0.40.0
|
||||
@@ -59,32 +63,144 @@ require (
|
||||
golang.org/x/text v0.32.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||
google.golang.org/grpc v1.76.0
|
||||
google.golang.org/api v0.257.0
|
||||
google.golang.org/grpc v1.77.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.0
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c
|
||||
maunium.net/go/mautrix v0.26.0
|
||||
modernc.org/sqlite v1.39.0
|
||||
tailscale.com v1.92.5
|
||||
)
|
||||
|
||||
require (
|
||||
cel.dev/expr v0.25.1 // indirect
|
||||
cloud.google.com/go v0.123.0 // indirect
|
||||
cloud.google.com/go/auth v0.17.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/firestore v1.20.0 // indirect
|
||||
cloud.google.com/go/iam v1.5.3 // indirect
|
||||
cloud.google.com/go/longrunning v0.7.0 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.3 // indirect
|
||||
cloud.google.com/go/storage v1.58.0 // indirect
|
||||
filippo.io/hpke v0.4.0 // indirect
|
||||
firebase.google.com/go/v4 v4.18.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 // indirect
|
||||
github.com/MicahParks/keyfunc v1.9.0 // indirect
|
||||
github.com/PagerDuty/go-pagerduty v1.8.0 // indirect
|
||||
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20250718055228-285ecf400b48 // indirect
|
||||
github.com/appleboy/go-fcm v1.2.6 // indirect
|
||||
github.com/atc0005/go-teams-notify/v2 v2.14.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ses v1.34.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.39.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/bwmarrin/discordgo v0.29.0 // indirect
|
||||
github.com/caarlos0/go-reddit/v3 v3.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/creachadair/msync v0.7.1 // indirect
|
||||
github.com/cschomburg/go-pushbullet v0.0.0-20171206132031-67759df45fbb // indirect
|
||||
github.com/dghubble/oauth1 v0.7.3 // indirect
|
||||
github.com/dghubble/sling v1.4.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-lark/lark v1.16.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/gregdel/pushover v1.4.0 // indirect
|
||||
github.com/invopop/jsonschema v0.13.0 // indirect
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kevinburke/go-types v0.0.0-20240719050749-165e75e768f7 // indirect
|
||||
github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f // indirect
|
||||
github.com/kevinburke/twilio-go v0.0.0-20250718182727-5fe7adc01f29 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible // indirect
|
||||
github.com/mailgun/errors v0.4.0 // indirect
|
||||
github.com/mailgun/mailgun-go/v5 v5.8.1 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mileusna/viber v1.0.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/oapi-codegen/runtime v1.1.2 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
|
||||
github.com/plivo/plivo-go/v7 v7.59.2 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/sendgrid/rest v2.6.9+incompatible // indirect
|
||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
|
||||
github.com/silenceper/wechat/v2 v2.1.11 // indirect
|
||||
github.com/slack-go/slack v0.17.3 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/textmagic/textmagic-rest-go-v2/v3 v3.0.43885 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
|
||||
github.com/ttacon/libphonenumber v1.2.1 // indirect
|
||||
github.com/utahta/go-linenotify v0.5.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.mau.fi/util v0.9.3 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
google.golang.org/appengine/v2 v2.0.6 // indirect
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.7.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Binject/shellcode v0.0.0-20191101084904-a8a90e7d4563 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
@@ -95,12 +211,12 @@ require (
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 // indirect
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/carapace-sh/carapace v1.9.0 // indirect
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0 // indirect
|
||||
github.com/carapace-sh/carapace v1.11.0
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||
@@ -111,7 +227,7 @@ require (
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/coder/websocket v1.8.12 // indirect
|
||||
github.com/coder/websocket v1.8.14 // indirect
|
||||
github.com/coreos/go-iptables v0.8.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d // indirect
|
||||
@@ -182,19 +298,19 @@ require (
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
|
||||
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/libc v1.66.10 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
|
||||
@@ -2,9 +2,31 @@
|
||||
9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM=
|
||||
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
|
||||
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
|
||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
|
||||
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
|
||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||
cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
|
||||
cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
cloud.google.com/go/firestore v1.20.0 h1:JLlT12QP0fM2SJirKVyu2spBCO8leElaW0OOtPm6HEo=
|
||||
cloud.google.com/go/firestore v1.20.0/go.mod h1:jqu4yKdBmDN5srneWzx3HlKrHFWFdlkgjgQ6BKIOFQo=
|
||||
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
||||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||
cloud.google.com/go/logging v1.13.1 h1:O7LvmO0kGLaHY/gq8cV7T0dyp6zJhYAOtZPX4TF3QtY=
|
||||
cloud.google.com/go/logging v1.13.1/go.mod h1:XAQkfkMBxQRjQek96WLPNze7vsOmay9H5PqfsNYDqvw=
|
||||
cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E=
|
||||
cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY=
|
||||
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
||||
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
||||
cloud.google.com/go/storage v1.58.0 h1:PflFXlmFJjG/nBeR9B7pKddLQWaFaRWx4uUi/LyNxxo=
|
||||
cloud.google.com/go/storage v1.58.0/go.mod h1:cMWbtM+anpC74gn6qjLh+exqYcfmB9Hqe5z6adx+CLI=
|
||||
cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U=
|
||||
cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s=
|
||||
filippo.io/age v1.3.1 h1:hbzdQOJkuaMEpRCLSN1/C5DX74RPcNCk6oqhKMXmZi0=
|
||||
filippo.io/age v1.3.1/go.mod h1:EZorDTYUxt836i3zdori5IJX/v2Lj6kWFU0cfh6C0D4=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
@@ -13,6 +35,8 @@ filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
|
||||
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
|
||||
filippo.io/mkcert v1.4.4 h1:8eVbbwfVlaqUM7OwuftKc2nuYOoTDQWqsoXmzoXZdbc=
|
||||
filippo.io/mkcert v1.4.4/go.mod h1:VyvOchVuAye3BoUsPUOOofKygVwLV2KQMVFJNRq+1dA=
|
||||
firebase.google.com/go/v4 v4.18.0 h1:S+g0P72oDGqOaG4wlLErX3zQmU9plVdu7j+Bc3R1qFw=
|
||||
firebase.google.com/go/v4 v4.18.0/go.mod h1:P7UfBpzc8+Z3MckX79+zsWzKVfpGryr6HLbAe7gCWfs=
|
||||
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb h1:c1Bb1n1wr2xgTHkNVxkUHBshff33m+IXgY6lE6oYttg=
|
||||
github.com/Binject/binjection v0.0.0-20210701074423-605d46e35deb/go.mod h1:fSQ9asCJK/lIGXUnoRwJNMXcnzel53AsZMCtlVBIn3k=
|
||||
github.com/Binject/debug v0.0.0-20210225042342-c9b8b45728d2/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ=
|
||||
@@ -28,12 +52,30 @@ github.com/Binject/universal v0.0.0-20220519011857-bea739e758c0/go.mod h1:J3XDRl
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs=
|
||||
github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0 h1:sBEjpZlNHzK1voKq9695PJSX2o5NEXl7/OL3coiIY0c=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0 h1:xfK3bbi6F2RDtaZFtUdKO3osOBIhNb+xTs8lFW6yx9o=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.54.0/go.mod h1:vB2GH9GAYYJTO3mEn8oYwzEdhlayZIdQz6zdzgUIRvA=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo=
|
||||
github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
|
||||
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Ne0nd0g/go-clr v1.0.3 h1:xt92wwuqY23ZSC7RuHD3mKu3K22Bk5NNbxI803vojK4=
|
||||
github.com/Ne0nd0g/go-clr v1.0.3/go.mod h1:TKYSQ/5xT25EvBUttAlUrzpR8yHuI0qTRK495I5xG/I=
|
||||
github.com/PagerDuty/go-pagerduty v1.8.0 h1:MTFqTffIcAervB83U7Bx6HERzLbyaSPL/+oxH3zyluI=
|
||||
github.com/PagerDuty/go-pagerduty v1.8.0/go.mod h1:nzIeAqyFSJAFkjWKvMzug0JtwDg+V+UoCWjFrfFH5mI=
|
||||
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20250718055228-285ecf400b48 h1:I4Uc+H5JgZ2o/o6AGz2fIQCf/pVio9mQDVffXkDVc24=
|
||||
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20250718055228-285ecf400b48/go.mod h1:rjP7sIipbZcagro/6TCk6X0ZeFT2eyudH5+fve/cbBA=
|
||||
github.com/SherClockHolmes/webpush-go v1.4.0 h1:ocnzNKWN23T9nvHi6IfyrQjkIc0oJWv1B1pULsf9i3s=
|
||||
github.com/SherClockHolmes/webpush-go v1.4.0/go.mod h1:XSq8pKX11vNV8MJEMwjrlTkxhAj1zKfxmyhdV7Pd6UA=
|
||||
github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
|
||||
github.com/VirusTotal/vt-go v1.0.1 h1:rj/qugIY8XNC6ogwOaeJAGCOsb3nmY63+yuDMtHOx0Q=
|
||||
github.com/VirusTotal/vt-go v1.0.1/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE=
|
||||
@@ -46,42 +88,56 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbf
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
|
||||
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/appleboy/go-fcm v1.2.6 h1:TU5/+2QnmTNjWkHLe9hUB9EPJ5Bv+CegzTJI0qK0QgA=
|
||||
github.com/appleboy/go-fcm v1.2.6/go.mod h1:nvi8DgoMax8o6nwQYgO8pIXSX6iaQY7yDYvtwIGa6aI=
|
||||
github.com/atc0005/go-teams-notify/v2 v2.14.0 h1:7N+xw+COnYANLREaAveQ65rsNQ12nIZJED9nMLyscCo=
|
||||
github.com/atc0005/go-teams-notify/v2 v2.14.0/go.mod h1:EECsWM2b0Hvoz7O+QdlsvyN2KCUOFQCGj8bUBXv3A3Q=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0 h1:JjwxKkxzcBk4k8147g0eBQRCIy0UN1Be8AAv6RaIj4Q=
|
||||
github.com/awgh/cppgo v0.0.0-20210224085512-3d24bca8edc0/go.mod h1:IbERvuyb387Hppp8hX0SQTFt/mkej8+OhuS8L0nC2CI=
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpFRNJrgZMwvy61BEVpGoHc2n4/yWs=
|
||||
github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4/go.mod h1:SalMPBCab3yuID8nIhLfzwoBV+lBRyaC7NhuN8qL8xE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I=
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ses v1.34.17 h1:XR7CtY988tck2Bhuy1JP4FsV8z0OAwjuh+gb7nAy8/M=
|
||||
github.com/aws/aws-sdk-go-v2/service/ses v1.34.17/go.mod h1:2CspeTVldnJdRixX36SzTZuoIpjyKlfeXyB7/JB5KGk=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.39.10 h1:wqErrLzV3iERQ7dbZbKQS0gOM6ngxZtmPwKyRGn+Krc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sns v1.39.10/go.mod h1:OiwBtRz6QlQyt69WLBMvSiyfgI7cOd6xSJ9ThTMjI5M=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 h1:TFg6XiS7EsHN0/jpV3eVNczZi/sPIVP5jxIs+euIESQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1/go.mod h1:OIezd9K0sM/64DDP4kXx/i0NdgXu6R5KE6SCsIPJsjc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8=
|
||||
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
|
||||
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk=
|
||||
github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
|
||||
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||
@@ -89,15 +145,29 @@ github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl
|
||||
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
|
||||
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/carapace-sh/carapace v1.9.0 h1:IuEP6YeeK4fhrH7MoOyL0I3GMhvROerkw6YGxkx8Fbw=
|
||||
github.com/carapace-sh/carapace v1.9.0/go.mod h1:Zs3DpsawpFm+8fEfRVYE9EzMBYH8u7S3Aw9yAAKX0JM=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0 h1:58QSJTUxghpa/rEn6+Z9UM/Hy0ipVHDUn9S1mp4i+ZE=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
|
||||
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/caarlos0/go-reddit/v3 v3.0.1 h1:w8ugvsrHhaE/m4ez0BO/sTBOBWI9WZTjG7VTecHnql4=
|
||||
github.com/caarlos0/go-reddit/v3 v3.0.1/go.mod h1:QlwgmG5SAqxMeQvg/A2dD1x9cIZCO56BMnMdjXLoisI=
|
||||
github.com/carapace-sh/carapace v1.11.0 h1:dTUFXvIrUTLd9BGLsfDt4wCgEyPl8bJeZOFDmN7fIfo=
|
||||
github.com/carapace-sh/carapace v1.11.0/go.mod h1:NVkuv1s8wyFvu7d98rdgYwqGKMB6GO1MEUZp5RcWri8=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1 h1:ccmNeetAYZOk4IcV36youFDsXusT9uCNW2Njkw+QS+Q=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
@@ -136,16 +206,22 @@ github.com/chromedp/chromedp v0.14.1 h1:0uAbnxewy/Q+Bg7oafVePE/6EXEho9hnaC38f+TT
|
||||
github.com/chromedp/chromedp v0.14.1/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
|
||||
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
|
||||
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
|
||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creachadair/msync v0.7.1 h1:SeZmuEBXQPe5GqV/C94ER7QIZPwtvFbeQiykzt/7uho=
|
||||
@@ -154,14 +230,23 @@ github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoi
|
||||
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/cschomburg/go-pushbullet v0.0.0-20171206132031-67759df45fbb h1:7X9nrm+LNWdxzQOiCjy0G51rNUxbH35IDHCjAMvogyM=
|
||||
github.com/cschomburg/go-pushbullet v0.0.0-20171206132031-67759df45fbb/go.mod h1:RfQ9wji3fjcSEsQ+uFCtIh3+BXgcZum8Kt3JxvzYzlk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d h1:QRKpU+9ZBDs62LyBfwhZkJdB5DJX2Sm3p4kUh7l1aA0=
|
||||
github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d/go.mod h1:SUxUaAK/0UG5lYyZR1L1nC4AaYYvSSYTWQSH3FPcxKU=
|
||||
github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d h1:hmOGJg3cq5XK2aMs7R4kXXVSHqHMaC5hI5fwkX7V2zE=
|
||||
github.com/demisto/goxforce v0.0.0-20160322194047-db8357535b1d/go.mod h1:q72QzdO6OUjwTqnLCFJczIQ7GsBa4ffzkQiQcq6rVTY=
|
||||
github.com/dghubble/oauth1 v0.7.3 h1:EkEM/zMDMp3zOsX2DC/ZQ2vnEX3ELK0/l9kb+vs4ptE=
|
||||
github.com/dghubble/oauth1 v0.7.3/go.mod h1:oxTe+az9NSMIucDPDCCtzJGsPhciJV33xocHfcR2sVY=
|
||||
github.com/dghubble/sling v1.4.2 h1:vs1HIGBbSl2SEALyU+irpYFLZMfc49Fp+jYryFebQjM=
|
||||
github.com/dghubble/sling v1.4.2/go.mod h1:o0arCOz0HwfqYQJLrRtqunaWOn4X6jxE/6ORKRpVTD4=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
@@ -169,19 +254,37 @@ github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYC
|
||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 h1:uh1GSejOhVPRQmoXZxY82TiewZB8QXiaP1skL7Nun3Y=
|
||||
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7/go.mod h1:ncTaGuXc5v7AuiVekeJ0Nwh8Bf4cudukoj0qM/15UZE=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=
|
||||
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gaissmai/bart v0.25.0 h1:eqiokVPqM3F94vJ0bTHXHtH91S8zkKL+bKh+BsGOsJM=
|
||||
@@ -194,21 +297,41 @@ github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3 h1:02WINGfSX5w0Mn+F28UyRoSt9uvMhKguwWMlOAh6U/0=
|
||||
github.com/go-json-experiment/json v0.0.0-20250910080747-cc2cfa0554c3/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-lark/lark v1.16.0 h1:U6BwkLM9wrZedSM7cIiMofganr8PCvJN+M75w2lf2Gg=
|
||||
github.com/go-lark/lark v1.16.0/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc h1:jZY+lpZB92nvBo2f31oPC/ivGll6NcsnEOORm8Fkr4M=
|
||||
github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc/go.mod h1:25mL1NKxbJhB63ihiK8MnNeTRd+xAizd6bOdydrTLUQ=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
|
||||
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
@@ -217,12 +340,20 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh3PFscJRLDnkL547IeP7kpPe3uUhEg=
|
||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
@@ -230,31 +361,69 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I=
|
||||
github.com/google/go-tpm v0.9.4/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
|
||||
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
|
||||
github.com/google/nftables v0.3.0 h1:bkyZ0cbpVeMHXOrtlFc8ISmfVqq5gPJukoYieyVmITg=
|
||||
github.com/google/nftables v0.3.0/go.mod h1:BCp9FsrbF1Fn/Yu6CLUc9GGZFw/+hsxfluNXXmxBfRM=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
|
||||
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
|
||||
github.com/google/uuid v0.0.0-20171113160352-8c31c18f31ed/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
|
||||
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
|
||||
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 h1:4EZlYQIiyecYJlUbVkFXCXHz1QPhVXcHnQKAzBTPfQo=
|
||||
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4/go.mod h1:lEO7XoHJ/xNRBCxrn4h/CEB67h0kW1B0t4ooP2yrjUA=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregdel/pushover v1.4.0 h1:P77WAJ2zPG+b0mEsmMjWGrPMuvhkh9k3v7OviwsoveE=
|
||||
github.com/gregdel/pushover v1.4.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk=
|
||||
github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
@@ -284,13 +453,25 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90=
|
||||
github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018 h1:NQYgMY188uWrS+E/7xMVpydsI48PMHcc7SfR4OxkDF4=
|
||||
github.com/kbinani/screenshot v0.0.0-20250624051815-089614a94018/go.mod h1:Pmpz2BLf55auQZ67u3rvyI2vAQvNetkK/4zYUmpauZQ=
|
||||
github.com/kevinburke/go-types v0.0.0-20240719050749-165e75e768f7 h1:36PMhfw/I1YYAjOOuA66ll5X7NJ8v3cJEqsAxiMv7bE=
|
||||
github.com/kevinburke/go-types v0.0.0-20240719050749-165e75e768f7/go.mod h1:8tQOif9eUJLpDnvfDcGtesfv6VpL2UvDbW4l8kXnSDE=
|
||||
github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f h1:y+inhBsY0ewgXFXCXlxodNxkXdYeU9YuneCYQEnRkmw=
|
||||
github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f/go.mod h1:3cBF15uOiTj025Ll5QHLw317EB+e06+AEwyt7oHUubI=
|
||||
github.com/kevinburke/twilio-go v0.0.0-20250718182727-5fe7adc01f29 h1:AZ5D/KUEro9jOlMv94YJoufveb3vwM5WBwQ0Pfsh+XI=
|
||||
github.com/kevinburke/twilio-go v0.0.0-20250718182727-5fe7adc01f29/go.mod h1:Z7bqFOTtIqCCIO+uPtod7Fp3gPMR8aTCcp5Pe8D+3fE=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||
@@ -309,22 +490,34 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lesnuages/go-winio v0.4.19 h1:lFDu1mnhg5em+8zTHO4ChSD11J56xkyE8m3N8IrVmbA=
|
||||
github.com/lesnuages/go-winio v0.4.19/go.mod h1:rm7jf4kBcldxMeljR7c7XY1qVCBc+8z3PtSgkrT9Clk=
|
||||
github.com/lesnuages/snitch v0.6.0 h1:vOao32MdYDHYDcTAq2CKszDh6Dn8Bv1Gmgc8uVh193U=
|
||||
github.com/lesnuages/snitch v0.6.0/go.mod h1:RfXlAVJYL7gUoZ9VMMon4LqrPiAjC8x62Ntbse4a4s0=
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible h1:Uf9/OxV0zCVfqyvwZPH8CrdiHXXmMRa/L91G3btQblQ=
|
||||
github.com/line/line-bot-sdk-go v7.8.0+incompatible/go.mod h1:0RjLjJEAU/3GIcHkC3av6O4jInAbt25nnZVmOFUgDBg=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
|
||||
github.com/mailgun/errors v0.4.0 h1:6LFBvod6VIW83CMIOT9sYNp28TCX0NejFPP4dSX++i8=
|
||||
github.com/mailgun/errors v0.4.0/go.mod h1:xGBaaKdEdQT0/FhwvoXv4oBaqqmVZz9P1XEnvD/onc0=
|
||||
github.com/mailgun/mailgun-go/v5 v5.8.1 h1:QoRa2oBqLq+8GTYgM4id/cKW/WySQRka3PtnW8IK6KQ=
|
||||
github.com/mailgun/mailgun-go/v5 v5.8.1/go.mod h1:qNTXXuJi9/myqpDLI8Mbn54WCXdto1kEHm6I2/WWYQQ=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
|
||||
github.com/mark3labs/mcp-go v0.43.2/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
@@ -344,20 +537,29 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos
|
||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||
github.com/miekg/dns v1.1.70 h1:DZ4u2AV35VJxdD9Fo9fIWm119BsQL5cZU1cQ9s0LkqA=
|
||||
github.com/miekg/dns v1.1.70/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
github.com/mileusna/viber v1.0.1 h1:gWB6/lKoWYVxkH0Jb8jRnGIRZ/9DEM7RBZRJHRfdYWs=
|
||||
github.com/mileusna/viber v1.0.1/go.mod h1:Pxu/iPMnYjnHgu+bEp3SiKWHWmlf/kDp/yOX8XUdYrQ=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/moloch--/asciicast v0.1.1 h1:btwwniCK9br3ys33XZ71IG/M3ZujSSCcBzsH/sieiXg=
|
||||
github.com/moloch--/asciicast v0.1.1/go.mod h1:OckO16UDLgxVLclrCnbocL1ix15Br/8Xv/caBoYq98o=
|
||||
github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701 h1:6caCgDZpovjvv+eclRytFDf0GpVEnjDmoJRHpfkb42E=
|
||||
github.com/moloch--/console v0.0.0-20260129035459-883dcb25c701/go.mod h1:UdqyqqCSnPu3Zc8Q3kpDszrq1hcyBHsG17hlWlRJmWI=
|
||||
github.com/moloch--/go-keystone v0.0.2 h1:xThbYs45CSzoDUz5kBZxAyTPIpK3t5M7NB1x+NTTRL8=
|
||||
github.com/moloch--/go-keystone v0.0.2/go.mod h1:kpGRJpw9b7Eq3MluHrba+GPKZhSyDrvhLULSwH5nGGM=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e h1:IkFCPlAa0iTiLxck+NqAwBx8JDlnHYm4orOQBbs4BDQ=
|
||||
github.com/moloch--/memmod v0.0.0-20230225130813-fd77d905589e/go.mod h1:eYeI6cQ5YHhHt9i0BBW0zc1DaQnb4ZMXsSPuEV/V5Og=
|
||||
github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d h1:9RbKockJl6ZdgoEh2QqU3An6Ij/NWZtRWS3J6yG0+qo=
|
||||
github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
|
||||
github.com/moloch--/sgn v0.0.4 h1:90IC5rx3TmPEfrqPnENB0yyiqcbcNk9LqezXx7uruF0=
|
||||
github.com/moloch--/sgn v0.0.4/go.mod h1:FyWfCtdynywgLmVR71vTJztZ3WkGSkR91Y4EOijUutE=
|
||||
github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a h1:Q69DxLWF+Z8oIjuNbeKC6Xygk8Z4heWzzO79Io1cEYA=
|
||||
github.com/moloch--/readline v0.0.0-20260129035512-cb5b3e87d51a/go.mod h1:CwNkh9BmFBBCSO6mdDaNWb34rOqQsI9eYbxyqvOEazY=
|
||||
github.com/moloch--/sgn v0.0.5 h1:92GFaRpe+353uhqzFiNKXKgmTiD4dBxnvb8gd4MaV/g=
|
||||
github.com/moloch--/sgn v0.0.5/go.mod h1:FyWfCtdynywgLmVR71vTJztZ3WkGSkR91Y4EOijUutE=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
@@ -366,6 +568,7 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/ncruces/go-sqlite3 v0.30.4 h1:j9hEoOL7f9ZoXl8uqXVniaq1VNwlWAXihZbTvhqPPjA=
|
||||
github.com/ncruces/go-sqlite3 v0.30.4/go.mod h1:7WR20VSC5IZusKhUdiR9y1NsUqnZgqIYCmKKoMEYg68=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
@@ -374,8 +577,29 @@ github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nikoksr/notify v1.5.0 h1:mzkCw8eb0P+qHwgmGQyPPGqz4GH+07FJDr44Bs16T9k=
|
||||
github.com/nikoksr/notify v1.5.0/go.mod h1:CEV9Bw9Y59K5oj7d8h83Xl32ATeL43ZEg9qTQsfwcCc=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
|
||||
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
@@ -383,10 +607,16 @@ github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/plivo/plivo-go/v7 v7.59.2 h1:S3CGORMXXYOnuKXBQ1Sk9DtSbeXa+bRA4S3TypfpOQY=
|
||||
github.com/plivo/plivo-go/v7 v7.59.2/go.mod h1:ceCFoYEzQrtrJjLcU7HR/r6Vz2kAVSaylrL7SjGPymc=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@@ -397,14 +627,15 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/reeflective/console v0.1.25 h1:do/r6rcKH6pCQDEzh5PhoVFcqHkqcjd0N6tNjpmYWWQ=
|
||||
github.com/reeflective/console v0.1.25/go.mod h1:It/IqEjo0PowaBuxBS9pEi5EYUgmtvQd0ytMtlS77kk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/rsteube/carapace v0.50.2 h1:9nPOBmIKPySFQvtBRGghFafRCR09Cf9K4/BEo1M+VFE=
|
||||
github.com/rsteube/carapace v0.50.2/go.mod h1:syVOvI8e2rEEK/9aMZxfWuHvcnQK/EcnTV4roClEnLE=
|
||||
github.com/rsteube/carapace-shlex v0.1.2 h1:ZKjhIfXoCkEnzensMglTaLbkNOaLkmM8SCRshpJKx6s=
|
||||
@@ -412,28 +643,51 @@ github.com/rsteube/carapace-shlex v0.1.2/go.mod h1:zPw1dOFwvLPKStUy9g2BYKanI6bsQ
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/safchain/ethtool v0.6.2 h1:O3ZPFAKEUEfbtE6J/feEe2Ft7dIJ2Sy8t4SdMRiIMHY=
|
||||
github.com/safchain/ethtool v0.6.2/go.mod h1:VS7cn+bP3Px3rIq55xImBiZGHVLNyBh5dqG6dDQy8+I=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
|
||||
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
|
||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||
github.com/silenceper/wechat/v2 v2.1.11 h1:KA0iuhEpwMl9L3R0Kg8KSE23CEszMbnhjBf/L2EJnSw=
|
||||
github.com/silenceper/wechat/v2 v2.1.11/go.mod h1:7Iu3EhQYVtDUJAj+ZVRy8yom75ga7aDWv8RurLkVm0s=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/slack-go/slack v0.17.3 h1:zV5qO3Q+WJAQ/XwbGfNFrRMaJ5T/naqaonyPV/1TP4g=
|
||||
github.com/slack-go/slack v0.17.3/go.mod h1:X+UqOufi3LYQHDnMG1vxf0J8asC6+WllXrVrhl8/Prk=
|
||||
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
|
||||
github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5PseKfZGF4=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||
@@ -460,18 +714,40 @@ github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp
|
||||
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
|
||||
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/tetratelabs/wazero v1.11.0 h1:+gKemEuKCTevU4d7ZTzlsvgd1uaToIDtlQlmNbwqYhA=
|
||||
github.com/tetratelabs/wazero v1.11.0/go.mod h1:eV28rsN8Q+xwjogd7f4/Pp4xFxO7uOGbLcD/LzB1wiU=
|
||||
github.com/textmagic/textmagic-rest-go-v2/v3 v3.0.43885 h1:Ve/YElmvgqg7yUQewiPfTV3PgCp1Tlu2PcPI0f8rAgg=
|
||||
github.com/textmagic/textmagic-rest-go-v2/v3 v3.0.43885/go.mod h1:76AjUSm7GRxmgio8I/XbwaO5Vvw40HdSD/W3Z3M+OWo=
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2 h1:CoMVaYyKFsVj6TjU6APqAhAvC07hTI6IQen8PHzHYY0=
|
||||
github.com/thedevsaddam/gojsonq/v2 v2.5.2/go.mod h1:bv6Xa7kWy82uT0LnXPE2SzGqTj33TAEeR560MdJkiXs=
|
||||
github.com/things-go/go-socks5 v0.1.0 h1:4f5dz0iMQ6cA4wseFmyLmCHmg3SWJTW92ndrKS6oERg=
|
||||
github.com/things-go/go-socks5 v0.1.0/go.mod h1:Riabiyu52kLsla0YmJqunt1c1JEl6iXSr4bRd7swFEA=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0=
|
||||
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w=
|
||||
github.com/ttacon/libphonenumber v1.2.1 h1:fzOfY5zUADkCkbIafAed11gL1sW+bJ26p6zWLBMElR4=
|
||||
github.com/ttacon/libphonenumber v1.2.1/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M=
|
||||
github.com/u-root/u-root v0.14.0 h1:Ka4T10EEML7dQ5XDvO9c3MBN8z4nuSnGjcd1jmU2ivg=
|
||||
github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1WMluqE=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/utahta/go-linenotify v0.5.0 h1:E1tJaB/XhqRY/iz203FD0MaHm10DjQPOq5/Mem2A3Gs=
|
||||
github.com/utahta/go-linenotify v0.5.0/go.mod h1:KsvBXil2wx+ByaCR0e+IZKTbp4pDesc7yjzRigLf6pE=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
||||
@@ -488,18 +764,31 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
||||
go.mau.fi/util v0.9.3 h1:aqNF8KDIN8bFpFbybSk+mEBil7IHeBwlujfyTnvP0uU=
|
||||
go.mau.fi/util v0.9.3/go.mod h1:krWWfBM1jWTb5f8NCa2TLqWMQuM81X7TGQjhMjBeXmQ=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0 h1:RN3ifU8y4prNWeEnQp2kRRHz8UwonAEYZl8tUzHEXAk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.64.0/go.mod h1:habDz3tEWiFANTo6oUE99EmaFUrCNYAAg3wiVmusm70=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0 h1:wm/Q0GAAykXv83wzcKzGGqAnnfLFyFe7RslekZuv+VI=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.38.0/go.mod h1:ra3Pa40+oKjvYh+ZD3EdxFZZB0xdMfuileHAm4nNN7w=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
@@ -513,11 +802,18 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9 h1:TQwNpfvNkxAVlItJf6Cr5JTsVZoC/Sj7K3OZv2Pc14A=
|
||||
golang.org/x/exp v0.0.0-20251002181428-27f1f14c8bb9/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
||||
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 h1:MDfG8Cvcqlt9XXrmEiD4epKn7VJHZO84hejP9Jmp0MM=
|
||||
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
|
||||
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
||||
@@ -528,57 +824,123 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/net v0.0.0-20171115151908-9dfe39835686/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.0.0-20171101214715-fd80eb99c8f6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@@ -587,7 +949,12 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -604,26 +971,50 @@ golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.257.0 h1:8Y0lzvHlZps53PEaw+G29SsQIkuKrumGWs9puiexNAA=
|
||||
google.golang.org/api v0.257.0/go.mod h1:4eJrr+vbVaZSqs7vovFd1Jb/A6ml6iw2e6FBYf3GAO4=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
|
||||
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M=
|
||||
gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
|
||||
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
|
||||
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
|
||||
@@ -634,8 +1025,14 @@ gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlI
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -656,6 +1053,8 @@ honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0 h1:5SXjd4ET5dYijLa
|
||||
honnef.co/go/tools v0.7.0-0.dev.0.20251022135355-8273271481d0/go.mod h1:EPDDhEZqVHhWuPI5zPAsjU0U7v9xNIWjoOVyZ5ZcniQ=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
maunium.net/go/mautrix v0.26.0 h1:valc2VmZF+oIY4bMq4Cd5H9cEKMRe8eP4FM7iiaYLxI=
|
||||
maunium.net/go/mautrix v0.26.0/go.mod h1:NWMv+243NX/gDrLofJ2nNXJPrG8vzoM+WUCWph85S6Q=
|
||||
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
||||
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
|
||||
|
||||
@@ -166,10 +166,14 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
dirList.Exists = true
|
||||
} else {
|
||||
dirList.Exists = false
|
||||
dirList.Response = &commonpb.Response{Err: err.Error()}
|
||||
}
|
||||
dirList.Files = []*sliverpb.FileInfo{}
|
||||
rootDirInfo, err := rootDirEntry.Info()
|
||||
if err == nil && filter == "" {
|
||||
var rootDirInfo fs.FileInfo
|
||||
if rootDirEntry != nil {
|
||||
rootDirInfo, err = rootDirEntry.Info()
|
||||
}
|
||||
if err == nil && rootDirInfo != nil && filter == "" {
|
||||
// We should not get an error because we created the DirEntry object from the FileInfo object
|
||||
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
|
||||
Name: ".", // Cannot use the name from the FileInfo / DirEntry because that is the name of the directory
|
||||
@@ -193,6 +197,7 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
if err != nil {
|
||||
// Then this is a bad filter, and it will be a bad filter
|
||||
// on every iteration of the loop, so we might as well break now
|
||||
dirList.Response = &commonpb.Response{Err: err.Error()}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -249,22 +254,26 @@ func getDirList(target string) (string, fs.DirEntry, []fs.DirEntry, error) {
|
||||
// {{end}}
|
||||
return "", nil, nil, err
|
||||
}
|
||||
if rootInfo, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
/*
|
||||
We could place the entry for the directory itself
|
||||
at the beginning of the returned slice of DirEntry
|
||||
objects, but then it is not clear if that is the
|
||||
root directory or a directory / file in the root
|
||||
directory with the same name as the root directory.
|
||||
|
||||
Using WalkDir is not great here because you cannot
|
||||
tell it to not be recursive, so we will be wasting
|
||||
cycles telling it to skip directories and files
|
||||
*/
|
||||
files, err := os.ReadDir(dir)
|
||||
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
|
||||
rootInfo, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
}
|
||||
return dir, nil, []fs.DirEntry{}, err
|
||||
}
|
||||
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
/*
|
||||
We could place the entry for the directory itself
|
||||
at the beginning of the returned slice of DirEntry
|
||||
objects, but then it is not clear if that is the
|
||||
root directory or a directory / file in the root
|
||||
directory with the same name as the root directory.
|
||||
|
||||
Using WalkDir is not great here because you cannot
|
||||
tell it to not be recursive, so we will be wasting
|
||||
cycles telling it to skip directories and files
|
||||
*/
|
||||
files, err := os.ReadDir(dir)
|
||||
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
|
||||
}
|
||||
|
||||
func rmHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
+1412
-1204
File diff suppressed because it is too large.
Load diff
@@ -586,6 +586,9 @@ message WebContent {
|
||||
string ContentType = 4;
|
||||
uint64 Size = 5 [ jstype = JS_STRING ];
|
||||
|
||||
string OriginalFile = 6;
|
||||
string Sha256 = 7;
|
||||
|
||||
bytes Content = 9;
|
||||
}
|
||||
|
||||
@@ -1316,4 +1319,18 @@ message CertificateData {
|
||||
|
||||
message CertificateInfo {
|
||||
repeated CertificateData info = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message CertificateAuthorityData {
|
||||
string CN = 1;
|
||||
string CreationTime = 2;
|
||||
string ValidityStart = 3;
|
||||
string ValidityExpiry = 4;
|
||||
string Type = 5;
|
||||
string KeyAlgorithm = 6;
|
||||
string ID = 7;
|
||||
}
|
||||
|
||||
message CertificateAuthorityInfo {
|
||||
repeated CertificateAuthorityData info = 1;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v6.33.4
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v6.33.4
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
|
||||
+864
-856
File diff suppressed because it is too large.
Load diff
@@ -108,6 +108,8 @@ service SliverRPC {
|
||||
|
||||
// *** Certificates ***
|
||||
rpc GetCertificateInfo(clientpb.CertificatesReq) returns (clientpb.CertificateInfo);
|
||||
rpc GetCertificateAuthorityInfo(commonpb.Empty)
|
||||
returns (clientpb.CertificateAuthorityInfo);
|
||||
|
||||
// *** Crackstation ***
|
||||
rpc CrackstationRegister(clientpb.Crackstation)
|
||||
|
||||
@@ -96,6 +96,7 @@ type SliverRPCClient interface {
|
||||
Builders(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.Builders, error)
|
||||
// *** Certificates ***
|
||||
GetCertificateInfo(ctx context.Context, in *clientpb.CertificatesReq, opts ...grpc.CallOption) (*clientpb.CertificateInfo, error)
|
||||
GetCertificateAuthorityInfo(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.CertificateAuthorityInfo, error)
|
||||
// *** Crackstation ***
|
||||
CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error)
|
||||
CrackstationTrigger(ctx context.Context, in *clientpb.Event, opts ...grpc.CallOption) (*commonpb.Empty, error)
|
||||
@@ -812,6 +813,15 @@ func (c *sliverRPCClient) GetCertificateInfo(ctx context.Context, in *clientpb.C
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) GetCertificateAuthorityInfo(ctx context.Context, in *commonpb.Empty, opts ...grpc.CallOption) (*clientpb.CertificateAuthorityInfo, error) {
|
||||
out := new(clientpb.CertificateAuthorityInfo)
|
||||
err := c.cc.Invoke(ctx, "/rpcpb.SliverRPC/GetCertificateAuthorityInfo", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *sliverRPCClient) CrackstationRegister(ctx context.Context, in *clientpb.Crackstation, opts ...grpc.CallOption) (SliverRPC_CrackstationRegisterClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &SliverRPC_ServiceDesc.Streams[2], "/rpcpb.SliverRPC/CrackstationRegister", opts...)
|
||||
if err != nil {
|
||||
@@ -2088,6 +2098,7 @@ type SliverRPCServer interface {
|
||||
Builders(context.Context, *commonpb.Empty) (*clientpb.Builders, error)
|
||||
// *** Certificates ***
|
||||
GetCertificateInfo(context.Context, *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error)
|
||||
GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error)
|
||||
// *** Crackstation ***
|
||||
CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error
|
||||
CrackstationTrigger(context.Context, *clientpb.Event) (*commonpb.Empty, error)
|
||||
@@ -2405,6 +2416,9 @@ func (UnimplementedSliverRPCServer) Builders(context.Context, *commonpb.Empty) (
|
||||
func (UnimplementedSliverRPCServer) GetCertificateInfo(context.Context, *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetCertificateInfo not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) GetCertificateAuthorityInfo(context.Context, *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetCertificateAuthorityInfo not implemented")
|
||||
}
|
||||
func (UnimplementedSliverRPCServer) CrackstationRegister(*clientpb.Crackstation, SliverRPC_CrackstationRegisterServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method CrackstationRegister not implemented")
|
||||
}
|
||||
@@ -3842,6 +3856,24 @@ func _SliverRPC_GetCertificateInfo_Handler(srv interface{}, ctx context.Context,
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_GetCertificateAuthorityInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(commonpb.Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(SliverRPCServer).GetCertificateAuthorityInfo(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpcpb.SliverRPC/GetCertificateAuthorityInfo",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(SliverRPCServer).GetCertificateAuthorityInfo(ctx, req.(*commonpb.Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _SliverRPC_CrackstationRegister_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
m := new(clientpb.Crackstation)
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
@@ -6309,6 +6341,10 @@ var SliverRPC_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "GetCertificateInfo",
|
||||
Handler: _SliverRPC_GetCertificateInfo_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetCertificateAuthorityInfo",
|
||||
Handler: _SliverRPC_GetCertificateAuthorityInfo_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CrackstationTrigger",
|
||||
Handler: _SliverRPC_CrackstationTrigger_Handler,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v6.33.4
|
||||
// source: sliverpb/sliver.proto
|
||||
|
||||
package sliverpb
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
@@ -37,7 +38,12 @@ var (
|
||||
|
||||
// GetACMEDir - Dir to store ACME certs
|
||||
func GetACMEDir() string {
|
||||
acmePath := filepath.Join(getCertDir(), ACMEDirName)
|
||||
certDir := getCertDir()
|
||||
baseDir := certDir
|
||||
if fi, err := os.Stat(certDir); err != nil || !fi.IsDir() {
|
||||
baseDir = assets.GetRootAppDir()
|
||||
}
|
||||
acmePath := filepath.Join(baseDir, ACMEDirName)
|
||||
if _, err := os.Stat(acmePath); os.IsNotExist(err) {
|
||||
acmeLog.Infof("[mkdir] %s", acmePath)
|
||||
os.MkdirAll(acmePath, 0700)
|
||||
|
||||
+134
-40
@@ -22,19 +22,23 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
)
|
||||
|
||||
// -----------------------
|
||||
// CERTIFICATE AUTHORITY
|
||||
// -----------------------
|
||||
|
||||
// SetupCAs - Creates directories for certs
|
||||
// SetupCAs - Ensure certificate authorities exist in storage
|
||||
func SetupCAs() {
|
||||
importCertificateAuthoritiesFromDisk()
|
||||
GenerateCertificateAuthority(MtlsImplantCA, "")
|
||||
GenerateCertificateAuthority(MtlsServerCA, "")
|
||||
GenerateCertificateAuthority(OperatorCA, "operators")
|
||||
@@ -43,24 +47,17 @@ func SetupCAs() {
|
||||
|
||||
func getCertDir() string {
|
||||
rootDir := assets.GetRootAppDir()
|
||||
certDir := filepath.Join(rootDir, "certs")
|
||||
if _, err := os.Stat(certDir); os.IsNotExist(err) {
|
||||
err := os.MkdirAll(certDir, 0700)
|
||||
if err != nil {
|
||||
certsLog.Fatalf("Failed to create cert dir %s", err)
|
||||
}
|
||||
}
|
||||
return certDir
|
||||
return filepath.Join(rootDir, "certs")
|
||||
}
|
||||
|
||||
// GenerateCertificateAuthority - Creates a new CA cert for a given type
|
||||
func GenerateCertificateAuthority(caType string, commonName string) (*x509.Certificate, *ecdsa.PrivateKey) {
|
||||
storageDir := getCertDir()
|
||||
certFilePath := filepath.Join(storageDir, fmt.Sprintf("%s-ca-cert.pem", caType))
|
||||
if _, err := os.Stat(certFilePath); os.IsNotExist(err) {
|
||||
certsLog.Infof("Generating certificate authority for '%s'", caType)
|
||||
cert, key := GenerateECCCertificate(caType, commonName, true, false, false)
|
||||
SaveCertificateAuthority(caType, cert, key)
|
||||
if !certificateAuthorityExists(caType) {
|
||||
if !importCertificateAuthorityFromDisk(caType) {
|
||||
certsLog.Infof("Generating certificate authority for '%s'", caType)
|
||||
cert, key := GenerateECCCertificate(caType, commonName, true, false, false)
|
||||
SaveCertificateAuthority(caType, cert, key)
|
||||
}
|
||||
}
|
||||
cert, key, err := GetCertificateAuthority(caType)
|
||||
if err != nil {
|
||||
@@ -104,45 +101,142 @@ func GetCertificateAuthority(caType string) (*x509.Certificate, *ecdsa.PrivateKe
|
||||
// GetCertificateAuthorityPEM - Get PEM encoded CA cert/key
|
||||
func GetCertificateAuthorityPEM(caType string) ([]byte, []byte, error) {
|
||||
caType = filepath.Base(caType)
|
||||
caCertPath := filepath.Join(getCertDir(), fmt.Sprintf("%s-ca-cert.pem", caType))
|
||||
caKeyPath := filepath.Join(getCertDir(), fmt.Sprintf("%s-ca-key.pem", caType))
|
||||
caModel := &models.CertificateAuthority{}
|
||||
dbSession := db.Session()
|
||||
result := dbSession.Where(&models.CertificateAuthority{CAType: caType}).First(caModel)
|
||||
if result.Error == nil {
|
||||
return []byte(caModel.CertificatePEM), []byte(caModel.PrivateKeyPEM), nil
|
||||
}
|
||||
if !errors.Is(result.Error, db.ErrRecordNotFound) {
|
||||
certsLog.Error(result.Error)
|
||||
return nil, nil, result.Error
|
||||
}
|
||||
|
||||
certPEM, keyPEM, err := loadCertificateAuthorityPEMFromDisk(caType)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
certsLog.Error(err)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
SaveCertificateAuthority(caType, certPEM, keyPEM)
|
||||
return certPEM, keyPEM, nil
|
||||
}
|
||||
|
||||
// SaveCertificateAuthority - Save the certificate and the key to the database
|
||||
// doesn't return an error because errors are fatal. If we can't generate CAs,
|
||||
// then we can't secure communication and we should die a horrible death.
|
||||
func SaveCertificateAuthority(caType string, cert []byte, key []byte) {
|
||||
caType = filepath.Base(caType)
|
||||
commonName := commonNameFromCertPEM(cert)
|
||||
dbSession := db.Session()
|
||||
caModel := &models.CertificateAuthority{}
|
||||
result := dbSession.Where(&models.CertificateAuthority{CAType: caType}).First(caModel)
|
||||
if errors.Is(result.Error, db.ErrRecordNotFound) {
|
||||
caModel = &models.CertificateAuthority{
|
||||
CommonName: commonName,
|
||||
CAType: caType,
|
||||
CertificatePEM: string(cert),
|
||||
PrivateKeyPEM: string(key),
|
||||
}
|
||||
if err := dbSession.Create(caModel).Error; err != nil {
|
||||
certsLog.Fatalf("Failed to save certificate authority: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if result.Error != nil {
|
||||
certsLog.Fatalf("Failed to save certificate authority: %s", result.Error)
|
||||
}
|
||||
|
||||
caModel.CommonName = commonName
|
||||
caModel.CertificatePEM = string(cert)
|
||||
caModel.PrivateKeyPEM = string(key)
|
||||
if err := dbSession.Save(caModel).Error; err != nil {
|
||||
certsLog.Fatalf("Failed to save certificate authority: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func certificateAuthorityExists(caType string) bool {
|
||||
caType = filepath.Base(caType)
|
||||
dbSession := db.Session()
|
||||
result := dbSession.Select("id").Where(&models.CertificateAuthority{CAType: caType}).First(&models.CertificateAuthority{})
|
||||
if result.Error == nil {
|
||||
return true
|
||||
}
|
||||
if !errors.Is(result.Error, db.ErrRecordNotFound) {
|
||||
certsLog.Error(result.Error)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func importCertificateAuthoritiesFromDisk() {
|
||||
certDir := getCertDir()
|
||||
fi, err := os.Stat(certDir)
|
||||
if err != nil || !fi.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
importCertificateAuthorityFromDisk(MtlsImplantCA)
|
||||
importCertificateAuthorityFromDisk(MtlsServerCA)
|
||||
importCertificateAuthorityFromDisk(OperatorCA)
|
||||
importCertificateAuthorityFromDisk(HTTPSCA)
|
||||
}
|
||||
|
||||
func importCertificateAuthorityFromDisk(caType string) bool {
|
||||
if certificateAuthorityExists(caType) {
|
||||
return false
|
||||
}
|
||||
certPEM, keyPEM, err := loadCertificateAuthorityPEMFromDisk(caType)
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
certsLog.Error(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
certsLog.Infof("Importing certificate authority for '%s' from disk", caType)
|
||||
SaveCertificateAuthority(caType, certPEM, keyPEM)
|
||||
return true
|
||||
}
|
||||
|
||||
func loadCertificateAuthorityPEMFromDisk(caType string) ([]byte, []byte, error) {
|
||||
caType = filepath.Base(caType)
|
||||
certDir := getCertDir()
|
||||
fi, err := os.Stat(certDir)
|
||||
if err != nil || !fi.IsDir() {
|
||||
return nil, nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
caCertPath := filepath.Join(certDir, fmt.Sprintf("%s-ca-cert.pem", caType))
|
||||
caKeyPath := filepath.Join(certDir, fmt.Sprintf("%s-ca-key.pem", caType))
|
||||
|
||||
certPEM, err := os.ReadFile(caCertPath)
|
||||
if err != nil {
|
||||
certsLog.Error(err)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil, os.ErrNotExist
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
keyPEM, err := os.ReadFile(caKeyPath)
|
||||
if err != nil {
|
||||
certsLog.Error(err)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil, os.ErrNotExist
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
return certPEM, keyPEM, nil
|
||||
}
|
||||
|
||||
// SaveCertificateAuthority - Save the certificate and the key to the filesystem
|
||||
// doesn't return an error because errors are fatal. If we can't generate CAs,
|
||||
// then we can't secure communication and we should die a horrible death.
|
||||
func SaveCertificateAuthority(caType string, cert []byte, key []byte) {
|
||||
|
||||
storageDir := getCertDir()
|
||||
if _, err := os.Stat(storageDir); os.IsNotExist(err) {
|
||||
os.MkdirAll(storageDir, 0700)
|
||||
func commonNameFromCertPEM(cert []byte) string {
|
||||
block, _ := pem.Decode(cert)
|
||||
if block == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// CAs get written to the filesystem since we control the names and makes them
|
||||
// easier to move around/backup
|
||||
certFilePath := filepath.Join(storageDir, fmt.Sprintf("%s-ca-cert.pem", caType))
|
||||
keyFilePath := filepath.Join(storageDir, fmt.Sprintf("%s-ca-key.pem", caType))
|
||||
|
||||
err := os.WriteFile(certFilePath, cert, 0600)
|
||||
parsed, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
certsLog.Fatalf("Failed write certificate data to: %s", certFilePath)
|
||||
}
|
||||
|
||||
err = os.WriteFile(keyFilePath, key, 0600)
|
||||
if err != nil {
|
||||
certsLog.Fatalf("Failed write certificate data to: %s", keyFilePath)
|
||||
return ""
|
||||
}
|
||||
return parsed.Subject.CommonName
|
||||
}
|
||||
@@ -92,9 +92,17 @@ var cmdImportCA = &cobra.Command{
|
||||
fmt.Printf("Failed to parse file %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if strings.TrimSpace(importCA.Certificate) == "" || strings.TrimSpace(importCA.PrivateKey) == "" {
|
||||
fmt.Printf("CA file missing certificate or private key data\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
cert := []byte(importCA.Certificate)
|
||||
key := []byte(importCA.PrivateKey)
|
||||
certs.SaveCertificateAuthority(ca, cert, key)
|
||||
if _, _, err := certs.GetCertificateAuthorityPEM(ca); err != nil {
|
||||
fmt.Printf("Failed to verify imported CA %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+112
-15
@@ -26,9 +26,11 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -39,7 +41,8 @@ const (
|
||||
// MySQL - MySQL protocol
|
||||
MySQL = "mysql"
|
||||
|
||||
databaseConfigFileName = "database.json"
|
||||
databaseConfigFileName = "database.yaml"
|
||||
databaseLegacyConfigFileName = "database.json"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -47,9 +50,16 @@ var (
|
||||
ErrInvalidDialect = errors.New("invalid SQL Dialect")
|
||||
|
||||
databaseConfigLog = log.NamedLogger("config", "database")
|
||||
|
||||
defaultSQLitePragmas = map[string]string{
|
||||
"journal_mode": "WAL", // reduce writer blocking, better concurrency
|
||||
"busy_timeout": "5000", // wait for locks instead of failing fast (ms)
|
||||
"synchronous": "NORMAL", // faster WAL syncs while retaining durability
|
||||
"temp_store": "MEMORY", // keep temp structures off disk for quicker startup
|
||||
}
|
||||
)
|
||||
|
||||
// GetDatabaseConfigPath - File path to config.json
|
||||
// GetDatabaseConfigPath - File path to config.yaml
|
||||
func GetDatabaseConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
databaseConfigPath := filepath.Join(appDir, "configs", databaseConfigFileName)
|
||||
@@ -57,21 +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"`
|
||||
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
|
||||
@@ -80,7 +96,7 @@ func (c *DatabaseConfig) DSN() (string, error) {
|
||||
switch c.Dialect {
|
||||
case Sqlite:
|
||||
filePath := filepath.Join(assets.GetRootAppDir(), "sliver.db")
|
||||
params := encodeParams(c.Params)
|
||||
params := encodeSQLiteParams(c.Params, c.Pragmas)
|
||||
return fmt.Sprintf("file:%s?%s", filePath, params), nil
|
||||
case MySQL:
|
||||
user := url.QueryEscape(c.Username)
|
||||
@@ -112,6 +128,44 @@ func encodeParams(rawParams map[string]string) string {
|
||||
return params.Encode()
|
||||
}
|
||||
|
||||
func encodeSQLiteParams(rawParams map[string]string, pragmas map[string]string) string {
|
||||
if rawParams == nil {
|
||||
rawParams = map[string]string{}
|
||||
}
|
||||
params := url.Values{}
|
||||
|
||||
// Preserve any user-provided parameters first.
|
||||
for key, value := range rawParams {
|
||||
params.Add(key, value)
|
||||
}
|
||||
|
||||
// Apply safer defaults only when the user has not set any custom pragma.
|
||||
if _, ok := rawParams["_pragma"]; !ok {
|
||||
selectedPragmas := pragmas
|
||||
if len(selectedPragmas) == 0 {
|
||||
selectedPragmas = defaultSQLitePragmas
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(selectedPragmas))
|
||||
for key := range selectedPragmas {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
value := selectedPragmas[key]
|
||||
params.Add("_pragma", fmt.Sprintf("%s(%s)", key, value))
|
||||
}
|
||||
}
|
||||
|
||||
// Encourage shared cache to play nicer with WAL and multiple connections.
|
||||
if _, ok := rawParams["cache"]; !ok {
|
||||
params.Add("cache", "shared")
|
||||
}
|
||||
|
||||
return params.Encode()
|
||||
}
|
||||
|
||||
// Save - Save config file to disk
|
||||
func (c *DatabaseConfig) Save() error {
|
||||
configPath := GetDatabaseConfigPath()
|
||||
@@ -123,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
|
||||
}
|
||||
@@ -138,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")
|
||||
}
|
||||
@@ -161,9 +230,17 @@ func GetDatabaseConfig() *DatabaseConfig {
|
||||
config.MaxOpenConns = 1
|
||||
}
|
||||
|
||||
ensureSQLiteDefaults(config)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -171,9 +248,29 @@ func GetDatabaseConfig() *DatabaseConfig {
|
||||
func getDefaultDatabaseConfig() *DatabaseConfig {
|
||||
return &DatabaseConfig{
|
||||
Dialect: Sqlite,
|
||||
Pragmas: defaultSQLitePragmas,
|
||||
MaxIdleConns: 10,
|
||||
MaxOpenConns: 100,
|
||||
|
||||
LogLevel: "warn",
|
||||
}
|
||||
}
|
||||
|
||||
func ensureSQLiteDefaults(c *DatabaseConfig) {
|
||||
if c.Dialect != Sqlite {
|
||||
return
|
||||
}
|
||||
if c.Params == nil {
|
||||
c.Params = map[string]string{}
|
||||
}
|
||||
if _, ok := c.Params["_pragma"]; ok {
|
||||
// User provided explicit pragmas; leave Pragmas untouched to avoid confusion.
|
||||
return
|
||||
}
|
||||
if len(c.Pragmas) == 0 {
|
||||
c.Pragmas = defaultSQLitePragmas
|
||||
}
|
||||
if _, ok := c.Params["cache"]; !ok {
|
||||
c.Params["cache"] = "shared"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
package configs
|
||||
|
||||
// NotificationsConfig - Notification configuration for the server.
|
||||
type NotificationsConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
Events []string `json:"events,omitempty" yaml:"events,omitempty"`
|
||||
Templates map[string]*NotificationTemplateConfig `json:"templates,omitempty" yaml:"templates,omitempty"`
|
||||
Services *NotificationsServicesConfig `json:"services,omitempty" yaml:"services,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationServiceConfig - Shared settings for notification services.
|
||||
type NotificationServiceConfig struct {
|
||||
Enabled bool `json:"enabled" yaml:"enabled"`
|
||||
Events []string `json:"events,omitempty" yaml:"events,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationTemplateConfig - Per-event template configuration.
|
||||
type NotificationTemplateConfig struct {
|
||||
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||
Template string `json:"template" yaml:"template"`
|
||||
}
|
||||
|
||||
// NotificationsServicesConfig - Available notification services.
|
||||
type NotificationsServicesConfig struct {
|
||||
AmazonSES *AmazonSESConfig `json:"amazon_ses,omitempty" yaml:"amazon_ses,omitempty"`
|
||||
AmazonSNS *AmazonSNSConfig `json:"amazon_sns,omitempty" yaml:"amazon_sns,omitempty"`
|
||||
Bark *BarkConfig `json:"bark,omitempty" yaml:"bark,omitempty"`
|
||||
DingDing *DingDingConfig `json:"dingding,omitempty" yaml:"dingding,omitempty"`
|
||||
Discord *DiscordConfig `json:"discord,omitempty" yaml:"discord,omitempty"`
|
||||
FCM *FCMConfig `json:"fcm,omitempty" yaml:"fcm,omitempty"`
|
||||
GoogleChat *GoogleChatConfig `json:"google_chat,omitempty" yaml:"google_chat,omitempty"`
|
||||
HTTP *HTTPConfig `json:"http,omitempty" yaml:"http,omitempty"`
|
||||
Lark *LarkConfig `json:"lark,omitempty" yaml:"lark,omitempty"`
|
||||
Line *LineConfig `json:"line,omitempty" yaml:"line,omitempty"`
|
||||
LineNotify *LineNotifyConfig `json:"line_notify,omitempty" yaml:"line_notify,omitempty"`
|
||||
Mail *MailConfig `json:"mail,omitempty" yaml:"mail,omitempty"`
|
||||
Mailgun *MailgunConfig `json:"mailgun,omitempty" yaml:"mailgun,omitempty"`
|
||||
Matrix *MatrixConfig `json:"matrix,omitempty" yaml:"matrix,omitempty"`
|
||||
Mattermost *MattermostConfig `json:"mattermost,omitempty" yaml:"mattermost,omitempty"`
|
||||
MSTeams *MSTeamsConfig `json:"msteams,omitempty" yaml:"msteams,omitempty"`
|
||||
PagerDuty *PagerDutyConfig `json:"pagerduty,omitempty" yaml:"pagerduty,omitempty"`
|
||||
Plivo *PlivoConfig `json:"plivo,omitempty" yaml:"plivo,omitempty"`
|
||||
Pushbullet *PushbulletConfig `json:"pushbullet,omitempty" yaml:"pushbullet,omitempty"`
|
||||
PushbulletSMS *PushbulletSMSConfig `json:"pushbullet_sms,omitempty" yaml:"pushbullet_sms,omitempty"`
|
||||
Pushover *PushoverConfig `json:"pushover,omitempty" yaml:"pushover,omitempty"`
|
||||
Reddit *RedditConfig `json:"reddit,omitempty" yaml:"reddit,omitempty"`
|
||||
RocketChat *RocketChatConfig `json:"rocketchat,omitempty" yaml:"rocketchat,omitempty"`
|
||||
SendGrid *SendGridConfig `json:"sendgrid,omitempty" yaml:"sendgrid,omitempty"`
|
||||
Slack *SlackConfig `json:"slack,omitempty" yaml:"slack,omitempty"`
|
||||
Syslog *SyslogConfig `json:"syslog,omitempty" yaml:"syslog,omitempty"`
|
||||
Telegram *TelegramConfig `json:"telegram,omitempty" yaml:"telegram,omitempty"`
|
||||
TextMagic *TextMagicConfig `json:"textmagic,omitempty" yaml:"textmagic,omitempty"`
|
||||
Twilio *TwilioConfig `json:"twilio,omitempty" yaml:"twilio,omitempty"`
|
||||
Twitter *TwitterConfig `json:"twitter,omitempty" yaml:"twitter,omitempty"`
|
||||
Viber *ViberConfig `json:"viber,omitempty" yaml:"viber,omitempty"`
|
||||
WebPush *WebPushConfig `json:"webpush,omitempty" yaml:"webpush,omitempty"`
|
||||
WeChat *WeChatConfig `json:"wechat,omitempty" yaml:"wechat,omitempty"`
|
||||
WhatsApp *WhatsAppConfig `json:"whatsapp,omitempty" yaml:"whatsapp,omitempty"`
|
||||
}
|
||||
|
||||
// AmazonSESConfig - Amazon SES notifications.
|
||||
type AmazonSESConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
AccessKeyID string `json:"access_key_id" yaml:"access_key_id"`
|
||||
SecretKey string `json:"secret_key" yaml:"secret_key"`
|
||||
Region string `json:"region" yaml:"region"`
|
||||
SenderAddress string `json:"sender_address" yaml:"sender_address"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// AmazonSNSConfig - Amazon SNS notifications.
|
||||
type AmazonSNSConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
AccessKeyID string `json:"access_key_id" yaml:"access_key_id"`
|
||||
SecretKey string `json:"secret_key" yaml:"secret_key"`
|
||||
Region string `json:"region" yaml:"region"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// BarkConfig - Bark notifications.
|
||||
type BarkConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
DeviceKey string `json:"device_key" yaml:"device_key"`
|
||||
Servers []string `json:"servers,omitempty" yaml:"servers,omitempty"`
|
||||
}
|
||||
|
||||
// DingDingConfig - DingTalk notifications.
|
||||
type DingDingConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Token string `json:"token" yaml:"token"`
|
||||
Secret string `json:"secret" yaml:"secret"`
|
||||
}
|
||||
|
||||
// DiscordConfig - Discord notifications.
|
||||
type DiscordConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Token string `json:"token" yaml:"token"`
|
||||
TokenType string `json:"token_type,omitempty" yaml:"token_type,omitempty"`
|
||||
Channels []string `json:"channels" yaml:"channels"`
|
||||
}
|
||||
|
||||
// FCMConfig - Firebase Cloud Messaging notifications.
|
||||
type FCMConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
CredentialsFile string `json:"credentials_file,omitempty" yaml:"credentials_file,omitempty"`
|
||||
ProjectID string `json:"project_id,omitempty" yaml:"project_id,omitempty"`
|
||||
DeviceTokens []string `json:"device_tokens" yaml:"device_tokens"`
|
||||
}
|
||||
|
||||
// GoogleChatConfig - Google Chat notifications.
|
||||
type GoogleChatConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
CredentialsFile string `json:"credentials_file,omitempty" yaml:"credentials_file,omitempty"`
|
||||
CredentialsJSON string `json:"credentials_json,omitempty" yaml:"credentials_json,omitempty"`
|
||||
Spaces []string `json:"spaces" yaml:"spaces"`
|
||||
}
|
||||
|
||||
// HTTPConfig - HTTP webhook notifications.
|
||||
type HTTPConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
URLs []string `json:"urls,omitempty" yaml:"urls,omitempty"`
|
||||
Webhooks []HTTPWebhookConfig `json:"webhooks,omitempty" yaml:"webhooks,omitempty"`
|
||||
}
|
||||
|
||||
// HTTPWebhookConfig - Detailed webhook configuration.
|
||||
type HTTPWebhookConfig struct {
|
||||
URL string `json:"url" yaml:"url"`
|
||||
Method string `json:"method,omitempty" yaml:"method,omitempty"`
|
||||
ContentType string `json:"content_type,omitempty" yaml:"content_type,omitempty"`
|
||||
Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"`
|
||||
}
|
||||
|
||||
// LarkConfig - Lark notifications.
|
||||
type LarkConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Webhook *LarkWebhookConfig `json:"webhook,omitempty" yaml:"webhook,omitempty"`
|
||||
CustomApp *LarkCustomAppConfig `json:"custom_app,omitempty" yaml:"custom_app,omitempty"`
|
||||
}
|
||||
|
||||
// LarkWebhookConfig - Lark webhook notifications.
|
||||
type LarkWebhookConfig struct {
|
||||
URL string `json:"url" yaml:"url"`
|
||||
}
|
||||
|
||||
// LarkCustomAppConfig - Lark custom app notifications.
|
||||
type LarkCustomAppConfig struct {
|
||||
AppID string `json:"app_id" yaml:"app_id"`
|
||||
AppSecret string `json:"app_secret" yaml:"app_secret"`
|
||||
Receivers []LarkReceiverConfig `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// LarkReceiverConfig - Lark receiver IDs.
|
||||
type LarkReceiverConfig struct {
|
||||
Type string `json:"type" yaml:"type"`
|
||||
ID string `json:"id" yaml:"id"`
|
||||
}
|
||||
|
||||
// LineConfig - Line bot notifications.
|
||||
type LineConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
ChannelSecret string `json:"channel_secret" yaml:"channel_secret"`
|
||||
ChannelAccessToken string `json:"channel_access_token" yaml:"channel_access_token"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// LineNotifyConfig - Line notify token notifications.
|
||||
type LineNotifyConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// MailConfig - SMTP mail notifications.
|
||||
type MailConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
SenderAddress string `json:"sender_address" yaml:"sender_address"`
|
||||
SMTPHost string `json:"smtp_host" yaml:"smtp_host"`
|
||||
SMTPIdentity string `json:"smtp_identity,omitempty" yaml:"smtp_identity,omitempty"`
|
||||
SMTPUsername string `json:"smtp_username,omitempty" yaml:"smtp_username,omitempty"`
|
||||
SMTPPassword string `json:"smtp_password,omitempty" yaml:"smtp_password,omitempty"`
|
||||
SMTPAuthHost string `json:"smtp_auth_host,omitempty" yaml:"smtp_auth_host,omitempty"`
|
||||
BodyType string `json:"body_type,omitempty" yaml:"body_type,omitempty"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// MailgunConfig - Mailgun notifications.
|
||||
type MailgunConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Domain string `json:"domain" yaml:"domain"`
|
||||
APIKey string `json:"api_key" yaml:"api_key"`
|
||||
SenderAddress string `json:"sender_address" yaml:"sender_address"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// MatrixConfig - Matrix notifications.
|
||||
type MatrixConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
UserID string `json:"user_id" yaml:"user_id"`
|
||||
RoomID string `json:"room_id" yaml:"room_id"`
|
||||
HomeServer string `json:"home_server" yaml:"home_server"`
|
||||
AccessToken string `json:"access_token" yaml:"access_token"`
|
||||
}
|
||||
|
||||
// MattermostConfig - Mattermost notifications.
|
||||
type MattermostConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
URL string `json:"url" yaml:"url"`
|
||||
LoginID string `json:"login_id,omitempty" yaml:"login_id,omitempty"`
|
||||
Password string `json:"password,omitempty" yaml:"password,omitempty"`
|
||||
Channels []string `json:"channels" yaml:"channels"`
|
||||
}
|
||||
|
||||
// MSTeamsConfig - Microsoft Teams notifications.
|
||||
type MSTeamsConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Webhooks []string `json:"webhooks" yaml:"webhooks"`
|
||||
WrapText bool `json:"wrap_text,omitempty" yaml:"wrap_text,omitempty"`
|
||||
DisableWebhookValidation bool `json:"disable_webhook_validation,omitempty" yaml:"disable_webhook_validation,omitempty"`
|
||||
UserAgent string `json:"user_agent,omitempty" yaml:"user_agent,omitempty"`
|
||||
}
|
||||
|
||||
// PagerDutyConfig - PagerDuty notifications.
|
||||
type PagerDutyConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Token string `json:"token" yaml:"token"`
|
||||
FromAddress string `json:"from_address" yaml:"from_address"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
NotificationType string `json:"notification_type,omitempty" yaml:"notification_type,omitempty"`
|
||||
Urgency string `json:"urgency,omitempty" yaml:"urgency,omitempty"`
|
||||
PriorityID string `json:"priority_id,omitempty" yaml:"priority_id,omitempty"`
|
||||
}
|
||||
|
||||
// PlivoConfig - Plivo notifications.
|
||||
type PlivoConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
AuthID string `json:"auth_id" yaml:"auth_id"`
|
||||
AuthToken string `json:"auth_token" yaml:"auth_token"`
|
||||
Source string `json:"source" yaml:"source"`
|
||||
CallbackURL string `json:"callback_url,omitempty" yaml:"callback_url,omitempty"`
|
||||
CallbackMethod string `json:"callback_method,omitempty" yaml:"callback_method,omitempty"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// PushbulletConfig - Pushbullet notifications.
|
||||
type PushbulletConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
APIToken string `json:"api_token" yaml:"api_token"`
|
||||
DeviceNicknames []string `json:"device_nicknames" yaml:"device_nicknames"`
|
||||
}
|
||||
|
||||
// PushbulletSMSConfig - Pushbullet SMS notifications.
|
||||
type PushbulletSMSConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
APIToken string `json:"api_token" yaml:"api_token"`
|
||||
DeviceNickname string `json:"device_nickname" yaml:"device_nickname"`
|
||||
PhoneNumbers []string `json:"phone_numbers" yaml:"phone_numbers"`
|
||||
}
|
||||
|
||||
// PushoverConfig - Pushover notifications.
|
||||
type PushoverConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
AppToken string `json:"app_token" yaml:"app_token"`
|
||||
Recipients []string `json:"recipients" yaml:"recipients"`
|
||||
}
|
||||
|
||||
// RedditConfig - Reddit notifications.
|
||||
type RedditConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
ClientID string `json:"client_id" yaml:"client_id"`
|
||||
ClientSecret string `json:"client_secret" yaml:"client_secret"`
|
||||
Username string `json:"username" yaml:"username"`
|
||||
Password string `json:"password" yaml:"password"`
|
||||
Recipients []string `json:"recipients" yaml:"recipients"`
|
||||
}
|
||||
|
||||
// RocketChatConfig - Rocket.Chat notifications.
|
||||
type RocketChatConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
ServerURL string `json:"server_url" yaml:"server_url"`
|
||||
Scheme string `json:"scheme" yaml:"scheme"`
|
||||
UserID string `json:"user_id" yaml:"user_id"`
|
||||
Token string `json:"token" yaml:"token"`
|
||||
Channels []string `json:"channels" yaml:"channels"`
|
||||
}
|
||||
|
||||
// SendGridConfig - SendGrid notifications.
|
||||
type SendGridConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
APIKey string `json:"api_key" yaml:"api_key"`
|
||||
SenderAddress string `json:"sender_address" yaml:"sender_address"`
|
||||
SenderName string `json:"sender_name" yaml:"sender_name"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// SlackConfig - Slack notifications.
|
||||
type SlackConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
APIToken string `json:"api_token" yaml:"api_token"`
|
||||
Channels []string `json:"channels" yaml:"channels"`
|
||||
}
|
||||
|
||||
// SyslogConfig - Syslog notifications.
|
||||
type SyslogConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Priority string `json:"priority,omitempty" yaml:"priority,omitempty"`
|
||||
Network string `json:"network,omitempty" yaml:"network,omitempty"`
|
||||
Address string `json:"address,omitempty" yaml:"address,omitempty"`
|
||||
Tag string `json:"tag,omitempty" yaml:"tag,omitempty"`
|
||||
}
|
||||
|
||||
// TelegramConfig - Telegram notifications.
|
||||
type TelegramConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
APIToken string `json:"api_token" yaml:"api_token"`
|
||||
ChatIDs []string `json:"chat_ids" yaml:"chat_ids"`
|
||||
ParseMode string `json:"parse_mode,omitempty" yaml:"parse_mode,omitempty"`
|
||||
}
|
||||
|
||||
// TextMagicConfig - TextMagic notifications.
|
||||
type TextMagicConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Username string `json:"username" yaml:"username"`
|
||||
APIKey string `json:"api_key" yaml:"api_key"`
|
||||
PhoneNumbers []string `json:"phone_numbers" yaml:"phone_numbers"`
|
||||
}
|
||||
|
||||
// TwilioConfig - Twilio notifications.
|
||||
type TwilioConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
AccountSID string `json:"account_sid" yaml:"account_sid"`
|
||||
AuthToken string `json:"auth_token" yaml:"auth_token"`
|
||||
FromNumber string `json:"from_number" yaml:"from_number"`
|
||||
PhoneNumbers []string `json:"phone_numbers" yaml:"phone_numbers"`
|
||||
}
|
||||
|
||||
// TwitterConfig - Twitter notifications.
|
||||
type TwitterConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
ConsumerKey string `json:"consumer_key" yaml:"consumer_key"`
|
||||
ConsumerSecret string `json:"consumer_secret" yaml:"consumer_secret"`
|
||||
AccessToken string `json:"access_token" yaml:"access_token"`
|
||||
AccessTokenSecret string `json:"access_token_secret" yaml:"access_token_secret"`
|
||||
Recipients []string `json:"recipients" yaml:"recipients"`
|
||||
}
|
||||
|
||||
// ViberConfig - Viber notifications.
|
||||
type ViberConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
AppKey string `json:"app_key" yaml:"app_key"`
|
||||
SenderName string `json:"sender_name" yaml:"sender_name"`
|
||||
SenderAvatar string `json:"sender_avatar,omitempty" yaml:"sender_avatar,omitempty"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
WebhookURL string `json:"webhook_url,omitempty" yaml:"webhook_url,omitempty"`
|
||||
}
|
||||
|
||||
// WebPushConfig - Webpush notifications.
|
||||
type WebPushConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
VAPIDPublicKey string `json:"vapid_public_key" yaml:"vapid_public_key"`
|
||||
VAPIDPrivateKey string `json:"vapid_private_key" yaml:"vapid_private_key"`
|
||||
Subscriptions []WebPushSubscriptionConfig `json:"subscriptions" yaml:"subscriptions"`
|
||||
}
|
||||
|
||||
// WebPushSubscriptionConfig - Webpush subscription data.
|
||||
type WebPushSubscriptionConfig struct {
|
||||
Endpoint string `json:"endpoint" yaml:"endpoint"`
|
||||
Keys WebPushKeysConfig `json:"keys" yaml:"keys"`
|
||||
}
|
||||
|
||||
// WebPushKeysConfig - Webpush subscription keys.
|
||||
type WebPushKeysConfig struct {
|
||||
Auth string `json:"auth" yaml:"auth"`
|
||||
P256DH string `json:"p256dh" yaml:"p256dh"`
|
||||
}
|
||||
|
||||
// WeChatConfig - WeChat notifications.
|
||||
type WeChatConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
AppID string `json:"app_id" yaml:"app_id"`
|
||||
AppSecret string `json:"app_secret" yaml:"app_secret"`
|
||||
Token string `json:"token" yaml:"token"`
|
||||
EncodingAESKey string `json:"encoding_aes_key" yaml:"encoding_aes_key"`
|
||||
Receivers []string `json:"receivers" yaml:"receivers"`
|
||||
}
|
||||
|
||||
// WhatsAppConfig - WhatsApp notifications.
|
||||
type WhatsAppConfig struct {
|
||||
NotificationServiceConfig `json:",inline" yaml:",inline"`
|
||||
Receivers []string `json:"receivers,omitempty" yaml:"receivers,omitempty"`
|
||||
}
|
||||
|
||||
func defaultNotificationsConfig() *NotificationsConfig {
|
||||
return &NotificationsConfig{
|
||||
Enabled: false,
|
||||
Events: []string{},
|
||||
Templates: map[string]*NotificationTemplateConfig{},
|
||||
Services: &NotificationsServicesConfig{},
|
||||
}
|
||||
}
|
||||
+135
-65
@@ -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,151 @@ 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.
|
||||
Notifications *NotificationsConfig `json:"notifications" yaml:"notifications"`
|
||||
|
||||
// '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 +204,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 +219,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 +265,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 +288,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",
|
||||
@@ -228,8 +297,9 @@ func getDefaultServerConfig() *ServerConfig {
|
||||
},
|
||||
},
|
||||
},
|
||||
DonutBypass: 3,
|
||||
CC: map[string]string{},
|
||||
CXX: map[string]string{},
|
||||
DonutBypass: 3,
|
||||
Notifications: defaultNotificationsConfig(),
|
||||
CC: map[string]string{},
|
||||
CXX: map[string]string{},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
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
|
||||
notifications:
|
||||
enabled: true
|
||||
events:
|
||||
- session-connected
|
||||
services:
|
||||
slack:
|
||||
enabled: true
|
||||
api_token: "slack-token"
|
||||
channels:
|
||||
- "C123"
|
||||
templates:
|
||||
session-connected:
|
||||
type: "text"
|
||||
template: "session.tmpl"
|
||||
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.Notifications == nil || !config.Notifications.Enabled {
|
||||
t.Fatalf("expected notifications enabled")
|
||||
}
|
||||
if len(config.Notifications.Events) != 1 || config.Notifications.Events[0] != "session-connected" {
|
||||
t.Fatalf("unexpected notifications events: %v", config.Notifications.Events)
|
||||
}
|
||||
if config.Notifications.Services == nil || config.Notifications.Services.Slack == nil {
|
||||
t.Fatalf("expected slack notifications config")
|
||||
}
|
||||
if !config.Notifications.Services.Slack.Enabled || config.Notifications.Services.Slack.APIToken != "slack-token" {
|
||||
t.Fatalf("unexpected slack notifications config: %#v", config.Notifications.Services.Slack)
|
||||
}
|
||||
if len(config.Notifications.Services.Slack.Channels) != 1 || config.Notifications.Services.Slack.Channels[0] != "C123" {
|
||||
t.Fatalf("unexpected slack channels: %v", config.Notifications.Services.Slack.Channels)
|
||||
}
|
||||
if config.Notifications.Templates == nil {
|
||||
t.Fatalf("expected notifications templates config")
|
||||
}
|
||||
tmpl := config.Notifications.Templates["session-connected"]
|
||||
if tmpl == nil || tmpl.Type != "text" || tmpl.Template != "session.tmpl" {
|
||||
t.Fatalf("unexpected template config: %#v", tmpl)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
+22
-2
@@ -789,8 +789,18 @@ func AddContent(pbWebContent *clientpb.WebContent, webContentDir string) (*clien
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
dbWebContent.ContentType = pbWebContent.ContentType
|
||||
dbWebContent.Size = pbWebContent.Size
|
||||
if pbWebContent.ContentType != "" {
|
||||
dbWebContent.ContentType = pbWebContent.ContentType
|
||||
}
|
||||
if pbWebContent.Size != 0 {
|
||||
dbWebContent.Size = pbWebContent.Size
|
||||
}
|
||||
if pbWebContent.OriginalFile != "" {
|
||||
dbWebContent.OriginalFile = pbWebContent.OriginalFile
|
||||
}
|
||||
if pbWebContent.Sha256 != "" {
|
||||
dbWebContent.Sha256 = pbWebContent.Sha256
|
||||
}
|
||||
|
||||
dbModelWebContent := models.WebContentFromProtobuf(dbWebContent)
|
||||
err = Session().Save(&dbModelWebContent).Error
|
||||
@@ -1484,3 +1494,13 @@ func GetCertificateInfo(categoryOptions uint32, cn string) ([]*models.Certificat
|
||||
// Processing of the data can occur at the caller. It does not need to happen here.
|
||||
return certInfo, nil
|
||||
}
|
||||
|
||||
// CertificateAuthorities - Return all certificate authorities
|
||||
func CertificateAuthorities() ([]*models.CertificateAuthority, error) {
|
||||
authorities := []*models.CertificateAuthority{}
|
||||
err := Session().Where(&models.CertificateAuthority{}).Find(&authorities).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return authorities, nil
|
||||
}
|
||||
@@ -8,6 +8,7 @@ Database models and ORM definitions for server state. Defines data schemas, rela
|
||||
|
||||
- `beacon.go` – Defines the beacon ORM model and associated relations.
|
||||
- `canary.go` – Stores DNS canary records and status flags.
|
||||
- `certificate_authorities.go` – Stores certificate authority PEM data for the server.
|
||||
- `certificates.go` – Persists certificate metadata for listeners and operators.
|
||||
- `crackstations.go` – Models cracking nodes and benchmark data.
|
||||
- `credentials.go` – Holds captured credential entries and tags.
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package models
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2021 Bishop Fox
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CertificateAuthority - Certificate authority database model
|
||||
// Stored separately from leaf certificates.
|
||||
type CertificateAuthority struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
CreatedAt time.Time `gorm:"->;<-:create;"`
|
||||
CommonName string
|
||||
CAType string
|
||||
CertificatePEM string
|
||||
PrivateKeyPEM string
|
||||
}
|
||||
|
||||
// BeforeCreate - GORM hook to automatically set values
|
||||
func (c *CertificateAuthority) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
c.ID, err = uuid.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.CreatedAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
+20
-14
@@ -67,9 +67,11 @@ type WebContent struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
WebsiteID uuid.UUID `gorm:"type:uuid;"`
|
||||
|
||||
Path string `gorm:"primaryKey"`
|
||||
Size uint64
|
||||
ContentType string
|
||||
Path string `gorm:"primaryKey"`
|
||||
Size uint64
|
||||
ContentType string
|
||||
OriginalFile string
|
||||
Sha256 string
|
||||
}
|
||||
|
||||
// BeforeCreate - GORM hook to automatically set values
|
||||
@@ -81,12 +83,14 @@ func (wc *WebContent) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
// ToProtobuf - Converts to protobuf object
|
||||
func (wc *WebContent) ToProtobuf(content *[]byte) *clientpb.WebContent {
|
||||
return &clientpb.WebContent{
|
||||
ID: wc.ID.String(),
|
||||
WebsiteID: wc.WebsiteID.String(),
|
||||
Path: wc.Path,
|
||||
Size: uint64(wc.Size),
|
||||
ContentType: wc.ContentType,
|
||||
Content: *content,
|
||||
ID: wc.ID.String(),
|
||||
WebsiteID: wc.WebsiteID.String(),
|
||||
Path: wc.Path,
|
||||
Size: uint64(wc.Size),
|
||||
ContentType: wc.ContentType,
|
||||
OriginalFile: wc.OriginalFile,
|
||||
Sha256: wc.Sha256,
|
||||
Content: *content,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +99,12 @@ func WebContentFromProtobuf(pbWebContent *clientpb.WebContent) WebContent {
|
||||
websiteUUID, _ := uuid.FromString(pbWebContent.WebsiteID)
|
||||
|
||||
return WebContent{
|
||||
ID: siteUUID,
|
||||
WebsiteID: websiteUUID,
|
||||
Path: pbWebContent.Path,
|
||||
Size: pbWebContent.Size,
|
||||
ContentType: pbWebContent.ContentType,
|
||||
ID: siteUUID,
|
||||
WebsiteID: websiteUUID,
|
||||
Path: pbWebContent.Path,
|
||||
Size: pbWebContent.Size,
|
||||
ContentType: pbWebContent.ContentType,
|
||||
OriginalFile: pbWebContent.OriginalFile,
|
||||
Sha256: pbWebContent.Sha256,
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ func newDBClient() *gorm.DB {
|
||||
&models.CrackCommand{},
|
||||
&models.CrackFile{},
|
||||
&models.CrackFileChunk{},
|
||||
&models.CertificateAuthority{},
|
||||
&models.Certificate{},
|
||||
&models.Host{},
|
||||
&models.KeyValue{},
|
||||
|
||||
@@ -136,6 +136,10 @@ func TestSliverSharedLibWindows(t *testing.T) {
|
||||
// 386
|
||||
multiLibrary(t, "windows", "386", true)
|
||||
multiLibrary(t, "windows", "386", false)
|
||||
|
||||
// Shared Library Shellcode
|
||||
multiWindowsLibraryShellcode(t, true)
|
||||
multiWindowsLibraryShellcode(t, false)
|
||||
}
|
||||
|
||||
func TestSliverExecutableLinux(t *testing.T) {
|
||||
@@ -554,7 +558,8 @@ func multiWindowsLibraryShellcode(t *testing.T, debug bool) {
|
||||
Debug: debug,
|
||||
ObfuscateSymbols: true,
|
||||
Format: clientpb.OutputFormat_SHELLCODE,
|
||||
IsSharedLib: true,
|
||||
IsShellcode: true,
|
||||
IsSharedLib: false,
|
||||
Exports: []string{"FoobarW"},
|
||||
IncludeMTLS: true,
|
||||
IncludeHTTP: true,
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,113 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func resolveEnvString(value string) (string, bool, bool) {
|
||||
name, ok := parseEnvVar(value)
|
||||
if !ok {
|
||||
return value, false, false
|
||||
}
|
||||
expanded, found := os.LookupEnv(name)
|
||||
if !found {
|
||||
notificationsLog.Warnf("Notifications env var %q is not set", name)
|
||||
return "", true, false
|
||||
}
|
||||
if expanded == "" {
|
||||
notificationsLog.Debugf("Notifications env var %q is set but empty", name)
|
||||
}
|
||||
return expanded, true, true
|
||||
}
|
||||
|
||||
func parseEnvVar(value string) (string, bool) {
|
||||
if len(value) < 2 || value[0] != '$' {
|
||||
return "", false
|
||||
}
|
||||
name := value[1:]
|
||||
if strings.HasPrefix(name, "{") && strings.HasSuffix(name, "}") {
|
||||
name = strings.TrimSuffix(strings.TrimPrefix(name, "{"), "}")
|
||||
}
|
||||
if !isValidEnvName(name) {
|
||||
return "", false
|
||||
}
|
||||
return name, true
|
||||
}
|
||||
|
||||
func isValidEnvName(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
for i, r := range name {
|
||||
if i == 0 {
|
||||
if r != '_' && !unicode.IsLetter(r) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
if r != '_' && !unicode.IsLetter(r) && !unicode.IsDigit(r) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func expandEnv(target any) {
|
||||
if target == nil {
|
||||
return
|
||||
}
|
||||
value := reflect.ValueOf(target)
|
||||
if value.Kind() != reflect.Pointer {
|
||||
return
|
||||
}
|
||||
expandEnvValue(value)
|
||||
}
|
||||
|
||||
func expandEnvValue(value reflect.Value) {
|
||||
if !value.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
if value.Kind() == reflect.Pointer {
|
||||
if value.IsNil() {
|
||||
return
|
||||
}
|
||||
expandEnvValue(value.Elem())
|
||||
return
|
||||
}
|
||||
|
||||
switch value.Kind() {
|
||||
case reflect.String:
|
||||
if !value.CanSet() {
|
||||
return
|
||||
}
|
||||
expanded, ok, _ := resolveEnvString(value.String())
|
||||
if ok {
|
||||
value.SetString(expanded)
|
||||
}
|
||||
case reflect.Struct:
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
expandEnvValue(value.Field(i))
|
||||
}
|
||||
case reflect.Slice:
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
expandEnvValue(value.Index(i))
|
||||
}
|
||||
case reflect.Map:
|
||||
if value.Type().Key().Kind() != reflect.String {
|
||||
return
|
||||
}
|
||||
if value.Type().Elem().Kind() == reflect.String {
|
||||
for _, key := range value.MapKeys() {
|
||||
val := value.MapIndex(key)
|
||||
expanded, ok, _ := resolveEnvString(val.String())
|
||||
if ok {
|
||||
value.SetMapIndex(key, reflect.ValueOf(expanded))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
)
|
||||
|
||||
func TestResolveEnvString(t *testing.T) {
|
||||
t.Setenv("SLIVER_NOTIFY_TEST", "secret")
|
||||
|
||||
got, ok, found := resolveEnvString("$SLIVER_NOTIFY_TEST")
|
||||
if !ok || !found || got != "secret" {
|
||||
t.Fatalf("expected env replacement, got %q (ok=%v)", got, ok)
|
||||
}
|
||||
|
||||
got, ok, found = resolveEnvString("${SLIVER_NOTIFY_TEST}")
|
||||
if !ok || !found || got != "secret" {
|
||||
t.Fatalf("expected env replacement with braces, got %q (ok=%v)", got, ok)
|
||||
}
|
||||
|
||||
got, ok, found = resolveEnvString("plain")
|
||||
if ok || found || got != "plain" {
|
||||
t.Fatalf("expected literal value, got %q (ok=%v)", got, ok)
|
||||
}
|
||||
|
||||
got, ok, found = resolveEnvString("$SLIVER_NOTIFY_MISSING")
|
||||
if !ok || found || got != "" {
|
||||
t.Fatalf("expected empty string for missing env, got %q (ok=%v)", got, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpandEnvConfig(t *testing.T) {
|
||||
t.Setenv("SLIVER_NOTIFY_SLACK_TOKEN", "slack-secret")
|
||||
t.Setenv("SLIVER_NOTIFY_SLACK_CHANNEL", "C123")
|
||||
t.Setenv("SLIVER_NOTIFY_WEBHOOK", "https://example.com/hook")
|
||||
t.Setenv("SLIVER_NOTIFY_HEADER", "Bearer 123")
|
||||
|
||||
cfg := &configs.NotificationsConfig{
|
||||
Enabled: true,
|
||||
Services: &configs.NotificationsServicesConfig{
|
||||
Slack: &configs.SlackConfig{
|
||||
NotificationServiceConfig: configs.NotificationServiceConfig{Enabled: true},
|
||||
APIToken: "$SLIVER_NOTIFY_SLACK_TOKEN",
|
||||
Channels: []string{"$SLIVER_NOTIFY_SLACK_CHANNEL"},
|
||||
},
|
||||
HTTP: &configs.HTTPConfig{
|
||||
NotificationServiceConfig: configs.NotificationServiceConfig{Enabled: true},
|
||||
Webhooks: []configs.HTTPWebhookConfig{
|
||||
{
|
||||
URL: "$SLIVER_NOTIFY_WEBHOOK",
|
||||
Headers: map[string]string{
|
||||
"Authorization": "$SLIVER_NOTIFY_HEADER",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expandEnv(cfg)
|
||||
|
||||
if cfg.Services.Slack.APIToken != "slack-secret" {
|
||||
t.Fatalf("expected slack token to expand, got %q", cfg.Services.Slack.APIToken)
|
||||
}
|
||||
if len(cfg.Services.Slack.Channels) != 1 || cfg.Services.Slack.Channels[0] != "C123" {
|
||||
t.Fatalf("unexpected slack channels: %v", cfg.Services.Slack.Channels)
|
||||
}
|
||||
if cfg.Services.HTTP.Webhooks[0].URL != "https://example.com/hook" {
|
||||
t.Fatalf("expected webhook url to expand, got %q", cfg.Services.HTTP.Webhooks[0].URL)
|
||||
}
|
||||
if cfg.Services.HTTP.Webhooks[0].Headers["Authorization"] != "Bearer 123" {
|
||||
t.Fatalf("expected header to expand, got %q", cfg.Services.HTTP.Webhooks[0].Headers["Authorization"])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package notifications
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestResolveEvents(t *testing.T) {
|
||||
global := []string{"a", "b"}
|
||||
service := []string{"c"}
|
||||
|
||||
set := resolveEvents(global, nil)
|
||||
if len(set) != 2 {
|
||||
t.Fatalf("expected global events, got %v", set)
|
||||
}
|
||||
|
||||
set = resolveEvents(global, service)
|
||||
if len(set) != 1 {
|
||||
t.Fatalf("expected service events, got %v", set)
|
||||
}
|
||||
if _, ok := set["c"]; !ok {
|
||||
t.Fatalf("expected service event 'c' to be present")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifierEntryAllows(t *testing.T) {
|
||||
entry := notifierEntry{
|
||||
events: map[string]struct{}{"event-a": {}},
|
||||
}
|
||||
if !entry.allows("event-a") {
|
||||
t.Fatalf("expected event-a to be allowed")
|
||||
}
|
||||
if entry.allows("event-b") {
|
||||
t.Fatalf("did not expect event-b to be allowed")
|
||||
}
|
||||
|
||||
entry.events = nil
|
||||
if !entry.allows("anything") {
|
||||
t.Fatalf("expected empty filter to allow all events")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
)
|
||||
|
||||
func formatEvent(event core.Event) (string, string) {
|
||||
subject := fmt.Sprintf("Sliver event: %s", event.EventType)
|
||||
lines := []string{
|
||||
fmt.Sprintf("Event: %s", event.EventType),
|
||||
}
|
||||
|
||||
if event.Session != nil {
|
||||
lines = append(lines, formatSession(event.Session)...)
|
||||
}
|
||||
if event.Beacon != nil {
|
||||
lines = append(lines, formatBeacon(event.Beacon)...)
|
||||
}
|
||||
if event.Job != nil {
|
||||
lines = append(lines, formatJob(event.Job)...)
|
||||
}
|
||||
if event.Client != nil {
|
||||
lines = append(lines, formatClient(event.Client)...)
|
||||
}
|
||||
if event.Err != nil {
|
||||
lines = append(lines, fmt.Sprintf("Error: %s", event.Err.Error()))
|
||||
}
|
||||
|
||||
return subject, strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func formatSession(session *core.Session) []string {
|
||||
lines := []string{
|
||||
fmt.Sprintf("Session ID: %s", session.ID),
|
||||
}
|
||||
if session.Name != "" {
|
||||
lines = append(lines, fmt.Sprintf("Name: %s", session.Name))
|
||||
}
|
||||
if session.Username != "" {
|
||||
lines = append(lines, fmt.Sprintf("User: %s", session.Username))
|
||||
}
|
||||
if session.Hostname != "" {
|
||||
lines = append(lines, fmt.Sprintf("Host: %s", session.Hostname))
|
||||
}
|
||||
if session.OS != "" {
|
||||
lines = append(lines, fmt.Sprintf("OS: %s", session.OS))
|
||||
}
|
||||
if session.Connection != nil {
|
||||
if session.Connection.Transport != "" {
|
||||
lines = append(lines, fmt.Sprintf("Transport: %s", session.Connection.Transport))
|
||||
}
|
||||
if session.Connection.RemoteAddress != "" {
|
||||
lines = append(lines, fmt.Sprintf("Remote: %s", session.Connection.RemoteAddress))
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func formatBeacon(beacon *models.Beacon) []string {
|
||||
lines := []string{
|
||||
fmt.Sprintf("Beacon ID: %s", beacon.ID.String()),
|
||||
}
|
||||
if beacon.Name != "" {
|
||||
lines = append(lines, fmt.Sprintf("Name: %s", beacon.Name))
|
||||
}
|
||||
if beacon.Hostname != "" {
|
||||
lines = append(lines, fmt.Sprintf("Host: %s", beacon.Hostname))
|
||||
}
|
||||
if beacon.Username != "" {
|
||||
lines = append(lines, fmt.Sprintf("User: %s", beacon.Username))
|
||||
}
|
||||
if beacon.OS != "" {
|
||||
lines = append(lines, fmt.Sprintf("OS: %s", beacon.OS))
|
||||
}
|
||||
if beacon.Transport != "" {
|
||||
lines = append(lines, fmt.Sprintf("Transport: %s", beacon.Transport))
|
||||
}
|
||||
if beacon.RemoteAddress != "" {
|
||||
lines = append(lines, fmt.Sprintf("Remote: %s", beacon.RemoteAddress))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func formatJob(job *core.Job) []string {
|
||||
lines := []string{
|
||||
fmt.Sprintf("Job ID: %d", job.ID),
|
||||
}
|
||||
if job.Name != "" {
|
||||
lines = append(lines, fmt.Sprintf("Name: %s", job.Name))
|
||||
}
|
||||
if job.Protocol != "" {
|
||||
lines = append(lines, fmt.Sprintf("Protocol: %s", job.Protocol))
|
||||
}
|
||||
if job.Port != 0 {
|
||||
lines = append(lines, fmt.Sprintf("Port: %d", job.Port))
|
||||
}
|
||||
if len(job.Domains) > 0 {
|
||||
lines = append(lines, fmt.Sprintf("Domains: %s", strings.Join(job.Domains, ", ")))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func formatClient(client *core.Client) []string {
|
||||
lines := []string{
|
||||
fmt.Sprintf("Client ID: %d", client.ID),
|
||||
}
|
||||
if client.Operator != nil && client.Operator.Name != "" {
|
||||
lines = append(lines, fmt.Sprintf("Operator: %s", client.Operator.Name))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
)
|
||||
|
||||
const (
|
||||
notificationQueueSize = 100
|
||||
notificationTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
notificationsLog = log.NamedLogger("notifications", "manager")
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
enabled bool
|
||||
entries []notifierEntry
|
||||
|
||||
templates map[string]templateSpec
|
||||
renderer *templateRenderer
|
||||
|
||||
queue chan core.Event
|
||||
cancel context.CancelFunc
|
||||
started bool
|
||||
}
|
||||
|
||||
func (m *Manager) Start() {
|
||||
if m == nil || m.started || !m.enabled || len(m.entries) == 0 {
|
||||
if m != nil && !m.enabled {
|
||||
notificationsLog.Infof("Notifications disabled")
|
||||
}
|
||||
return
|
||||
}
|
||||
m.started = true
|
||||
m.queue = make(chan core.Event, notificationQueueSize)
|
||||
|
||||
notificationsLog.Infof("Starting notifications with %d service(s)", len(m.entries))
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
m.cancel = cancel
|
||||
|
||||
events := core.EventBroker.Subscribe()
|
||||
go func() {
|
||||
defer core.EventBroker.Unsubscribe(events)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case event, ok := <-events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case m.queue <- event:
|
||||
default:
|
||||
notificationsLog.Warnf("Dropping notification event %q (queue full)", event.EventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case event := <-m.queue:
|
||||
m.dispatch(ctx, event)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *Manager) Stop() {
|
||||
if m == nil || !m.started {
|
||||
return
|
||||
}
|
||||
if m.cancel != nil {
|
||||
m.cancel()
|
||||
}
|
||||
m.started = false
|
||||
notificationsLog.Infof("Notifications stopped")
|
||||
}
|
||||
|
||||
func (m *Manager) dispatch(ctx context.Context, event core.Event) {
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
notificationsLog.Errorf("Notification dispatch panic for event %q: %v", event.EventType, recovered)
|
||||
}
|
||||
}()
|
||||
subject, message := formatEvent(event)
|
||||
if m.renderer != nil && len(m.templates) > 0 {
|
||||
if spec, ok := m.templates[event.EventType]; ok {
|
||||
rendered, err := m.renderer.render(spec, buildTemplateData(event, subject, message))
|
||||
if err != nil {
|
||||
notificationsLog.Warnf("Failed to render template %q for event %q: %v", spec.name, event.EventType, err)
|
||||
} else {
|
||||
message = rendered
|
||||
notificationsLog.Debugf("Rendered %s template %q for event %q", spec.typ, spec.name, event.EventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, entry := range m.entries {
|
||||
if !entry.allows(event.EventType) {
|
||||
notificationsLog.Debugf("Skipping notification %s for event %q (filtered)", entry.name, event.EventType)
|
||||
continue
|
||||
}
|
||||
sendCtx, cancel := context.WithTimeout(ctx, notificationTimeout)
|
||||
if err := entry.notifier.Send(sendCtx, subject, message); err != nil {
|
||||
notificationsLog.Warnf("Notification %s failed: %v", entry.name, err)
|
||||
} else {
|
||||
notificationsLog.Debugf("Notification %s delivered for event %q", entry.name, event.EventType)
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
//go:build !windows
|
||||
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/syslog"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/nikoksr/notify"
|
||||
notifysyslog "github.com/nikoksr/notify/service/syslog"
|
||||
)
|
||||
|
||||
func buildSyslog(cfg *configs.SyslogConfig) (notify.Notifier, error) {
|
||||
if cfg == nil {
|
||||
return nil, errors.New("syslog config is nil")
|
||||
}
|
||||
priority := parseSyslogPriority(cfg.Priority)
|
||||
network := strings.TrimSpace(cfg.Network)
|
||||
address := strings.TrimSpace(cfg.Address)
|
||||
tag := strings.TrimSpace(cfg.Tag)
|
||||
if network != "" || address != "" {
|
||||
return notifysyslog.NewFromDial(network, address, priority, tag)
|
||||
}
|
||||
return notifysyslog.New(priority, tag)
|
||||
}
|
||||
|
||||
func parseSyslogPriority(value string) syslog.Priority {
|
||||
value = strings.TrimSpace(strings.ToLower(value))
|
||||
if value == "" {
|
||||
return syslog.LOG_INFO
|
||||
}
|
||||
switch value {
|
||||
case "emerg", "emergency":
|
||||
return syslog.LOG_EMERG
|
||||
case "alert":
|
||||
return syslog.LOG_ALERT
|
||||
case "crit", "critical":
|
||||
return syslog.LOG_CRIT
|
||||
case "err", "error":
|
||||
return syslog.LOG_ERR
|
||||
case "warning", "warn":
|
||||
return syslog.LOG_WARNING
|
||||
case "notice":
|
||||
return syslog.LOG_NOTICE
|
||||
case "info":
|
||||
return syslog.LOG_INFO
|
||||
case "debug":
|
||||
return syslog.LOG_DEBUG
|
||||
default:
|
||||
if numeric, err := strconv.Atoi(value); err == nil {
|
||||
return syslog.Priority(numeric)
|
||||
}
|
||||
}
|
||||
return syslog.LOG_INFO
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
//go:build windows
|
||||
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/bishopfox/sliver/server/configs"
|
||||
"github.com/nikoksr/notify"
|
||||
)
|
||||
|
||||
func buildSyslog(_ *configs.SyslogConfig) (notify.Notifier, error) {
|
||||
return nil, errors.New("syslog notifications are not supported on windows")
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
htmltemplate "html/template"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
texttemplate "text/template"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
)
|
||||
|
||||
const (
|
||||
templateTypeText = "text"
|
||||
templateTypeHTML = "html"
|
||||
)
|
||||
|
||||
type templateSpec struct {
|
||||
name string
|
||||
typ string
|
||||
}
|
||||
|
||||
type templateRenderer struct {
|
||||
baseDir string
|
||||
textTemplates map[string]*texttemplate.Template
|
||||
htmlTemplates map[string]*htmltemplate.Template
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
type templateData struct {
|
||||
EventType string
|
||||
Session *core.Session
|
||||
Beacon *models.Beacon
|
||||
Job *core.Job
|
||||
Client *core.Client
|
||||
Error string
|
||||
DefaultSubject string
|
||||
DefaultMessage string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
func templatesDir() string {
|
||||
return filepath.Join(assets.GetRootAppDir(), "notifications", "templates")
|
||||
}
|
||||
|
||||
func ensureTemplatesDir() (string, error) {
|
||||
dir := templatesDir()
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, nil
|
||||
}
|
||||
|
||||
func newTemplateRenderer(baseDir string) *templateRenderer {
|
||||
return &templateRenderer{
|
||||
baseDir: baseDir,
|
||||
textTemplates: map[string]*texttemplate.Template{},
|
||||
htmlTemplates: map[string]*htmltemplate.Template{},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *templateRenderer) render(spec templateSpec, data templateData) (rendered string, err error) {
|
||||
if spec.name == "" {
|
||||
return "", errors.New("template name is empty")
|
||||
}
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
notificationsLog.Errorf("Template render panic for %q: %v", spec.name, recovered)
|
||||
err = fmt.Errorf("template render panic: %v", recovered)
|
||||
}
|
||||
}()
|
||||
exec, err := r.getTemplate(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := exec.Execute(&buf, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
type templateExecutor interface {
|
||||
Execute(io.Writer, any) error
|
||||
}
|
||||
|
||||
func (r *templateRenderer) getTemplate(spec templateSpec) (templateExecutor, error) {
|
||||
switch spec.typ {
|
||||
case templateTypeHTML:
|
||||
return r.getHTMLTemplate(spec.name)
|
||||
default:
|
||||
return r.getTextTemplate(spec.name)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *templateRenderer) getTextTemplate(name string) (templateExecutor, error) {
|
||||
r.mutex.RLock()
|
||||
if tmpl, ok := r.textTemplates[name]; ok {
|
||||
r.mutex.RUnlock()
|
||||
return tmpl, nil
|
||||
}
|
||||
r.mutex.RUnlock()
|
||||
|
||||
path, err := resolveTemplatePath(r.baseDir, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpl, err := texttemplate.New(name).Parse(string(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.mutex.Lock()
|
||||
r.textTemplates[name] = tmpl
|
||||
r.mutex.Unlock()
|
||||
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
func (r *templateRenderer) getHTMLTemplate(name string) (templateExecutor, error) {
|
||||
r.mutex.RLock()
|
||||
if tmpl, ok := r.htmlTemplates[name]; ok {
|
||||
r.mutex.RUnlock()
|
||||
return tmpl, nil
|
||||
}
|
||||
r.mutex.RUnlock()
|
||||
|
||||
path, err := resolveTemplatePath(r.baseDir, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tmpl, err := htmltemplate.New(name).Parse(string(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.mutex.Lock()
|
||||
r.htmlTemplates[name] = tmpl
|
||||
r.mutex.Unlock()
|
||||
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
func resolveTemplatePath(baseDir, name string) (string, error) {
|
||||
trimmed := strings.TrimSpace(name)
|
||||
if trimmed == "" {
|
||||
return "", errors.New("template name is empty")
|
||||
}
|
||||
if strings.Contains(trimmed, "..") {
|
||||
return "", errors.New("template name contains invalid path segments")
|
||||
}
|
||||
if strings.ContainsAny(trimmed, `/\\`) {
|
||||
return "", errors.New("template name must not contain path separators")
|
||||
}
|
||||
cleanBase := filepath.Clean(baseDir)
|
||||
path := filepath.Join(cleanBase, filepath.Base(trimmed))
|
||||
cleanPath := filepath.Clean(path)
|
||||
if !strings.HasPrefix(cleanPath, cleanBase+string(filepath.Separator)) && cleanPath != cleanBase {
|
||||
return "", errors.New("template path escapes template directory")
|
||||
}
|
||||
return cleanPath, nil
|
||||
}
|
||||
|
||||
func buildTemplateData(event core.Event, subject, message string) templateData {
|
||||
var errText string
|
||||
if event.Err != nil {
|
||||
errText = event.Err.Error()
|
||||
}
|
||||
return templateData{
|
||||
EventType: event.EventType,
|
||||
Session: event.Session,
|
||||
Beacon: event.Beacon,
|
||||
Job: event.Job,
|
||||
Client: event.Client,
|
||||
Error: errText,
|
||||
DefaultSubject: subject,
|
||||
DefaultMessage: message,
|
||||
Timestamp: time.Now().UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
func parseTemplateType(value string) (string, bool) {
|
||||
trimmed := strings.ToLower(strings.TrimSpace(value))
|
||||
if trimmed == "" {
|
||||
return templateTypeText, true
|
||||
}
|
||||
switch trimmed {
|
||||
case "text", "plain":
|
||||
return templateTypeText, true
|
||||
case "html":
|
||||
return templateTypeHTML, true
|
||||
default:
|
||||
return templateTypeText, false
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,5 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
)
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2024 Bishop Fox
|
||||
@@ -30,6 +18,19 @@ import (
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/server/certs"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/db/models"
|
||||
)
|
||||
|
||||
const (
|
||||
timeFormat = "2006-01-02 15:04:05 UTC-0700"
|
||||
)
|
||||
@@ -47,16 +48,7 @@ func convertDatabaseRecordToProtobuf(record *models.Certificate) *clientpb.Certi
|
||||
certData.CN = record.CommonName
|
||||
certData.ID = record.ID.String()
|
||||
certData.CreationTime = record.CreatedAt.Format(timeFormat)
|
||||
switch record.CAType {
|
||||
case certs.MtlsImplantCA:
|
||||
certData.Type = "MTLS (Implant)"
|
||||
case certs.MtlsServerCA:
|
||||
certData.Type = "MTLS (Server)"
|
||||
case certs.HTTPSCA:
|
||||
certData.Type = "HTTPS"
|
||||
default:
|
||||
certData.Type = record.CAType
|
||||
}
|
||||
certData.Type = certificateTypeLabel(record.CAType)
|
||||
|
||||
switch record.KeyType {
|
||||
case certs.ECCKey:
|
||||
@@ -84,6 +76,67 @@ func convertDatabaseRecordToProtobuf(record *models.Certificate) *clientpb.Certi
|
||||
return certData
|
||||
}
|
||||
|
||||
func convertAuthorityRecordToProtobuf(record *models.CertificateAuthority) *clientpb.CertificateAuthorityData {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
authData := &clientpb.CertificateAuthorityData{}
|
||||
authData.ValidityStart = "Unknown (could not parse certificate)"
|
||||
authData.ValidityExpiry = "Unknown (could not parse certificate)"
|
||||
|
||||
authData.CN = record.CommonName
|
||||
authData.ID = record.ID.String()
|
||||
authData.CreationTime = record.CreatedAt.Format(timeFormat)
|
||||
authData.Type = certificateTypeLabel(record.CAType)
|
||||
authData.KeyAlgorithm = "Unknown"
|
||||
|
||||
pemBlock, _ := pem.Decode([]byte(record.CertificatePEM))
|
||||
if pemBlock == nil {
|
||||
return authData
|
||||
}
|
||||
|
||||
certificate, err := x509.ParseCertificate(pemBlock.Bytes)
|
||||
if err != nil {
|
||||
return authData
|
||||
}
|
||||
|
||||
if authData.CN == "" {
|
||||
authData.CN = certificate.Subject.CommonName
|
||||
}
|
||||
|
||||
authData.ValidityStart = certificate.NotBefore.Format(timeFormat)
|
||||
authData.ValidityExpiry = certificate.NotAfter.Format(timeFormat)
|
||||
|
||||
switch certificate.PublicKeyAlgorithm {
|
||||
case x509.ECDSA:
|
||||
authData.KeyAlgorithm = "ECC"
|
||||
case x509.RSA:
|
||||
authData.KeyAlgorithm = "RSA"
|
||||
case x509.Ed25519:
|
||||
authData.KeyAlgorithm = "Ed25519"
|
||||
default:
|
||||
authData.KeyAlgorithm = strings.ToUpper(certificate.PublicKeyAlgorithm.String())
|
||||
}
|
||||
|
||||
return authData
|
||||
}
|
||||
|
||||
func certificateTypeLabel(caType string) string {
|
||||
switch caType {
|
||||
case certs.MtlsImplantCA:
|
||||
return "MTLS (Implant)"
|
||||
case certs.MtlsServerCA:
|
||||
return "MTLS (Server)"
|
||||
case certs.HTTPSCA:
|
||||
return "HTTPS"
|
||||
case certs.OperatorCA:
|
||||
return "Operator"
|
||||
default:
|
||||
return caType
|
||||
}
|
||||
}
|
||||
|
||||
func (rpc *Server) GetCertificateInfo(ctx context.Context, req *clientpb.CertificatesReq) (*clientpb.CertificateInfo, error) {
|
||||
certInfo := clientpb.CertificateInfo{}
|
||||
|
||||
@@ -100,3 +153,19 @@ func (rpc *Server) GetCertificateInfo(ctx context.Context, req *clientpb.Certifi
|
||||
|
||||
return &certInfo, nil
|
||||
}
|
||||
|
||||
func (rpc *Server) GetCertificateAuthorityInfo(ctx context.Context, _ *commonpb.Empty) (*clientpb.CertificateAuthorityInfo, error) {
|
||||
authInfo := clientpb.CertificateAuthorityInfo{}
|
||||
|
||||
caInfoDB, err := db.CertificateAuthorities()
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
|
||||
authInfo.Info = make([]*clientpb.CertificateAuthorityData, len(caInfoDB))
|
||||
for idx, record := range caInfoDB {
|
||||
authInfo.Info[idx] = convertAuthorityRecordToProtobuf(record)
|
||||
}
|
||||
|
||||
return &authInfo, nil
|
||||
}
|
||||
@@ -20,10 +20,13 @@ package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/server/sgn"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// ShellcodeEncode - Encode a piece shellcode
|
||||
@@ -35,9 +38,11 @@ func (rpc *Server) ShellcodeEncoder(ctx context.Context, req *clientpb.Shellcode
|
||||
switch req.Encoder {
|
||||
case clientpb.ShellcodeEncoder_SHIKATA_GA_NAI:
|
||||
rpcLog.Infof("[rpc] Shellcode encoder request for: SHIKATA_GA_NAI")
|
||||
rpcLog.Infof("[rpc] Encoding shellcode (%d bytes) for architecture %s with %d iterations and badchars: %v", len(req.Data), req.Architecture, req.Iterations, req.BadChars)
|
||||
resp.Data, err = sgn.EncodeShellcode(req.Data, req.Architecture, int(req.Iterations), req.BadChars)
|
||||
if err != nil {
|
||||
resp.Response.Err = err.Error()
|
||||
rpcLog.Errorf("[rpc] Failed to encode shellcode: %v", err)
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to encode shellcode (%s)", err))
|
||||
}
|
||||
default:
|
||||
resp.Response.Err = "Unknown encoder"
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/bishopfox/sliver/server/core"
|
||||
"github.com/bishopfox/sliver/server/db"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"github.com/bishopfox/sliver/server/notifications"
|
||||
"github.com/bishopfox/sliver/server/version"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
@@ -78,6 +79,7 @@ type GenericResponse interface {
|
||||
// NewServer - Create new server instance
|
||||
func NewServer() *Server {
|
||||
core.StartEventAutomation()
|
||||
notifications.Start()
|
||||
return &Server{}
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -56,9 +56,12 @@ type SGNConfig struct {
|
||||
// EncodeShellcode - Encode a shellcode
|
||||
func EncodeShellcode(shellcode []byte, arch string, iterations int, badChars []byte) ([]byte, error) {
|
||||
cfg := SGNConfig{
|
||||
Architecture: arch,
|
||||
Iterations: iterations,
|
||||
BadChars: badChars,
|
||||
Architecture: arch,
|
||||
Iterations: iterations,
|
||||
BadChars: badChars,
|
||||
PlainDecoder: false,
|
||||
Safe: true,
|
||||
MaxObfuscation: 100,
|
||||
}
|
||||
return EncodeShellcodeWithConfig(shellcode, cfg)
|
||||
}
|
||||
@@ -83,10 +86,10 @@ func EncodeShellcodeWithConfig(shellcode []byte, cfg SGNConfig) ([]byte, error)
|
||||
|
||||
needsConstraints := cfg.Asci || len(cfg.BadChars) > 0
|
||||
seed := sgnpkg.GetRandomByte()
|
||||
const maxAttempts = 512
|
||||
const maxAttempts = 64
|
||||
|
||||
var lastErr error
|
||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
||||
for attempt := range maxAttempts {
|
||||
encoder, err := newEncoderWithConfig(arch, cfg)
|
||||
if err != nil {
|
||||
sgnLog.Errorf("[sgn] EncodeShellcode setup failed: %s", err)
|
||||
|
||||
@@ -19,6 +19,8 @@ package website
|
||||
*/
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -87,14 +89,30 @@ func AddContent(name string, pbWebContent *clientpb.WebContent) error {
|
||||
pbWebContent.WebsiteID = website.ID
|
||||
}
|
||||
|
||||
if pbWebContent.Size == 0 && len(pbWebContent.Content) > 0 {
|
||||
pbWebContent.Size = uint64(len(pbWebContent.Content))
|
||||
}
|
||||
if pbWebContent.OriginalFile == "" {
|
||||
pbWebContent.OriginalFile = filepath.Base(pbWebContent.Path)
|
||||
}
|
||||
|
||||
if len(pbWebContent.Content) > 0 {
|
||||
sha := sha256.Sum256(pbWebContent.Content)
|
||||
pbWebContent.Sha256 = hex.EncodeToString(sha[:])
|
||||
}
|
||||
|
||||
webContent, err := db.AddContent(pbWebContent, webContentDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write content to disk
|
||||
webContentPath := filepath.Join(webContentDir, webContent.ID)
|
||||
return os.WriteFile(webContentPath, pbWebContent.Content, 0600)
|
||||
// Write content to disk when provided (metadata-only updates skip disk writes)
|
||||
if len(pbWebContent.Content) > 0 {
|
||||
webContentPath := filepath.Join(webContentDir, webContent.ID)
|
||||
return os.WriteFile(webContentPath, pbWebContent.Content, 0600)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveContent - Remove website content for a path
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
7.3.2
|
||||
# Keep this pinned version in parity with cel-go
|
||||
@@ -0,0 +1,2 @@
|
||||
*.pb.go linguist-generated=true
|
||||
*.pb.go -diff -merge
|
||||
@@ -0,0 +1,2 @@
|
||||
bazel-*
|
||||
MODULE.bazel.lock
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"]) # Apache 2.0
|
||||
|
||||
go_library(
|
||||
name = "expr",
|
||||
srcs = [
|
||||
"checked.pb.go",
|
||||
"eval.pb.go",
|
||||
"explain.pb.go",
|
||||
"syntax.pb.go",
|
||||
"value.pb.go",
|
||||
],
|
||||
importpath = "cel.dev/expr",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"@org_golang_google_protobuf//reflect/protoreflect",
|
||||
"@org_golang_google_protobuf//runtime/protoimpl",
|
||||
"@org_golang_google_protobuf//types/known/anypb",
|
||||
"@org_golang_google_protobuf//types/known/durationpb",
|
||||
"@org_golang_google_protobuf//types/known/emptypb",
|
||||
"@org_golang_google_protobuf//types/known/structpb",
|
||||
"@org_golang_google_protobuf//types/known/timestamppb",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":expr",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Contributor Code of Conduct
|
||||
## Version 0.1.1 (adapted from 0.3b-angular)
|
||||
|
||||
As contributors and maintainers of the Common Expression Language
|
||||
(CEL) project, we pledge to respect everyone who contributes by
|
||||
posting issues, updating documentation, submitting pull requests,
|
||||
providing feedback in comments, and any other activities.
|
||||
|
||||
Communication through any of CEL's channels (GitHub, Gitter, IRC,
|
||||
mailing lists, Google+, Twitter, etc.) must be constructive and never
|
||||
resort to personal attacks, trolling, public or private harassment,
|
||||
insults, or other unprofessional conduct.
|
||||
|
||||
We promise to extend courtesy and respect to everyone involved in this
|
||||
project regardless of gender, gender identity, sexual orientation,
|
||||
disability, age, race, ethnicity, religion, or level of experience. We
|
||||
expect anyone contributing to the project to do the same.
|
||||
|
||||
If any member of the community violates this code of conduct, the
|
||||
maintainers of the CEL project may take action, removing issues,
|
||||
comments, and PRs or blocking accounts as deemed appropriate.
|
||||
|
||||
If you are subject to or witness unacceptable behavior, or have any
|
||||
other concerns, please email us at
|
||||
[[email protected]](mailto:[email protected]).
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are a
|
||||
few guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement. You (or your employer) retain the copyright to your contribution,
|
||||
this simply gives us permission to use and redistribute your contributions as
|
||||
part of the project. Head over to <https://cla.developers.google.com/> to see
|
||||
your current agreements on file or to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
## What to expect from maintainers
|
||||
|
||||
Expect maintainers to respond to new issues or pull requests within a week.
|
||||
For outstanding and ongoing issues and particularly for long-running
|
||||
pull requests, expect the maintainers to review within a week of a
|
||||
contributor asking for a new review. There is no commitment to resolution --
|
||||
merging or closing a pull request, or fixing or closing an issue -- because some
|
||||
issues will require more discussion than others.
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
# Project Governance
|
||||
|
||||
This document defines the governance process for the CEL language. CEL is
|
||||
Google-developed, but openly governed. Major contributors to the CEL
|
||||
specification and its corresponding implementations constitute the CEL
|
||||
Language Council. New members may be added by a unanimous vote of the
|
||||
Council.
|
||||
|
||||
The MAINTAINERS.md file lists the members of the CEL Language Council, and
|
||||
unofficially indicates the "areas of expertise" of each member with respect
|
||||
to the publicly available CEL repos.
|
||||
|
||||
## Code Changes
|
||||
|
||||
Code changes must follow the standard pull request (PR) model documented in the
|
||||
CONTRIBUTING.md for each CEL repo. All fixes and features must be reviewed by a
|
||||
maintainer. The maintainer reserves the right to request that any feature
|
||||
request (FR) or PR be reviewed by the language council.
|
||||
|
||||
## Syntax and Semantic Changes
|
||||
|
||||
Syntactic and semantic changes must be reviewed by the CEL Language Council.
|
||||
Maintainers may also request language council review at their discretion.
|
||||
|
||||
The review process is as follows:
|
||||
|
||||
- Create a Feature Request in the CEL-Spec repo. The feature description will
|
||||
serve as an abstract for the detailed design document.
|
||||
- Co-develop a design document with the Language Council.
|
||||
- Once the proposer gives the design document approval, the document will be
|
||||
linked to the FR in the CEL-Spec repo and opened for comments to members of
|
||||
the cel-lang-discuss@googlegroups.com.
|
||||
- The Language Council will review the design doc at the next council meeting
|
||||
(once every three weeks) and the council decision included in the document.
|
||||
|
||||
If the proposal is approved, the spec will be updated by a maintainer (if
|
||||
applicable) and a rationale will be included in the CEL-Spec wiki to ensure
|
||||
future developers may follow CEL's growth and direction over time.
|
||||
|
||||
Approved proposals may be implemented by the proposer or by the maintainers as
|
||||
the parties see fit. At the discretion of the maintainer, changes from the
|
||||
approved design are permitted during implementation if they improve the user
|
||||
experience and clarity of the feature.
|
||||
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
# CEL Language Council
|
||||
|
||||
| Name | Company | Area of Expertise |
|
||||
|-----------------|--------------|-------------------|
|
||||
| Alfred Fuller | Facebook | cel-cpp, cel-spec |
|
||||
| Jim Larson | Google | cel-go, cel-spec |
|
||||
| Matthais Blume | Google | cel-spec |
|
||||
| Tristan Swadell | Google | cel-go, cel-spec |
|
||||
|
||||
## Emeritus
|
||||
|
||||
* Sanjay Ghemawat (Google)
|
||||
* Wolfgang Grieskamp (Facebook)
|
||||
Loaded 100 of 4006 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user