mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 01:38:17 -10:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71cd8fa610 | ||
|
|
e9fdcc1285 | ||
|
|
870c995ae2 | ||
|
|
15d7e7c4d1 | ||
|
|
750cdfce0a | ||
|
|
0f128a22b3 | ||
|
|
3296a8f483 | ||
|
|
b77ade2bb9 | ||
|
|
41dc54d736 | ||
|
|
8a0694cb38 | ||
|
|
52c0618bc3 | ||
|
|
e2e1912628 | ||
|
|
36794f1b07 | ||
|
|
ccd8f75b03 | ||
|
|
dc89afe404 | ||
|
|
9db1f3a967 | ||
|
|
a2ab3aede6 | ||
|
|
545fc27d03 | ||
|
|
5bf65aa2b0 | ||
|
|
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 | ||
|
|
3c46cf2210 | ||
|
|
edcdfb5937 | ||
|
|
f81efdb38c | ||
|
|
db34ded0a1 | ||
|
|
8a7a92a9b9 | ||
|
|
3eda32f9d7 | ||
|
|
c18c193ea7 | ||
|
|
79a8758448 |
No files matched your search
@@ -48,8 +48,21 @@ jobs:
|
||||
- name: Make Linux (amd64)
|
||||
run: make linux-amd64
|
||||
|
||||
- name: Determine go-tests flags
|
||||
id: go-tests-flags
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_sha="${{ github.event.pull_request.base.sha }}"
|
||||
head_sha="${{ github.sha }}"
|
||||
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
|
||||
echo "args=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Test Linux
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
|
||||
|
||||
|
||||
macos-build-test:
|
||||
@@ -74,8 +87,21 @@ jobs:
|
||||
- name: Make MacOS (arm64)
|
||||
run: make macos-arm64
|
||||
|
||||
- name: Determine go-tests flags
|
||||
id: go-tests-flags
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base_sha="${{ github.event.pull_request.base.sha }}"
|
||||
head_sha="${{ github.sha }}"
|
||||
if git diff --name-only "$base_sha" "$head_sha" | grep -qE '^(implant/|server/gogo/|server/generate/)'; then
|
||||
echo "args=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "args=--skip-generate" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Test MacOS
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh
|
||||
run: make clean && make && ./sliver-server unpack --force && ./go-tests.sh ${{ steps.go-tests-flags.outputs.args }}
|
||||
|
||||
clients-build:
|
||||
name: Clients Build
|
||||
|
||||
@@ -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)"
|
||||
|
||||
|
||||
+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)
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ Shared registration glue, scaffolding, and helpers for Sliver client console com
|
||||
- `creds/` – Implements the 'creds' command group for the Sliver client console.
|
||||
- `cursed/` – Implements the 'cursed' command group for the Sliver client console.
|
||||
- `dllhijack/` – Implements the 'dllhijack' command group for the Sliver client console.
|
||||
- `edit/` – Implements the 'edit' command for the Sliver client console.
|
||||
- `environment/` – Implements the 'environment' command group for the Sliver client console.
|
||||
- `exec/` – Implements the 'exec' command group for the Sliver client console.
|
||||
- `exit/` – Implements the 'exit' command group for the Sliver client console.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package edit
|
||||
|
||||
import (
|
||||
"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/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the edit command.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
editCmd := &cobra.Command{
|
||||
Use: consts.EditStr,
|
||||
Short: "Edit a remote text file",
|
||||
Long: help.GetHelpFor([]string{consts.EditStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
EditCmd(cmd, con, args)
|
||||
},
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
}
|
||||
|
||||
flags.Bind("", false, editCmd, func(f *pflag.FlagSet) {
|
||||
f.String("syntax", "", "syntax highlighting lexer name (optional)")
|
||||
f.Bool("syntax-select", false, "prompt to select syntax highlighting lexer")
|
||||
f.Bool("line-numbers", false, "show line numbers")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
carapace.Gen(editCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("path to the remote file to edit"),
|
||||
)
|
||||
|
||||
return []*cobra.Command{editCmd}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
package edit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const beaconPollInterval = 1 * time.Second
|
||||
|
||||
// EditCmd downloads a remote text file, opens it in a TUI editor, and optionally uploads changes.
|
||||
func EditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
remotePath := strings.TrimSpace(args[0])
|
||||
if remotePath == "" {
|
||||
con.PrintErrorf("Missing parameter: remote path\n")
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
|
||||
download, err := downloadRemote(ctx, con, cmd, remotePath)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
if !download.Exists {
|
||||
con.PrintErrorf("Remote file does not exist: %s\n", remotePath)
|
||||
return
|
||||
}
|
||||
if download.IsDir {
|
||||
con.PrintErrorf("Remote path is a directory: %s\n", download.Path)
|
||||
return
|
||||
}
|
||||
|
||||
data := download.Data
|
||||
if download.Encoder == "gzip" {
|
||||
data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if !isText(data) {
|
||||
con.PrintErrorf("Refusing to edit non-text file: %s\n", download.Path)
|
||||
return
|
||||
}
|
||||
|
||||
path := download.Path
|
||||
if path == "" {
|
||||
path = remotePath
|
||||
}
|
||||
|
||||
lineEnding := detectLineEnding(data)
|
||||
content := normalizeLineEndings(data)
|
||||
|
||||
lexer, lexerName, err := resolveSyntax(cmd, path, string(data))
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
showLineNumbers, _ := cmd.Flags().GetBool("line-numbers")
|
||||
|
||||
for {
|
||||
result, err := runEditor(content, path, lexer, lexerName, showLineNumbers)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Editor error: %s\n", err)
|
||||
return
|
||||
}
|
||||
content = result.Content
|
||||
|
||||
if result.Action == actionNone {
|
||||
return
|
||||
}
|
||||
|
||||
if !result.Modified {
|
||||
if result.Action == actionSaveQuit {
|
||||
con.PrintInfof("No changes to upload for %s\n", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch result.Action {
|
||||
case actionSaveQuit:
|
||||
uploadNow, err := confirm("Upload changes to remote file (overwrite)?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if !uploadNow {
|
||||
keepEditing, err := confirm("Continue editing?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if keepEditing {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
uploadData := applyLineEnding(content, lineEnding)
|
||||
if err := uploadEditedContent(cmd, con, path, uploadData); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
case actionQuit:
|
||||
if result.Force {
|
||||
return
|
||||
}
|
||||
discard, err := confirm("Discard changes?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if discard {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runEditor(content, filename string, lexer chroma.Lexer, lexerName string, showLineNumbers bool) (editorResult, error) {
|
||||
model := newEditorModel(content, filename, lexer, lexerName, showLineNumbers)
|
||||
program := tea.NewProgram(model, tea.WithAltScreen())
|
||||
finalModel, err := program.Run()
|
||||
if err != nil {
|
||||
return editorResult{}, err
|
||||
}
|
||||
editor, ok := finalModel.(*editorModel)
|
||||
if !ok {
|
||||
return editorResult{}, fmt.Errorf("unexpected editor state")
|
||||
}
|
||||
return editor.result(), nil
|
||||
}
|
||||
|
||||
func downloadRemote(ctx context.Context, con *console.SliverClient, cmd *cobra.Command, path string) (*sliverpb.Download, error) {
|
||||
download, err := con.Rpc.Download(ctx, &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
RestrictedToFile: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
taskID := download.Response.TaskID
|
||||
download = &sliverpb.Download{}
|
||||
if err := waitForBeaconTaskResponse(ctx, con, taskID, download); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return download, nil
|
||||
}
|
||||
|
||||
func uploadEditedContent(cmd *cobra.Command, con *console.SliverClient, path string, data []byte) error {
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
uploadData, err := new(encoders.Gzip).Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Uploading %s ...", path), ctrl)
|
||||
upload, err := con.Rpc.Upload(ctx, &sliverpb.UploadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
Data: uploadData,
|
||||
Encoder: "gzip",
|
||||
IsDirectory: false,
|
||||
Overwrite: true,
|
||||
})
|
||||
if err == nil && upload.Response != nil && upload.Response.Async {
|
||||
taskID := upload.Response.TaskID
|
||||
upload = &sliverpb.Upload{}
|
||||
err = waitForBeaconTaskResponse(ctx, con, taskID, upload)
|
||||
}
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filesystem.PrintUpload(upload, con)
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForBeaconTaskResponse(ctx context.Context, con *console.SliverClient, taskID string, out proto.Message) error {
|
||||
task, err := waitForBeaconTask(ctx, con, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state != "completed" {
|
||||
if state == "" {
|
||||
state = "unknown"
|
||||
}
|
||||
return fmt.Errorf("beacon task %s %s", task.ID, state)
|
||||
}
|
||||
if len(task.Response) == 0 {
|
||||
return fmt.Errorf("beacon task %s returned empty response", task.ID)
|
||||
}
|
||||
return proto.Unmarshal(task.Response, out)
|
||||
}
|
||||
|
||||
func waitForBeaconTask(ctx context.Context, con *console.SliverClient, taskID string) (*clientpb.BeaconTask, error) {
|
||||
ticker := time.NewTicker(beaconPollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
task, err := con.Rpc.GetBeaconTaskContent(ctx, &clientpb.BeaconTask{ID: taskID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state == "completed" || state == "failed" || state == "canceled" {
|
||||
return task, nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveSyntax(cmd *cobra.Command, path string, content string) (chroma.Lexer, string, error) {
|
||||
syntaxName, _ := cmd.Flags().GetString("syntax")
|
||||
selectSyntax, _ := cmd.Flags().GetBool("syntax-select")
|
||||
|
||||
syntaxName = strings.TrimSpace(syntaxName)
|
||||
if strings.EqualFold(syntaxName, "auto") {
|
||||
syntaxName = ""
|
||||
}
|
||||
|
||||
if syntaxName != "" {
|
||||
if strings.EqualFold(syntaxName, "none") {
|
||||
return nil, "none", nil
|
||||
}
|
||||
lexer := lexers.Get(syntaxName)
|
||||
if lexer == nil {
|
||||
return nil, "", fmt.Errorf("unknown syntax lexer: %s", syntaxName)
|
||||
}
|
||||
name := lexer.Config().Name
|
||||
if name == "" {
|
||||
name = syntaxName
|
||||
}
|
||||
return lexer, name, nil
|
||||
}
|
||||
|
||||
if selectSyntax {
|
||||
options := syntaxOptions()
|
||||
selection := "auto"
|
||||
if err := forms.Select("Syntax highlighting", options, &selection); err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return detectSyntax(path, content)
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
if strings.EqualFold(selection, "none") {
|
||||
return nil, "none", nil
|
||||
}
|
||||
if strings.EqualFold(selection, "auto") {
|
||||
return detectSyntax(path, content)
|
||||
}
|
||||
lexer := lexers.Get(selection)
|
||||
if lexer == nil {
|
||||
return nil, "", fmt.Errorf("unknown syntax lexer: %s", selection)
|
||||
}
|
||||
name := lexer.Config().Name
|
||||
if name == "" {
|
||||
name = selection
|
||||
}
|
||||
return lexer, name, nil
|
||||
}
|
||||
|
||||
return detectSyntax(path, content)
|
||||
}
|
||||
|
||||
func detectSyntax(path string, content string) (chroma.Lexer, string, error) {
|
||||
lexer := lexers.Match(path)
|
||||
if lexer == nil {
|
||||
lexer = lexers.Analyse(content)
|
||||
}
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
name := lexer.Config().Name
|
||||
if name == "" {
|
||||
name = "auto"
|
||||
}
|
||||
return lexer, name, nil
|
||||
}
|
||||
|
||||
func syntaxOptions() []string {
|
||||
names := lexers.Names(true)
|
||||
sort.Strings(names)
|
||||
options := make([]string, 0, len(names)+2)
|
||||
options = append(options, "auto", "none")
|
||||
options = append(options, names...)
|
||||
return options
|
||||
}
|
||||
|
||||
func confirm(title string) (bool, error) {
|
||||
answer := false
|
||||
if err := forms.Confirm(title, &answer); err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
func detectLineEnding(data []byte) string {
|
||||
if strings.Contains(string(data), "\r\n") {
|
||||
return "\r\n"
|
||||
}
|
||||
return "\n"
|
||||
}
|
||||
|
||||
func normalizeLineEndings(data []byte) string {
|
||||
content := strings.ReplaceAll(string(data), "\r\n", "\n")
|
||||
content = strings.ReplaceAll(content, "\r", "\n")
|
||||
return content
|
||||
}
|
||||
|
||||
func applyLineEnding(content, lineEnding string) []byte {
|
||||
if lineEnding == "\r\n" {
|
||||
content = strings.ReplaceAll(content, "\n", "\r\n")
|
||||
}
|
||||
return []byte(content)
|
||||
}
|
||||
|
||||
func isText(sample []byte) bool {
|
||||
const max = 1024
|
||||
if len(sample) > max {
|
||||
sample = sample[:max]
|
||||
}
|
||||
for i, c := range string(sample) {
|
||||
if i+utf8.UTFMax > len(sample) {
|
||||
break
|
||||
}
|
||||
if c == 0xFFFD || c < ' ' && c != '\n' && c != '\t' && c != '\f' && c != '\r' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -19,6 +19,8 @@ package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -28,14 +30,77 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const chtimesDefaultLayout = "2006-01-02 15:04:05"
|
||||
|
||||
type chtimesTimeFormat struct {
|
||||
name string
|
||||
parse func(string) (int64, error)
|
||||
}
|
||||
|
||||
func chtimesParseUnixSeconds(value string) (int64, error) {
|
||||
parsed, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func chtimesParseUnixMillis(value string) (int64, error) {
|
||||
parsed, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.UnixMilli(parsed).Unix(), nil
|
||||
}
|
||||
|
||||
func chtimesParseLayout(layout string) func(string) (int64, error) {
|
||||
return func(value string) (int64, error) {
|
||||
parsed, err := time.Parse(layout, value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parsed.Unix(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func chtimesFormatFromFlags(cmd *cobra.Command) (chtimesTimeFormat, error) {
|
||||
formatFlags := []struct {
|
||||
flag string
|
||||
format chtimesTimeFormat
|
||||
}{
|
||||
{flag: "unix", format: chtimesTimeFormat{name: "unix", parse: chtimesParseUnixSeconds}},
|
||||
{flag: "unix-ms", format: chtimesTimeFormat{name: "unix-ms", parse: chtimesParseUnixMillis}},
|
||||
{flag: "rfc3339", format: chtimesTimeFormat{name: "rfc3339", parse: chtimesParseLayout(time.RFC3339)}},
|
||||
{flag: "rfc1123", format: chtimesTimeFormat{name: "rfc1123", parse: chtimesParseLayout(time.RFC1123)}},
|
||||
}
|
||||
|
||||
var selected *chtimesTimeFormat
|
||||
selectedFlag := ""
|
||||
for _, candidate := range formatFlags {
|
||||
enabled, _ := cmd.Flags().GetBool(candidate.flag)
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
if selected != nil {
|
||||
return chtimesTimeFormat{}, fmt.Errorf("only one time format flag can be used (--%s and --%s are both set)", selectedFlag, candidate.flag)
|
||||
}
|
||||
selected = &candidate.format
|
||||
selectedFlag = candidate.flag
|
||||
}
|
||||
|
||||
if selected == nil {
|
||||
return chtimesTimeFormat{name: "datetime", parse: chtimesParseLayout(chtimesDefaultLayout)}, nil
|
||||
}
|
||||
|
||||
return *selected, nil
|
||||
}
|
||||
|
||||
// ChtimesCmd - Change the access and modified time of a file on the remote file system.
|
||||
func ChtimesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
// DateTime layout (https://pkg.go.dev/time)
|
||||
layout := "2006-01-02 15:04:05"
|
||||
filePath := args[0]
|
||||
|
||||
if filePath == "" {
|
||||
@@ -43,33 +108,35 @@ func ChtimesCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
atime := args[1]
|
||||
format, err := chtimesFormatFromFlags(cmd)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
atime := args[1]
|
||||
if atime == "" {
|
||||
con.PrintErrorf("Missing parameter: Last accessed time id\n")
|
||||
return
|
||||
}
|
||||
|
||||
t_a, err := time.Parse(layout, atime)
|
||||
unixAtime, err := format.parse(atime)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
con.PrintErrorf("Invalid access time (%s): %s\n", format.name, err)
|
||||
return
|
||||
}
|
||||
unixAtime := t_a.Unix()
|
||||
|
||||
mtime := args[2]
|
||||
|
||||
if mtime == "" {
|
||||
con.PrintErrorf("Missing parameter: Last modified time id\n")
|
||||
return
|
||||
}
|
||||
|
||||
t_b, err := time.Parse(layout, mtime)
|
||||
unixMtime, err := format.parse(mtime)
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
con.PrintErrorf("Invalid modified time (%s): %s\n", format.name, err)
|
||||
return
|
||||
}
|
||||
unixMtime := t_b.Unix()
|
||||
|
||||
chtimes, err := con.Rpc.Chtimes(context.Background(), &sliverpb.ChtimesReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
|
||||
@@ -56,6 +56,8 @@ var (
|
||||
consts.CdStr: cdHelp,
|
||||
consts.PwdStr: pwdHelp,
|
||||
consts.CatStr: catHelp,
|
||||
consts.EditStr: editHelp,
|
||||
consts.HexEditStr: hexEditHelp,
|
||||
consts.DownloadStr: downloadHelp,
|
||||
consts.GrepStr: grepHelp,
|
||||
consts.HeadStr: headHelp,
|
||||
@@ -63,6 +65,7 @@ var (
|
||||
consts.UploadStr: uploadHelp,
|
||||
consts.MkdirStr: mkdirHelp,
|
||||
consts.RmStr: rmHelp,
|
||||
consts.ChtimesStr: chtimesHelp,
|
||||
consts.ProcdumpStr: procdumpHelp,
|
||||
consts.ElevateStr: elevateHelp,
|
||||
consts.RunAsStr: runAsHelp,
|
||||
@@ -298,9 +301,39 @@ On Windows, escaping is disabled. Instead, '\\' is treated as path separator.
|
||||
rmHelp = `[[.Bold]]Command:[[.Normal]] rm [remote path]
|
||||
[[.Bold]]About:[[.Normal]] Delete a remote file or directory.`
|
||||
|
||||
chtimesHelp = `[[.Bold]]Command:[[.Normal]] chtimes [--unix | --unix-ms | --rfc3339 | --rfc1123] <path> <atime> <mtime>
|
||||
[[.Bold]]About:[[.Normal]] Change access and modification times on a file (timestomp).
|
||||
|
||||
[[.Bold]][[.Underline]]Time Formats[[.Normal]]
|
||||
Default (datetime): "2006-01-02 15:04:05"
|
||||
--unix: 1704067200
|
||||
--unix-ms: 1704067200000
|
||||
--rfc3339: 2024-01-01T00:00:00Z
|
||||
--rfc1123: Mon, 02 Jan 2006 15:04:05 MST
|
||||
|
||||
[[.Bold]][[.Underline]]Examples[[.Normal]]
|
||||
chtimes /tmp/file "2024-01-01 12:34:56" "2024-01-01 12:35:56"
|
||||
chtimes --unix /tmp/file 1704112496 1704112556
|
||||
chtimes --unix-ms /tmp/file 1704112496000 1704112556000
|
||||
chtimes --rfc3339 /tmp/file 2024-01-01T12:34:56Z 2024-01-01T12:35:56Z
|
||||
chtimes --rfc1123 /tmp/file "Mon, 02 Jan 2006 15:04:05 MST" "Mon, 02 Jan 2006 16:04:05 MST"
|
||||
`
|
||||
|
||||
catHelp = `[[.Bold]]Command:[[.Normal]] cat <remote path>
|
||||
[[.Bold]]About:[[.Normal]] Cat a remote file to stdout.`
|
||||
|
||||
editHelp = `[[.Bold]]Command:[[.Normal]] edit <remote path>
|
||||
[[.Bold]]About:[[.Normal]] Download a remote text file, edit it in a built-in TUI editor, and optionally upload changes.
|
||||
[[.Bold]]Keys:[[.Normal]] Normal mode uses vim-like navigation (h/j/k/l) and insert mode (i). Use :wq to save+quit or :q to quit.
|
||||
[[.Bold]]Syntax:[[.Normal]] Use --syntax <lexer> to force a lexer or --syntax-select to pick one interactively.
|
||||
[[.Bold]]Line Numbers:[[.Normal]] Use --line-numbers to start with line numbers on; toggle with :n.`
|
||||
|
||||
hexEditHelp = `[[.Bold]]Command:[[.Normal]] hex-edit [--max-size <size>] [--offset <byte offset>] <remote path>
|
||||
[[.Bold]]About:[[.Normal]] Download a remote file and edit it in a built-in hex editor, then optionally upload changes.
|
||||
[[.Bold]]Keys:[[.Normal]] Normal mode uses vim-like navigation (h/j/k/l). Use i for hex edit mode, a for ASCII edit mode, :wq to save+quit or :q to quit.
|
||||
[[.Bold]]Size Limit:[[.Normal]] Default max size is 8MB. Use --max-size to override (e.g., 512KB, 12MB).
|
||||
[[.Bold]]Offset:[[.Normal]] Use --offset to jump to a specific byte offset when opening the file.`
|
||||
|
||||
downloadHelp = `[[.Bold]]Command:[[.Normal]] download [remote src] <local dst>
|
||||
[[.Bold]]About:[[.Normal]] Download a file or directory from the remote system. Directories will be downloaded as a gzipped TAR file.
|
||||
[[.Bold]][[.Underline]]Filters[[.Normal]]
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package hexedit
|
||||
|
||||
import (
|
||||
"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/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Commands returns the hex-edit command.
|
||||
func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
hexEditCmd := &cobra.Command{
|
||||
Use: consts.HexEditStr + " <remote path>",
|
||||
Short: "Hex edit a remote file",
|
||||
Long: help.GetHelpFor([]string{consts.HexEditStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
GroupID: consts.FilesystemHelpGroup,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
HexEditCmd(cmd, con, args)
|
||||
},
|
||||
}
|
||||
|
||||
flags.Bind("", false, hexEditCmd, func(f *pflag.FlagSet) {
|
||||
f.String("max-size", "8MB", "maximum file size to load (e.g. 8MB, 512KB)")
|
||||
f.Int64("offset", 0, "byte offset to jump to (decimal or 0x...)")
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
})
|
||||
|
||||
carapace.Gen(hexEditCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("path to the remote file to hex edit"),
|
||||
)
|
||||
|
||||
return []*cobra.Command{hexEditCmd}
|
||||
}
|
||||
@@ -0,0 +1,713 @@
|
||||
package hexedit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/util"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type editorMode int
|
||||
|
||||
const (
|
||||
modeNormal editorMode = iota
|
||||
modeHexEdit
|
||||
modeASCIIEdit
|
||||
modeCommand
|
||||
)
|
||||
|
||||
type exitAction int
|
||||
|
||||
const (
|
||||
actionNone exitAction = iota
|
||||
actionQuit
|
||||
actionSaveQuit
|
||||
)
|
||||
|
||||
const bytesPerLine = 16
|
||||
|
||||
type editorResult struct {
|
||||
Data []byte
|
||||
Modified bool
|
||||
Action exitAction
|
||||
Force bool
|
||||
}
|
||||
|
||||
type editorModel struct {
|
||||
data []byte
|
||||
original []byte
|
||||
modified []bool
|
||||
modifiedCount int
|
||||
cursor int
|
||||
top int
|
||||
width int
|
||||
height int
|
||||
mode editorMode
|
||||
command string
|
||||
commandPrefix rune
|
||||
pending rune
|
||||
dirty bool
|
||||
forceQuit bool
|
||||
action exitAction
|
||||
filename string
|
||||
message string
|
||||
clearMessage bool
|
||||
nibbleSet bool
|
||||
nibbleValue byte
|
||||
}
|
||||
|
||||
var (
|
||||
cursorStyle = lipgloss.NewStyle().Reverse(true)
|
||||
modifiedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215"))
|
||||
modifiedCursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("215")).Reverse(true)
|
||||
statusStyle = lipgloss.NewStyle().Reverse(true)
|
||||
lineStyle = lipgloss.NewStyle()
|
||||
)
|
||||
|
||||
func newEditorModel(data []byte, filename string, offset int) *editorModel {
|
||||
copyData := append([]byte(nil), data...)
|
||||
model := &editorModel{
|
||||
data: copyData,
|
||||
original: append([]byte(nil), data...),
|
||||
modified: make([]bool, len(copyData)),
|
||||
mode: modeNormal,
|
||||
filename: filename,
|
||||
commandPrefix: ':',
|
||||
}
|
||||
if offset > 0 && offset < len(copyData) {
|
||||
model.cursor = offset
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
func (m *editorModel) Init() tea.Cmd {
|
||||
if m.width == 0 || m.height == 0 {
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
m.width = w
|
||||
m.height = h
|
||||
} else {
|
||||
m.width = 80
|
||||
m.height = 24
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *editorModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
case tea.KeyMsg:
|
||||
if m.clearMessage {
|
||||
m.message = ""
|
||||
m.clearMessage = false
|
||||
}
|
||||
if msg.Type == tea.KeyCtrlC {
|
||||
m.action = actionQuit
|
||||
return m, tea.Quit
|
||||
}
|
||||
switch m.mode {
|
||||
case modeCommand:
|
||||
return m.handleCommand(msg)
|
||||
case modeHexEdit:
|
||||
return m.handleHexEdit(msg)
|
||||
case modeASCIIEdit:
|
||||
return m.handleASCIIEdit(msg)
|
||||
default:
|
||||
return m.handleNormal(msg)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) View() string {
|
||||
if m.width == 0 || m.height == 0 {
|
||||
if w, h, err := term.GetSize(0); err == nil && w > 0 && h > 0 {
|
||||
m.width = w
|
||||
m.height = h
|
||||
} else {
|
||||
m.width = 80
|
||||
m.height = 24
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
}
|
||||
|
||||
textHeight := m.textHeight()
|
||||
var b strings.Builder
|
||||
for i := 0; i < textHeight; i++ {
|
||||
lineIndex := m.top + i
|
||||
b.WriteString(m.renderLine(lineIndex))
|
||||
if i < textHeight-1 {
|
||||
b.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
if m.height < 2 {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(statusStyle.Width(m.width).Render(m.statusLine()))
|
||||
|
||||
if m.height < 3 {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(lineStyle.Width(m.width).Render(m.commandLine()))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m *editorModel) result() editorResult {
|
||||
data := append([]byte(nil), m.data...)
|
||||
return editorResult{
|
||||
Data: data,
|
||||
Modified: m.dirty,
|
||||
Action: m.action,
|
||||
Force: m.forceQuit,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) handleCommand(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
m.command = ""
|
||||
m.commandPrefix = ':'
|
||||
case tea.KeyEnter:
|
||||
m.executeCommand(strings.TrimSpace(m.command))
|
||||
m.command = ""
|
||||
m.mode = modeNormal
|
||||
m.commandPrefix = ':'
|
||||
if m.action != actionNone {
|
||||
return m, tea.Quit
|
||||
}
|
||||
case tea.KeyBackspace:
|
||||
m.command = popRune(m.command)
|
||||
case tea.KeyRunes:
|
||||
m.command += string(msg.Runes)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleHexEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
m.nibbleSet = false
|
||||
case tea.KeyBackspace:
|
||||
if m.nibbleSet {
|
||||
m.nibbleSet = false
|
||||
return m, nil
|
||||
}
|
||||
m.moveLeft()
|
||||
case tea.KeyLeft:
|
||||
m.moveLeft()
|
||||
m.nibbleSet = false
|
||||
case tea.KeyRight:
|
||||
m.moveRight()
|
||||
m.nibbleSet = false
|
||||
case tea.KeyUp:
|
||||
m.moveUp()
|
||||
m.nibbleSet = false
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
m.nibbleSet = false
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
val, ok := hexNibble(r)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !m.nibbleSet {
|
||||
m.nibbleValue = val << 4
|
||||
m.nibbleSet = true
|
||||
continue
|
||||
}
|
||||
m.setByte(m.nibbleValue | val)
|
||||
m.nibbleSet = false
|
||||
m.moveRight()
|
||||
}
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleASCIIEdit(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
case tea.KeyEsc:
|
||||
m.mode = modeNormal
|
||||
case tea.KeyLeft:
|
||||
m.moveLeft()
|
||||
case tea.KeyRight:
|
||||
m.moveRight()
|
||||
case tea.KeyUp:
|
||||
m.moveUp()
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
if r < 0 || r > 0xFF {
|
||||
continue
|
||||
}
|
||||
m.setByte(byte(r))
|
||||
m.moveRight()
|
||||
}
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleNormal(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.Type {
|
||||
case tea.KeyEsc:
|
||||
m.pending = 0
|
||||
case tea.KeyLeft:
|
||||
m.moveLeft()
|
||||
case tea.KeyRight:
|
||||
m.moveRight()
|
||||
case tea.KeyUp:
|
||||
m.moveUp()
|
||||
case tea.KeyDown:
|
||||
m.moveDown()
|
||||
case tea.KeyRunes:
|
||||
for _, r := range msg.Runes {
|
||||
if m.handleNormalRune(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
m.ensureCursorVisible()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *editorModel) handleNormalRune(r rune) bool {
|
||||
if m.pending != 0 {
|
||||
handled := true
|
||||
switch m.pending {
|
||||
case 'g':
|
||||
if r == 'g' {
|
||||
m.gotoTop()
|
||||
} else {
|
||||
handled = false
|
||||
}
|
||||
default:
|
||||
handled = false
|
||||
}
|
||||
m.pending = 0
|
||||
if handled {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
switch r {
|
||||
case 'h':
|
||||
m.moveLeft()
|
||||
case 'j':
|
||||
m.moveDown()
|
||||
case 'k':
|
||||
m.moveUp()
|
||||
case 'l':
|
||||
m.moveRight()
|
||||
case '0':
|
||||
m.moveLineStart()
|
||||
case '$':
|
||||
m.moveLineEnd()
|
||||
case 'i':
|
||||
m.mode = modeHexEdit
|
||||
m.nibbleSet = false
|
||||
case 'a':
|
||||
m.mode = modeASCIIEdit
|
||||
case 'x':
|
||||
m.setByte(0x00)
|
||||
case 'g':
|
||||
m.pending = 'g'
|
||||
case 'G':
|
||||
m.gotoBottom()
|
||||
case ':':
|
||||
m.mode = modeCommand
|
||||
m.command = ""
|
||||
m.commandPrefix = ':'
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *editorModel) executeCommand(cmd string) {
|
||||
if cmd == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if isNumericOffset(cmd) {
|
||||
if value, err := strconv.ParseInt(cmd, 0, 64); err == nil {
|
||||
m.gotoOffset(int(value))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch strings.ToLower(cmd) {
|
||||
case "q":
|
||||
m.action = actionQuit
|
||||
case "q!":
|
||||
m.action = actionQuit
|
||||
m.forceQuit = true
|
||||
case "wq", "x":
|
||||
m.action = actionSaveQuit
|
||||
case "w":
|
||||
m.message = "Use :wq to upload and exit"
|
||||
m.clearMessage = true
|
||||
default:
|
||||
m.message = fmt.Sprintf("Unknown command: %s", cmd)
|
||||
m.clearMessage = true
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) renderLine(lineIndex int) string {
|
||||
totalWidth := m.textWidth()
|
||||
if totalWidth <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if lineIndex >= m.totalLines() {
|
||||
return lineStyle.Width(totalWidth).Render("~")
|
||||
}
|
||||
|
||||
lineStart := lineIndex * bytesPerLine
|
||||
hexCells := make([]string, bytesPerLine)
|
||||
asciiCells := make([]string, bytesPerLine)
|
||||
|
||||
for i := 0; i < bytesPerLine; i++ {
|
||||
idx := lineStart + i
|
||||
if idx >= len(m.data) {
|
||||
hexCells[i] = " "
|
||||
asciiCells[i] = " "
|
||||
continue
|
||||
}
|
||||
byteVal := m.data[idx]
|
||||
hexRaw := fmt.Sprintf("%02x", byteVal)
|
||||
if idx == m.cursor && m.mode == modeHexEdit && m.nibbleSet {
|
||||
hexRaw = string([]byte{hexChar(m.nibbleValue >> 4), '_'})
|
||||
}
|
||||
asciiRaw := string(printableByte(byteVal))
|
||||
isModified := m.isModified(idx)
|
||||
if idx == m.cursor {
|
||||
if isModified {
|
||||
hexCells[i] = modifiedCursorStyle.Render(hexRaw)
|
||||
asciiCells[i] = modifiedCursorStyle.Render(asciiRaw)
|
||||
} else {
|
||||
hexCells[i] = cursorStyle.Render(hexRaw)
|
||||
asciiCells[i] = cursorStyle.Render(asciiRaw)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if isModified {
|
||||
hexCells[i] = modifiedStyle.Render(hexRaw)
|
||||
asciiCells[i] = modifiedStyle.Render(asciiRaw)
|
||||
continue
|
||||
}
|
||||
hexCells[i] = hexRaw
|
||||
asciiCells[i] = asciiRaw
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf("%08x", lineStart))
|
||||
b.WriteString(": ")
|
||||
for i := 0; i < bytesPerLine; i++ {
|
||||
if i == 8 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteString(hexCells[i])
|
||||
if i < bytesPerLine-1 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
b.WriteString(" |")
|
||||
for i := 0; i < bytesPerLine; i++ {
|
||||
b.WriteString(asciiCells[i])
|
||||
}
|
||||
b.WriteString("|")
|
||||
|
||||
return lineStyle.Width(totalWidth).Render(b.String())
|
||||
}
|
||||
|
||||
func (m *editorModel) statusLine() string {
|
||||
mode := "NORMAL"
|
||||
switch m.mode {
|
||||
case modeHexEdit:
|
||||
mode = "HEX"
|
||||
case modeASCIIEdit:
|
||||
mode = "ASCII"
|
||||
case modeCommand:
|
||||
mode = "COMMAND"
|
||||
}
|
||||
fileName := baseName(m.filename)
|
||||
if fileName == "" {
|
||||
fileName = "[No Name]"
|
||||
}
|
||||
modified := ""
|
||||
if m.dirty {
|
||||
modified = " [+]"
|
||||
}
|
||||
cursor := m.cursor
|
||||
return fmt.Sprintf(" %s %s Off 0x%08x (%d) Size %s%s", mode, fileName, cursor, cursor, util.ByteCountBinary(int64(len(m.data))), modified)
|
||||
}
|
||||
|
||||
func (m *editorModel) commandLine() string {
|
||||
if m.mode == modeCommand {
|
||||
return ":" + m.command
|
||||
}
|
||||
if m.message != "" {
|
||||
return m.message
|
||||
}
|
||||
switch m.mode {
|
||||
case modeHexEdit:
|
||||
return "ESC: normal hex: 0-9/a-f :wq save+quit :q quit"
|
||||
case modeASCIIEdit:
|
||||
return "ESC: normal type to edit bytes :wq save+quit :q quit"
|
||||
default:
|
||||
return "i: hex edit a: ascii edit h/j/k/l move x zero :wq save+quit :q quit"
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) textHeight() int {
|
||||
if m.height <= 2 {
|
||||
return 1
|
||||
}
|
||||
return m.height - 2
|
||||
}
|
||||
|
||||
func (m *editorModel) textWidth() int {
|
||||
if m.width <= 0 {
|
||||
return 1
|
||||
}
|
||||
return m.width
|
||||
}
|
||||
|
||||
func (m *editorModel) totalLines() int {
|
||||
if len(m.data) == 0 {
|
||||
return 1
|
||||
}
|
||||
return (len(m.data) + bytesPerLine - 1) / bytesPerLine
|
||||
}
|
||||
|
||||
func (m *editorModel) ensureCursorVisible() {
|
||||
if len(m.data) == 0 {
|
||||
m.cursor = 0
|
||||
m.top = 0
|
||||
return
|
||||
}
|
||||
if m.cursor < 0 {
|
||||
m.cursor = 0
|
||||
}
|
||||
if m.cursor >= len(m.data) {
|
||||
m.cursor = len(m.data) - 1
|
||||
}
|
||||
|
||||
line := m.cursor / bytesPerLine
|
||||
height := m.textHeight()
|
||||
if line < m.top {
|
||||
m.top = line
|
||||
} else if line >= m.top+height {
|
||||
m.top = line - height + 1
|
||||
}
|
||||
if m.top < 0 {
|
||||
m.top = 0
|
||||
}
|
||||
maxTop := m.totalLines() - height
|
||||
if maxTop < 0 {
|
||||
maxTop = 0
|
||||
}
|
||||
if m.top > maxTop {
|
||||
m.top = maxTop
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveLeft() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveRight() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
if m.cursor < len(m.data)-1 {
|
||||
m.cursor++
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveUp() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
if m.cursor >= bytesPerLine {
|
||||
m.cursor -= bytesPerLine
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveDown() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
if m.cursor+bytesPerLine < len(m.data) {
|
||||
m.cursor += bytesPerLine
|
||||
}
|
||||
}
|
||||
|
||||
func (m *editorModel) moveLineStart() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
line := m.cursor / bytesPerLine
|
||||
m.cursor = line * bytesPerLine
|
||||
}
|
||||
|
||||
func (m *editorModel) moveLineEnd() {
|
||||
if len(m.data) == 0 {
|
||||
return
|
||||
}
|
||||
line := m.cursor / bytesPerLine
|
||||
end := (line+1)*bytesPerLine - 1
|
||||
if end >= len(m.data) {
|
||||
end = len(m.data) - 1
|
||||
}
|
||||
m.cursor = end
|
||||
}
|
||||
|
||||
func (m *editorModel) gotoTop() {
|
||||
if len(m.data) == 0 {
|
||||
m.cursor = 0
|
||||
return
|
||||
}
|
||||
m.cursor = 0
|
||||
}
|
||||
|
||||
func (m *editorModel) gotoBottom() {
|
||||
if len(m.data) == 0 {
|
||||
m.cursor = 0
|
||||
return
|
||||
}
|
||||
m.cursor = len(m.data) - 1
|
||||
}
|
||||
|
||||
func (m *editorModel) gotoOffset(offset int) {
|
||||
if len(m.data) == 0 {
|
||||
m.message = "Empty file"
|
||||
m.clearMessage = true
|
||||
return
|
||||
}
|
||||
if offset < 0 || offset >= len(m.data) {
|
||||
m.message = fmt.Sprintf("Offset out of range: %d", offset)
|
||||
m.clearMessage = true
|
||||
return
|
||||
}
|
||||
m.cursor = offset
|
||||
m.ensureCursorVisible()
|
||||
}
|
||||
|
||||
func (m *editorModel) setByte(value byte) {
|
||||
if len(m.data) == 0 {
|
||||
m.message = "Empty file"
|
||||
m.clearMessage = true
|
||||
return
|
||||
}
|
||||
if m.cursor >= len(m.data) || m.cursor >= len(m.original) {
|
||||
return
|
||||
}
|
||||
wasModified := m.modified[m.cursor]
|
||||
orig := m.original[m.cursor]
|
||||
m.data[m.cursor] = value
|
||||
if value == orig {
|
||||
if wasModified {
|
||||
m.modified[m.cursor] = false
|
||||
if m.modifiedCount > 0 {
|
||||
m.modifiedCount--
|
||||
}
|
||||
}
|
||||
} else if !wasModified {
|
||||
m.modified[m.cursor] = true
|
||||
m.modifiedCount++
|
||||
}
|
||||
m.dirty = m.modifiedCount > 0
|
||||
}
|
||||
|
||||
func printableByte(b byte) byte {
|
||||
if b >= 32 && b <= 126 {
|
||||
return b
|
||||
}
|
||||
return '.'
|
||||
}
|
||||
|
||||
func (m *editorModel) isModified(idx int) bool {
|
||||
if idx < 0 || idx >= len(m.modified) {
|
||||
return false
|
||||
}
|
||||
return m.modified[idx]
|
||||
}
|
||||
|
||||
func hexChar(n byte) byte {
|
||||
if n < 10 {
|
||||
return '0' + n
|
||||
}
|
||||
return 'a' + (n - 10)
|
||||
}
|
||||
|
||||
func hexNibble(r rune) (byte, bool) {
|
||||
switch {
|
||||
case r >= '0' && r <= '9':
|
||||
return byte(r - '0'), true
|
||||
case r >= 'a' && r <= 'f':
|
||||
return byte(r-'a') + 10, true
|
||||
case r >= 'A' && r <= 'F':
|
||||
return byte(r-'A') + 10, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func isNumericOffset(value string) bool {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return false
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(value), "0x") {
|
||||
_, err := strconv.ParseInt(value, 0, 64)
|
||||
return err == nil
|
||||
}
|
||||
for _, r := range value {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func popRune(value string) string {
|
||||
runes := []rune(value)
|
||||
if len(runes) == 0 {
|
||||
return value
|
||||
}
|
||||
return string(runes[:len(runes)-1])
|
||||
}
|
||||
|
||||
func baseName(path string) string {
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
clean := strings.ReplaceAll(path, "\\", "/")
|
||||
if idx := strings.LastIndex(clean, "/"); idx != -1 {
|
||||
return clean[idx+1:]
|
||||
}
|
||||
return path
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
package hexedit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/filesystem"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/bishopfox/sliver/util/encoders"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
beaconPollInterval = 1 * time.Second
|
||||
)
|
||||
|
||||
// HexEditCmd downloads a remote file, opens it in a hex editor, and optionally uploads changes.
|
||||
func HexEditCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
session, beacon := con.ActiveTarget.GetInteractive()
|
||||
if session == nil && beacon == nil {
|
||||
return
|
||||
}
|
||||
|
||||
remotePath := strings.TrimSpace(args[0])
|
||||
if remotePath == "" {
|
||||
con.PrintErrorf("Missing parameter: remote path\n")
|
||||
return
|
||||
}
|
||||
|
||||
maxSizeStr, _ := cmd.Flags().GetString("max-size")
|
||||
maxSize, err := parseByteSize(maxSizeStr)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Invalid --max-size %q: %s\n", maxSizeStr, err)
|
||||
return
|
||||
}
|
||||
if maxSize <= 0 {
|
||||
con.PrintErrorf("--max-size must be greater than zero\n")
|
||||
return
|
||||
}
|
||||
|
||||
offset, _ := cmd.Flags().GetInt64("offset")
|
||||
if offset < 0 {
|
||||
con.PrintErrorf("--offset must be zero or positive\n")
|
||||
return
|
||||
}
|
||||
|
||||
fetchSize := maxSize + 1
|
||||
if fetchSize < maxSize {
|
||||
fetchSize = maxSize
|
||||
}
|
||||
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Downloading %s ...", remotePath), ctrl)
|
||||
download, err := downloadRemote(ctx, con, cmd, remotePath, fetchSize)
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if download.Response != nil && download.Response.Err != "" {
|
||||
con.PrintErrorf("%s\n", download.Response.Err)
|
||||
return
|
||||
}
|
||||
if !download.Exists {
|
||||
con.PrintErrorf("Remote file does not exist: %s\n", remotePath)
|
||||
return
|
||||
}
|
||||
if download.IsDir {
|
||||
con.PrintErrorf("Remote path is a directory: %s\n", download.Path)
|
||||
return
|
||||
}
|
||||
|
||||
data := download.Data
|
||||
if download.Encoder == "gzip" {
|
||||
data, err = new(encoders.Gzip).Decode(download.Data)
|
||||
if err != nil {
|
||||
con.PrintErrorf("Decoding failed %s\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if int64(len(data)) > maxSize {
|
||||
con.PrintErrorf("Remote file size %s exceeds --max-size %s\n", util.ByteCountBinary(int64(len(data))), util.ByteCountBinary(maxSize))
|
||||
return
|
||||
}
|
||||
|
||||
if offset > 0 {
|
||||
if len(data) == 0 {
|
||||
con.PrintErrorf("Offset %d out of range for empty file\n", offset)
|
||||
return
|
||||
}
|
||||
if offset >= int64(len(data)) {
|
||||
con.PrintErrorf("Offset %d out of range (file size %d bytes)\n", offset, len(data))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
path := download.Path
|
||||
if path == "" {
|
||||
path = remotePath
|
||||
}
|
||||
|
||||
for {
|
||||
result, err := runHexEditor(data, path, int(offset))
|
||||
if err != nil {
|
||||
con.PrintErrorf("Editor error: %s\n", err)
|
||||
return
|
||||
}
|
||||
data = result.Data
|
||||
|
||||
if result.Action == actionNone {
|
||||
return
|
||||
}
|
||||
|
||||
if !result.Modified {
|
||||
if result.Action == actionSaveQuit {
|
||||
con.PrintInfof("No changes to upload for %s\n", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch result.Action {
|
||||
case actionSaveQuit:
|
||||
uploadNow, err := confirm("Upload changes to remote file (overwrite)?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if !uploadNow {
|
||||
keepEditing, err := confirm("Continue editing?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if keepEditing {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := uploadEditedContent(cmd, con, path, data); err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
case actionQuit:
|
||||
if result.Force {
|
||||
return
|
||||
}
|
||||
discard, err := confirm("Discard changes?")
|
||||
if err != nil {
|
||||
con.PrintErrorf("%s\n", err)
|
||||
return
|
||||
}
|
||||
if discard {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runHexEditor(data []byte, filename string, offset int) (editorResult, error) {
|
||||
model := newEditorModel(data, filename, offset)
|
||||
program := tea.NewProgram(model, tea.WithAltScreen())
|
||||
finalModel, err := program.Run()
|
||||
if err != nil {
|
||||
return editorResult{}, err
|
||||
}
|
||||
editor, ok := finalModel.(*editorModel)
|
||||
if !ok {
|
||||
return editorResult{}, fmt.Errorf("unexpected editor state")
|
||||
}
|
||||
return editor.result(), nil
|
||||
}
|
||||
|
||||
func downloadRemote(ctx context.Context, con *console.SliverClient, cmd *cobra.Command, path string, maxBytes int64) (*sliverpb.Download, error) {
|
||||
download, err := con.Rpc.Download(ctx, &sliverpb.DownloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
RestrictedToFile: true,
|
||||
MaxBytes: maxBytes,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if download.Response != nil && download.Response.Async {
|
||||
taskID := download.Response.TaskID
|
||||
download = &sliverpb.Download{}
|
||||
if err := waitForBeaconTaskResponse(ctx, con, taskID, download); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return download, nil
|
||||
}
|
||||
|
||||
func uploadEditedContent(cmd *cobra.Command, con *console.SliverClient, path string, data []byte) error {
|
||||
ctx, cancel := con.GrpcContext(cmd)
|
||||
defer cancel()
|
||||
|
||||
uploadData, err := new(encoders.Gzip).Encode(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Uploading %s ...", path), ctrl)
|
||||
upload, err := con.Rpc.Upload(ctx, &sliverpb.UploadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Path: path,
|
||||
Data: uploadData,
|
||||
Encoder: "gzip",
|
||||
IsDirectory: false,
|
||||
Overwrite: true,
|
||||
})
|
||||
if err == nil && upload.Response != nil && upload.Response.Async {
|
||||
taskID := upload.Response.TaskID
|
||||
upload = &sliverpb.Upload{}
|
||||
err = waitForBeaconTaskResponse(ctx, con, taskID, upload)
|
||||
}
|
||||
ctrl <- true
|
||||
<-ctrl
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filesystem.PrintUpload(upload, con)
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitForBeaconTaskResponse(ctx context.Context, con *console.SliverClient, taskID string, out proto.Message) error {
|
||||
task, err := waitForBeaconTask(ctx, con, taskID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state != "completed" {
|
||||
if state == "" {
|
||||
state = "unknown"
|
||||
}
|
||||
return fmt.Errorf("beacon task %s %s", task.ID, state)
|
||||
}
|
||||
if len(task.Response) == 0 {
|
||||
return fmt.Errorf("beacon task %s returned empty response", task.ID)
|
||||
}
|
||||
return proto.Unmarshal(task.Response, out)
|
||||
}
|
||||
|
||||
func waitForBeaconTask(ctx context.Context, con *console.SliverClient, taskID string) (*clientpb.BeaconTask, error) {
|
||||
ticker := time.NewTicker(beaconPollInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
task, err := con.Rpc.GetBeaconTaskContent(ctx, &clientpb.BeaconTask{ID: taskID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := strings.ToLower(task.State)
|
||||
if state == "completed" || state == "failed" || state == "canceled" {
|
||||
return task, nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func confirm(title string) (bool, error) {
|
||||
answer := false
|
||||
if err := forms.Confirm(title, &answer); err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
|
||||
func parseByteSize(input string) (int64, error) {
|
||||
value := strings.TrimSpace(input)
|
||||
if value == "" {
|
||||
return 0, fmt.Errorf("size is required")
|
||||
}
|
||||
value = strings.ReplaceAll(value, " ", "")
|
||||
value = strings.ToUpper(value)
|
||||
|
||||
idx := 0
|
||||
for idx < len(value) {
|
||||
c := value[idx]
|
||||
if (c >= '0' && c <= '9') || c == '.' {
|
||||
idx++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if idx == 0 {
|
||||
return 0, fmt.Errorf("invalid size %q", input)
|
||||
}
|
||||
|
||||
numStr := value[:idx]
|
||||
unitStr := strings.TrimSpace(value[idx:])
|
||||
if unitStr == "" {
|
||||
unitStr = "B"
|
||||
}
|
||||
|
||||
multiplier := int64(1)
|
||||
switch unitStr {
|
||||
case "B":
|
||||
multiplier = 1
|
||||
case "K", "KB", "KIB":
|
||||
multiplier = 1024
|
||||
case "M", "MB", "MIB":
|
||||
multiplier = 1024 * 1024
|
||||
case "G", "GB", "GIB":
|
||||
multiplier = 1024 * 1024 * 1024
|
||||
case "T", "TB", "TIB":
|
||||
multiplier = 1024 * 1024 * 1024 * 1024
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown size unit %q", unitStr)
|
||||
}
|
||||
|
||||
numVal, err := strconv.ParseFloat(numStr, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid size %q", input)
|
||||
}
|
||||
if numVal <= 0 {
|
||||
return 0, fmt.Errorf("size must be greater than zero")
|
||||
}
|
||||
|
||||
size := int64(numVal * float64(multiplier))
|
||||
if size <= 0 {
|
||||
return 0, fmt.Errorf("size is out of range")
|
||||
}
|
||||
return size, nil
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -150,11 +150,15 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
}
|
||||
flags.Bind("", false, chtimesCmd, func(f *pflag.FlagSet) {
|
||||
f.Int64P("timeout", "t", flags.DefaultTimeout, "grpc timeout in seconds")
|
||||
f.Bool("unix", false, "interpret timestamps as Unix seconds")
|
||||
f.Bool("unix-ms", false, "interpret timestamps as Unix milliseconds")
|
||||
f.Bool("rfc3339", false, "interpret timestamps as RFC3339")
|
||||
f.Bool("rfc1123", false, "interpret timestamps as RFC1123")
|
||||
})
|
||||
carapace.Gen(chtimesCmd).PositionalCompletion(
|
||||
carapace.ActionValues().Usage("path to file to change access timestamps"),
|
||||
carapace.ActionValues().Usage("last accessed time in DateTime format, i.e. 2006-01-02 15:04:05"),
|
||||
carapace.ActionValues().Usage("last modified time in DateTime format, i.e. 2006-01-02 15:04:05"),
|
||||
carapace.ActionValues().Usage("last accessed time (default: 2006-01-02 15:04:05; see --help for format flags)"),
|
||||
carapace.ActionValues().Usage("last modified time (default: 2006-01-02 15:04:05; see --help for format flags)"),
|
||||
)
|
||||
|
||||
getprivsCmd := &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
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/backdoor"
|
||||
"github.com/bishopfox/sliver/client/command/cursed"
|
||||
"github.com/bishopfox/sliver/client/command/dllhijack"
|
||||
"github.com/bishopfox/sliver/client/command/edit"
|
||||
"github.com/bishopfox/sliver/client/command/environment"
|
||||
"github.com/bishopfox/sliver/client/command/exec"
|
||||
"github.com/bishopfox/sliver/client/command/extensions"
|
||||
@@ -48,6 +49,7 @@ import (
|
||||
"github.com/bishopfox/sliver/client/command/tasks"
|
||||
"github.com/bishopfox/sliver/client/command/wasm"
|
||||
"github.com/bishopfox/sliver/client/command/wireguard"
|
||||
"github.com/bishopfox/sliver/client/command/hexedit"
|
||||
client "github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/reeflective/console"
|
||||
@@ -79,7 +81,7 @@ func SliverCommands(con *client.SliverClient) console.Commands {
|
||||
// use.Commands,
|
||||
tasks.Commands,
|
||||
pivots.Commands,
|
||||
aka.ImplantCommands,
|
||||
aka.ImplantCommands,
|
||||
)
|
||||
|
||||
// [ Info ]
|
||||
@@ -94,6 +96,8 @@ func SliverCommands(con *client.SliverClient) console.Commands {
|
||||
|
||||
// [ Filesystem ]
|
||||
bind(consts.FilesystemHelpGroup,
|
||||
edit.Commands,
|
||||
hexedit.Commands,
|
||||
filesystem.Commands,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/client/forms"
|
||||
@@ -117,7 +119,7 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
|
||||
}
|
||||
|
||||
if contentType == "" {
|
||||
contentType = sniffContentType(file)
|
||||
contentType = sniffContentType(file, contentPath)
|
||||
}
|
||||
|
||||
web.Contents[webpath] = &clientpb.WebContent{
|
||||
@@ -135,13 +137,19 @@ func confirmAddDirectory() bool {
|
||||
return confirm
|
||||
}
|
||||
|
||||
func sniffContentType(out *os.File) string {
|
||||
func sniffContentType(out *os.File, contentPath string) string {
|
||||
ext := strings.ToLower(filepath.Ext(contentPath))
|
||||
if ext != "" {
|
||||
contentType := mime.TypeByExtension(ext)
|
||||
if contentType != "" {
|
||||
return contentType
|
||||
}
|
||||
}
|
||||
out.Seek(0, io.SeekStart)
|
||||
buffer := make([]byte, fileSampleSize)
|
||||
_, err := out.Read(buffer)
|
||||
if err != nil {
|
||||
return defaultMimeType
|
||||
}
|
||||
contentType := http.DetectContentType(buffer)
|
||||
return contentType
|
||||
return http.DetectContentType(buffer)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -220,6 +220,8 @@ const (
|
||||
CdStr = "cd"
|
||||
PwdStr = "pwd"
|
||||
CatStr = "cat"
|
||||
EditStr = "edit"
|
||||
HexEditStr = "hex-edit"
|
||||
DownloadStr = "download"
|
||||
HeadStr = "head"
|
||||
TailStr = "tail"
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"github.com/bishopfox/sliver/util/leaky"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -210,6 +211,8 @@ const leakyBufSize = 4108 // data.len(2) + hmacsha1(10) + data(4096)
|
||||
var leakyBuf = leaky.NewLeakyBuf(2048, leakyBufSize)
|
||||
|
||||
func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliverpb.SocksData) {
|
||||
// Client Rate Limiter: 10 operations per second, burst of 1
|
||||
limiter := rate.NewLimiter(rate.Limit(10), 1)
|
||||
|
||||
SocksConnPool.Store(frame.TunnelID, conn)
|
||||
|
||||
@@ -241,6 +244,11 @@ func connect(conn net.Conn, stream rpcpb.SliverRPC_SocksProxyClient, frame *sliv
|
||||
return
|
||||
}
|
||||
if n > 0 {
|
||||
if err := limiter.Wait(context.Background()); err != nil {
|
||||
log.Printf("[socks] rate limiter error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
frame.Data = buff[:n]
|
||||
frame.Sequence = ToImplantSequence
|
||||
log.Printf("[socks] (User to Client) to Server to agent Data Sequence %d , Data Size %d \n", ToImplantSequence, len(frame.Data))
|
||||
|
||||
+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
|
||||
}
|
||||
Generated
+396
-406
File diff suppressed because it is too large.
Load diff
@@ -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 +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":"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-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,8 +39,9 @@ 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.4
|
||||
github.com/rsteube/carapace v0.50.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,145 @@ 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/alecthomas/chroma/v2 v2.23.1 // 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,11 +212,11 @@ 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.11.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
|
||||
@@ -111,7 +228,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 +299,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=
|
||||
@@ -44,44 +86,60 @@ github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
|
||||
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||
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,19 +147,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/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.0 h1:58QSJTUxghpa/rEn6+Z9UM/Hy0ipVHDUn9S1mp4i+ZE=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
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=
|
||||
@@ -140,16 +208,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=
|
||||
@@ -158,14 +232,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=
|
||||
@@ -173,19 +256,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=
|
||||
@@ -198,21 +299,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=
|
||||
@@ -221,12 +342,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=
|
||||
@@ -234,31 +363,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=
|
||||
@@ -288,13 +455,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=
|
||||
@@ -313,22 +492,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=
|
||||
@@ -348,20 +539,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=
|
||||
@@ -370,6 +570,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=
|
||||
@@ -378,8 +579,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=
|
||||
@@ -387,10 +609,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=
|
||||
@@ -401,14 +629,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=
|
||||
@@ -416,28 +645,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=
|
||||
@@ -464,18 +716,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=
|
||||
@@ -492,18 +766,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=
|
||||
@@ -517,11 +804,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=
|
||||
@@ -532,57 +826,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=
|
||||
@@ -591,7 +951,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=
|
||||
@@ -608,26 +973,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=
|
||||
@@ -638,8 +1027,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=
|
||||
@@ -660,6 +1055,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=
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/extension"
|
||||
"github.com/bishopfox/sliver/implant/sliver/mount"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
pb "github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -56,6 +57,7 @@ var (
|
||||
pb.MsgNetstatReq: netstatHandler,
|
||||
|
||||
pb.MsgSideloadReq: sideloadHandler,
|
||||
pb.MsgMountReq: mountHandler,
|
||||
|
||||
pb.MsgReconfigureReq: reconfigureHandler,
|
||||
pb.MsgSSHCommandReq: runSSHCommandHandler,
|
||||
@@ -154,6 +156,27 @@ func listExtensionsHandler(data []byte, resp RPCResponse) {
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func mountHandler(data []byte, resp RPCResponse) {
|
||||
mountReq := &pb.MountReq{}
|
||||
err := proto.Unmarshal(data, mountReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mountData, err := mount.GetMountInformation()
|
||||
mountResp := &pb.Mount{
|
||||
Info: mountData,
|
||||
Response: &commonpb.Response{},
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
mountResp.Response.Err = err.Error()
|
||||
}
|
||||
|
||||
data, err = proto.Marshal(mountResp)
|
||||
resp(data, err)
|
||||
}
|
||||
|
||||
func getUid(fileInfo os.FileInfo) string {
|
||||
uid := int32(fileInfo.Sys().(*syscall.Stat_t).Uid)
|
||||
uid_str := strconv.FormatUint(uint64(uid), 10)
|
||||
|
||||
@@ -36,16 +36,29 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
inactivityCheckInterval = 4 * time.Second
|
||||
inactivityTimeout = 15 * time.Second
|
||||
)
|
||||
|
||||
type socksTunnelPool struct {
|
||||
tunnels *sync.Map // map[uint64]chan []byte
|
||||
tunnels *sync.Map // map[uint64]chan []byte
|
||||
lastActivity *sync.Map // map[uint64]time.Time
|
||||
}
|
||||
|
||||
var socksTunnels = socksTunnelPool{
|
||||
tunnels: &sync.Map{},
|
||||
tunnels: &sync.Map{},
|
||||
lastActivity: &sync.Map{},
|
||||
}
|
||||
|
||||
var socksServer *socks5.Server
|
||||
|
||||
// Initialize socks server
|
||||
func init() {
|
||||
socksServer = socks5.NewServer()
|
||||
socksTunnels.startCleanupMonitor()
|
||||
}
|
||||
|
||||
func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connection) {
|
||||
socksData := &sliverpb.SocksData{}
|
||||
err := proto.Unmarshal(envelope.Data, socksData)
|
||||
@@ -55,9 +68,26 @@ func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connect
|
||||
// {{end}}
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond) // Necessary delay
|
||||
|
||||
// Check early to see if this is a close request from server
|
||||
if socksData.CloseConn {
|
||||
if tunnel, ok := socksTunnels.tunnels.LoadAndDelete(socksData.TunnelID); ok {
|
||||
if ch, ok := tunnel.(chan []byte); ok {
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
socksTunnels.lastActivity.Delete(socksData.TunnelID)
|
||||
return
|
||||
}
|
||||
|
||||
if socksData.Data == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Record activity as soon as we get data for this tunnel
|
||||
socksTunnels.recordActivity(socksData.TunnelID)
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[socks] User to Client to (server to implant) Data Sequence %d, Data Size %d\n", socksData.Sequence, len(socksData.Data))
|
||||
// {{end}}
|
||||
@@ -70,8 +100,6 @@ func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connect
|
||||
socksServer = socks5.NewServer(
|
||||
socks5.WithAuthMethods([]socks5.Authenticator{auth}),
|
||||
)
|
||||
} else {
|
||||
socksServer = socks5.NewServer()
|
||||
}
|
||||
|
||||
// {{if .Config.Debug}}
|
||||
@@ -80,7 +108,7 @@ func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connect
|
||||
|
||||
// init tunnel
|
||||
if tunnel, ok := socksTunnels.tunnels.Load(socksData.TunnelID); !ok {
|
||||
tunnelChan := make(chan []byte, 10)
|
||||
tunnelChan := make(chan []byte, 100) // Buffered channel for 100 messages
|
||||
socksTunnels.tunnels.Store(socksData.TunnelID, tunnelChan)
|
||||
tunnelChan <- socksData.Data
|
||||
err := socksServer.ServeConn(&socks{stream: socksData, conn: connection})
|
||||
@@ -88,9 +116,12 @@ func SocksReqHandler(envelope *sliverpb.Envelope, connection *transports.Connect
|
||||
// {{if .Config.Debug}}
|
||||
log.Printf("[socks] Failed to serve connection: %v", err)
|
||||
// {{end}}
|
||||
// Cleanup on serve failure
|
||||
socksTunnels.tunnels.Delete(socksData.TunnelID)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Will block when channel is full
|
||||
tunnel.(chan []byte) <- socksData.Data
|
||||
}
|
||||
}
|
||||
@@ -105,16 +136,22 @@ type socks struct {
|
||||
}
|
||||
|
||||
func (s *socks) Read(b []byte) (n int, err error) {
|
||||
time.Sleep(10 * time.Millisecond) // Necessary delay
|
||||
|
||||
channel, ok := socksTunnels.tunnels.Load(s.stream.TunnelID)
|
||||
if !ok {
|
||||
return 0, errors.New("[socks] invalid tunnel id")
|
||||
}
|
||||
|
||||
socksTunnels.recordActivity(s.stream.TunnelID)
|
||||
data := <-channel.(chan []byte)
|
||||
return copy(b, data), nil
|
||||
}
|
||||
|
||||
func (s *socks) Write(b []byte) (n int, err error) {
|
||||
time.Sleep(10 * time.Millisecond) // Necessary delay
|
||||
|
||||
socksTunnels.recordActivity(s.stream.TunnelID)
|
||||
data, err := proto.Marshal(&sliverpb.SocksData{
|
||||
TunnelID: s.stream.TunnelID,
|
||||
Data: b,
|
||||
@@ -136,12 +173,15 @@ func (s *socks) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (s *socks) Close() error {
|
||||
time.Sleep(10 * time.Millisecond) // Necessary delay
|
||||
|
||||
channel, ok := socksTunnels.tunnels.LoadAndDelete(s.stream.TunnelID)
|
||||
if !ok {
|
||||
return errors.New("[socks] can't close unknown channel")
|
||||
}
|
||||
close(channel.(chan []byte))
|
||||
|
||||
// Signal to server that we need to close this tunnel
|
||||
data, err := proto.Marshal(&sliverpb.SocksData{
|
||||
TunnelID: s.stream.TunnelID,
|
||||
CloseConn: true,
|
||||
@@ -181,3 +221,38 @@ func (c *socks) SetReadDeadline(t time.Time) error {
|
||||
func (c *socks) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *socksTunnelPool) recordActivity(tunnelID uint64) {
|
||||
s.lastActivity.Store(tunnelID, time.Now())
|
||||
}
|
||||
|
||||
// Periodically check for inactive tunnels and clean up
|
||||
func (s *socksTunnelPool) startCleanupMonitor() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(inactivityCheckInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
s.tunnels.Range(func(key, value interface{}) bool {
|
||||
tunnelID := key.(uint64)
|
||||
lastActivityI, exists := s.lastActivity.Load(tunnelID)
|
||||
if !exists {
|
||||
// If no activity record exists, create one
|
||||
s.recordActivity(tunnelID)
|
||||
return true
|
||||
}
|
||||
|
||||
lastActivity := lastActivityI.(time.Time)
|
||||
if time.Since(lastActivity) > inactivityTimeout {
|
||||
// Clean up the inactive tunnel
|
||||
if ch, ok := value.(chan []byte); ok {
|
||||
close(ch)
|
||||
}
|
||||
s.tunnels.Delete(tunnelID)
|
||||
s.lastActivity.Delete(tunnelID)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
//go:build darwin
|
||||
|
||||
package mount
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2023 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 (
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/sliverpb"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func getString(input []byte) string {
|
||||
ver := string(input)
|
||||
if i := strings.Index(ver, "\x00"); i != -1 {
|
||||
ver = ver[:i]
|
||||
}
|
||||
return ver
|
||||
}
|
||||
|
||||
func formatMountFlags(flags uint32) string {
|
||||
opts := make([]string, 0, 12)
|
||||
|
||||
if flags&unix.MNT_RDONLY != 0 {
|
||||
opts = append(opts, "ro")
|
||||
} else {
|
||||
opts = append(opts, "rw")
|
||||
}
|
||||
if flags&unix.MNT_NOSUID != 0 {
|
||||
opts = append(opts, "nosuid")
|
||||
}
|
||||
if flags&unix.MNT_NODEV != 0 {
|
||||
opts = append(opts, "nodev")
|
||||
}
|
||||
if flags&unix.MNT_NOEXEC != 0 {
|
||||
opts = append(opts, "noexec")
|
||||
}
|
||||
if flags&unix.MNT_SYNCHRONOUS != 0 {
|
||||
opts = append(opts, "sync")
|
||||
}
|
||||
if flags&unix.MNT_ASYNC != 0 {
|
||||
opts = append(opts, "async")
|
||||
}
|
||||
if flags&unix.MNT_NOATIME != 0 {
|
||||
opts = append(opts, "noatime")
|
||||
}
|
||||
if flags&unix.MNT_JOURNALED != 0 {
|
||||
opts = append(opts, "journaled")
|
||||
}
|
||||
if flags&unix.MNT_QUOTA != 0 {
|
||||
opts = append(opts, "quota")
|
||||
}
|
||||
if flags&unix.MNT_UNION != 0 {
|
||||
opts = append(opts, "union")
|
||||
}
|
||||
if flags&unix.MNT_AUTOMOUNTED != 0 {
|
||||
opts = append(opts, "automounted")
|
||||
}
|
||||
if flags&unix.MNT_REMOVABLE != 0 {
|
||||
opts = append(opts, "removable")
|
||||
}
|
||||
if flags&unix.MNT_DONTBROWSE != 0 {
|
||||
opts = append(opts, "nobrowse")
|
||||
}
|
||||
if flags&unix.MNT_IGNORE_OWNERSHIP != 0 {
|
||||
opts = append(opts, "ignore-ownership")
|
||||
}
|
||||
if flags&unix.MNT_LOCAL != 0 {
|
||||
opts = append(opts, "local")
|
||||
}
|
||||
if flags&unix.MNT_ROOTFS != 0 {
|
||||
opts = append(opts, "rootfs")
|
||||
}
|
||||
if flags&unix.MNT_SNAPSHOT != 0 {
|
||||
opts = append(opts, "snapshot")
|
||||
}
|
||||
if flags&unix.MNT_CPROTECT != 0 {
|
||||
opts = append(opts, "cprotect")
|
||||
}
|
||||
if flags&unix.MNT_NOUSERXATTR != 0 {
|
||||
opts = append(opts, "nouserxattr")
|
||||
}
|
||||
if flags&unix.MNT_QUARANTINE != 0 {
|
||||
opts = append(opts, "quarantine")
|
||||
}
|
||||
if flags&unix.MNT_UNKNOWNPERMISSIONS != 0 {
|
||||
opts = append(opts, "unknown-permissions")
|
||||
}
|
||||
if flags&unix.MNT_DEFWRITE != 0 {
|
||||
opts = append(opts, "defwrite")
|
||||
}
|
||||
if flags&unix.MNT_DOVOLFS != 0 {
|
||||
opts = append(opts, "volfs")
|
||||
}
|
||||
if flags&unix.MNT_NOBLOCK != 0 {
|
||||
opts = append(opts, "noblock")
|
||||
}
|
||||
if flags&unix.MNT_RELOAD != 0 {
|
||||
opts = append(opts, "reload")
|
||||
}
|
||||
if flags&unix.MNT_UPDATE != 0 {
|
||||
opts = append(opts, "update")
|
||||
}
|
||||
if flags&unix.MNT_EXPORTED != 0 {
|
||||
opts = append(opts, "exported")
|
||||
}
|
||||
if flags&unix.MNT_MULTILABEL != 0 {
|
||||
opts = append(opts, "multilabel")
|
||||
}
|
||||
if flags&unix.MNT_STRICTATIME != 0 {
|
||||
opts = append(opts, "strictatime")
|
||||
}
|
||||
if flags&unix.MNT_FORCE != 0 {
|
||||
opts = append(opts, "force")
|
||||
}
|
||||
|
||||
return strings.Join(opts, ",")
|
||||
}
|
||||
|
||||
func GetMountInformation() ([]*sliverpb.MountInfo, error) {
|
||||
mountInfo := make([]*sliverpb.MountInfo, 0)
|
||||
|
||||
count, err := unix.Getfsstat(nil, unix.MNT_NOWAIT)
|
||||
if err != nil || count == 0 {
|
||||
return mountInfo, err
|
||||
}
|
||||
|
||||
stats := make([]unix.Statfs_t, count)
|
||||
count, err = unix.Getfsstat(stats, unix.MNT_NOWAIT)
|
||||
if err != nil {
|
||||
return mountInfo, err
|
||||
}
|
||||
|
||||
stats = stats[:count]
|
||||
for _, stat := range stats {
|
||||
var mountData sliverpb.MountInfo
|
||||
mountData.VolumeName = getString(stat.Mntfromname[:])
|
||||
mountData.MountPoint = getString(stat.Mntonname[:])
|
||||
mountData.Label = "/"
|
||||
mountData.VolumeType = getString(stat.Fstypename[:])
|
||||
mountData.MountOptions = formatMountFlags(stat.Flags)
|
||||
mountData.TotalSpace = stat.Blocks * uint64(stat.Bsize)
|
||||
mountData.FreeSpace = stat.Bavail * uint64(stat.Bsize)
|
||||
mountData.UsedSpace = (stat.Blocks - stat.Bfree) * uint64(stat.Bsize)
|
||||
mountInfo = append(mountInfo, &mountData)
|
||||
}
|
||||
|
||||
return mountInfo, nil
|
||||
}
|
||||
@@ -1,7 +1,25 @@
|
||||
// +linux
|
||||
//go:build linux
|
||||
|
||||
package mount
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
Copyright (C) 2023 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 (
|
||||
"bufio"
|
||||
"os"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package mount
|
||||
|
||||
/*
|
||||
Sliver Implant Framework
|
||||
@@ -19,8 +20,6 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package mount
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// +darwin
|
||||
//go:build darwin
|
||||
|
||||
package procdump
|
||||
|
||||
|
||||
@@ -18,6 +18,129 @@ package version
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
func GetVersion() string {
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
//{{if .Config.Debug}}
|
||||
"log"
|
||||
//{{end}}
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
type plist struct {
|
||||
XMLName xml.Name `xml:"plist"`
|
||||
Dict dict `xml:"dict"`
|
||||
}
|
||||
|
||||
type dict struct {
|
||||
Key []string `xml:"key"`
|
||||
String []string `xml:"string"`
|
||||
}
|
||||
|
||||
func getString(input []byte) string {
|
||||
ver := string(input)
|
||||
if i := strings.Index(ver, "\x00"); i != -1 {
|
||||
ver = ver[:i]
|
||||
}
|
||||
return ver
|
||||
}
|
||||
|
||||
func readOSRelease() string {
|
||||
paths := []string{
|
||||
"/System/Library/CoreServices/SystemVersion.plist",
|
||||
"/System/Library/CoreServices/ServerVersion.plist",
|
||||
}
|
||||
for _, path := range paths {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
properties, err := parsePlistFile(file)
|
||||
_ = file.Close()
|
||||
if err != nil {
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("error parsing %s: %v", path, err)
|
||||
//{{end}}
|
||||
continue
|
||||
}
|
||||
if osRelease := buildOSRelease(properties); osRelease != "" {
|
||||
return osRelease
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parsePlistFile(file *os.File) (map[string]string, error) {
|
||||
var v plist
|
||||
if err := xml.NewDecoder(file).Decode(&v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(v.Dict.Key) != len(v.Dict.String) {
|
||||
return nil, errors.New("invalid plist content")
|
||||
}
|
||||
properties := make(map[string]string, len(v.Dict.Key))
|
||||
for i, key := range v.Dict.Key {
|
||||
properties[key] = v.Dict.String[i]
|
||||
}
|
||||
return properties, nil
|
||||
}
|
||||
|
||||
func buildOSRelease(properties map[string]string) string {
|
||||
productName := properties["ProductName"]
|
||||
productVersion := properties["ProductVersion"]
|
||||
productBuildVersion := properties["ProductBuildVersion"]
|
||||
if productName == "" || productVersion == "" || productBuildVersion == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%s %s (%s)", productName, productVersion, productBuildVersion)
|
||||
}
|
||||
|
||||
func normalizeArch(arch string) string {
|
||||
arch = strings.TrimSpace(arch)
|
||||
if arch == "" {
|
||||
return ""
|
||||
}
|
||||
switch strings.ToLower(arch) {
|
||||
case "amd64":
|
||||
return "x86_64"
|
||||
case "i386", "i486", "i586", "i686", "x86":
|
||||
return "x86"
|
||||
default:
|
||||
return arch
|
||||
}
|
||||
}
|
||||
|
||||
func formatDetailedVersion(osRelease, kernel, arch string) string {
|
||||
parts := make([]string, 0, 3)
|
||||
if osRelease != "" {
|
||||
parts = append(parts, osRelease)
|
||||
}
|
||||
if kernel != "" {
|
||||
parts = append(parts, fmt.Sprintf("kernel %s", kernel))
|
||||
}
|
||||
if arch != "" {
|
||||
parts = append(parts, arch)
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
osRelease := readOSRelease()
|
||||
var uname unix.Utsname
|
||||
if err := unix.Uname(&uname); err != nil {
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("error getting OS version: %v", err)
|
||||
//{{end}}
|
||||
return osRelease
|
||||
}
|
||||
if osRelease == "" {
|
||||
osRelease = getString(uname.Sysname[:])
|
||||
}
|
||||
kernel := getString(uname.Release[:])
|
||||
arch := normalizeArch(getString(uname.Machine[:]))
|
||||
return formatDetailedVersion(osRelease, kernel, arch)
|
||||
}
|
||||
@@ -22,7 +22,10 @@ package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
//{{if .Config.Debug}}
|
||||
"log"
|
||||
//{{end}}
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
@@ -39,11 +42,111 @@ func getString(input [65]int8) string {
|
||||
return ver
|
||||
}
|
||||
|
||||
func readOSRelease() string {
|
||||
paths := []string{"/etc/os-release", "/usr/lib/os-release"}
|
||||
for _, path := range paths {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return buildOSRelease(parseOSRelease(string(data)))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseOSRelease(content string) map[string]string {
|
||||
values := make(map[string]string)
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok || key == "" {
|
||||
continue
|
||||
}
|
||||
key = strings.TrimSpace(key)
|
||||
value = unescape(unquote(strings.TrimSpace(value)))
|
||||
values[key] = value
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func unquote(s string) string {
|
||||
if len(s) < 2 {
|
||||
return s
|
||||
}
|
||||
if (s[0] == '"' || s[0] == '\'') && s[0] == s[len(s)-1] {
|
||||
return s[1 : len(s)-1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func unescape(s string) string {
|
||||
return strings.NewReplacer(
|
||||
`\$`, `$`,
|
||||
`\"`, `"`,
|
||||
`\'`, `'`,
|
||||
`\\`, `\`,
|
||||
"\\`", "`",
|
||||
).Replace(s)
|
||||
}
|
||||
|
||||
func buildOSRelease(values map[string]string) string {
|
||||
name := values["NAME"]
|
||||
version := values["VERSION"]
|
||||
if version == "" {
|
||||
version = values["VERSION_ID"]
|
||||
}
|
||||
if name != "" && version != "" {
|
||||
return fmt.Sprintf("%s %s", name, version)
|
||||
}
|
||||
return values["PRETTY_NAME"]
|
||||
}
|
||||
|
||||
func normalizeArch(arch string) string {
|
||||
arch = strings.TrimSpace(arch)
|
||||
if arch == "" {
|
||||
return ""
|
||||
}
|
||||
switch strings.ToLower(arch) {
|
||||
case "amd64":
|
||||
return "x86_64"
|
||||
case "i386", "i486", "i586", "i686", "x86":
|
||||
return "x86"
|
||||
default:
|
||||
return arch
|
||||
}
|
||||
}
|
||||
|
||||
func formatDetailedVersion(osRelease, kernel, arch string) string {
|
||||
parts := make([]string, 0, 3)
|
||||
if osRelease != "" {
|
||||
parts = append(parts, osRelease)
|
||||
}
|
||||
if kernel != "" {
|
||||
parts = append(parts, fmt.Sprintf("kernel %s", kernel))
|
||||
}
|
||||
if arch != "" {
|
||||
parts = append(parts, arch)
|
||||
}
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
// GetVersion returns the os version information
|
||||
func GetVersion() string {
|
||||
osRelease := readOSRelease()
|
||||
var uname syscall.Utsname
|
||||
if err := syscall.Uname(&uname); err != nil {
|
||||
log.Fatal(err)
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("error getting OS version: %v", err)
|
||||
//{{end}}
|
||||
return osRelease
|
||||
}
|
||||
return fmt.Sprintf("%s %s %s", getString(uname.Sysname), getString(uname.Nodename), getString(uname.Release))
|
||||
if osRelease == "" {
|
||||
osRelease = getString(uname.Sysname)
|
||||
}
|
||||
kernel := getString(uname.Release)
|
||||
arch := normalizeArch(getString(uname.Machine))
|
||||
return formatDetailedVersion(osRelease, kernel, arch)
|
||||
}
|
||||
+601
-413
File diff suppressed because it is too large.
Load diff
@@ -1320,3 +1320,17 @@ 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 v3.15.8
|
||||
// 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 v3.15.8
|
||||
// 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 v3.15.8
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
+43
-15
@@ -30,6 +30,7 @@ import (
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -40,7 +41,8 @@ const (
|
||||
// MySQL - MySQL protocol
|
||||
MySQL = "mysql"
|
||||
|
||||
databaseConfigFileName = "database.json"
|
||||
databaseConfigFileName = "database.yaml"
|
||||
databaseLegacyConfigFileName = "database.json"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -57,7 +59,7 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// GetDatabaseConfigPath - File path to config.json
|
||||
// GetDatabaseConfigPath - File path to config.yaml
|
||||
func GetDatabaseConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
databaseConfigPath := filepath.Join(appDir, "configs", databaseConfigFileName)
|
||||
@@ -65,22 +67,27 @@ func GetDatabaseConfigPath() string {
|
||||
return databaseConfigPath
|
||||
}
|
||||
|
||||
func getDatabaseLegacyConfigPath() string {
|
||||
appDir := assets.GetRootAppDir()
|
||||
return filepath.Join(appDir, "configs", databaseLegacyConfigFileName)
|
||||
}
|
||||
|
||||
// DatabaseConfig - Server config
|
||||
type DatabaseConfig struct {
|
||||
Dialect string `json:"dialect"`
|
||||
Database string `json:"database"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
Dialect string `json:"dialect" yaml:"dialect"`
|
||||
Database string `json:"database" yaml:"database"`
|
||||
Username string `json:"username" yaml:"username"`
|
||||
Password string `json:"password" yaml:"password"`
|
||||
Host string `json:"host" yaml:"host"`
|
||||
Port uint16 `json:"port" yaml:"port"`
|
||||
|
||||
Params map[string]string `json:"params"`
|
||||
Pragmas map[string]string `json:"pragmas"`
|
||||
Params map[string]string `json:"params" yaml:"params"`
|
||||
Pragmas map[string]string `json:"pragmas" yaml:"pragmas"`
|
||||
|
||||
MaxIdleConns int `json:"max_idle_conns"`
|
||||
MaxOpenConns int `json:"max_open_conns"`
|
||||
MaxIdleConns int `json:"max_idle_conns" yaml:"max_idle_conns"`
|
||||
MaxOpenConns int `json:"max_open_conns" yaml:"max_open_conns"`
|
||||
|
||||
LogLevel string `json:"log_level"`
|
||||
LogLevel string `json:"log_level" yaml:"log_level"`
|
||||
}
|
||||
|
||||
// DSN - Get the db connections string
|
||||
@@ -170,7 +177,7 @@ func (c *DatabaseConfig) Save() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
data, err := json.MarshalIndent(c, "", " ")
|
||||
data, err := yaml.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,18 +192,33 @@ func (c *DatabaseConfig) Save() error {
|
||||
// GetDatabaseConfig - Get config value
|
||||
func GetDatabaseConfig() *DatabaseConfig {
|
||||
configPath := GetDatabaseConfigPath()
|
||||
legacyPath := getDatabaseLegacyConfigPath()
|
||||
config := getDefaultDatabaseConfig()
|
||||
migratedLegacy := false
|
||||
if _, err := os.Stat(configPath); !os.IsNotExist(err) {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to read config file %s", err)
|
||||
return config
|
||||
}
|
||||
err = json.Unmarshal(data, config)
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to parse config file %s", err)
|
||||
return config
|
||||
}
|
||||
} else if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
data, err := os.ReadFile(legacyPath)
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to read legacy config file %s", err)
|
||||
return config
|
||||
}
|
||||
err = json.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to parse legacy config file %s", err)
|
||||
return config
|
||||
}
|
||||
migratedLegacy = true
|
||||
databaseConfigLog.Infof("Migrating legacy config %s to %s", legacyPath, configPath)
|
||||
} else {
|
||||
databaseConfigLog.Warnf("Config file does not exist, using defaults")
|
||||
}
|
||||
@@ -213,6 +235,12 @@ func GetDatabaseConfig() *DatabaseConfig {
|
||||
err := config.Save() // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to save default config %s", err)
|
||||
return config
|
||||
}
|
||||
if migratedLegacy {
|
||||
if err := renameLegacyConfig(legacyPath); err != nil {
|
||||
databaseConfigLog.Errorf("Failed to rename legacy config %s", err)
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDatabaseConfigParsesYAML(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configPath := GetDatabaseConfigPath()
|
||||
if err := os.MkdirAll(filepath.Dir(configPath), 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
|
||||
data := []byte(`dialect: mysql
|
||||
database: "sliver"
|
||||
username: "tester"
|
||||
password: "secret"
|
||||
host: "127.0.0.1"
|
||||
port: 3306
|
||||
params:
|
||||
charset: "utf8mb4"
|
||||
pragmas: {}
|
||||
max_idle_conns: 7
|
||||
max_open_conns: 15
|
||||
log_level: "info"
|
||||
`)
|
||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write yaml config: %v", err)
|
||||
}
|
||||
|
||||
config := GetDatabaseConfig()
|
||||
if config.Dialect != MySQL {
|
||||
t.Fatalf("expected dialect %q, got %q", MySQL, config.Dialect)
|
||||
}
|
||||
if config.Database != "sliver" {
|
||||
t.Fatalf("expected database %q, got %q", "sliver", config.Database)
|
||||
}
|
||||
if config.Username != "tester" {
|
||||
t.Fatalf("expected username %q, got %q", "tester", config.Username)
|
||||
}
|
||||
if config.Password != "secret" {
|
||||
t.Fatalf("expected password %q, got %q", "secret", config.Password)
|
||||
}
|
||||
if config.Host != "127.0.0.1" {
|
||||
t.Fatalf("expected host %q, got %q", "127.0.0.1", config.Host)
|
||||
}
|
||||
if config.Port != 3306 {
|
||||
t.Fatalf("expected port %d, got %d", 3306, config.Port)
|
||||
}
|
||||
if config.Params["charset"] != "utf8mb4" {
|
||||
t.Fatalf("expected charset param %q, got %q", "utf8mb4", config.Params["charset"])
|
||||
}
|
||||
if config.MaxIdleConns != 7 {
|
||||
t.Fatalf("expected max_idle_conns %d, got %d", 7, config.MaxIdleConns)
|
||||
}
|
||||
if config.MaxOpenConns != 15 {
|
||||
t.Fatalf("expected max_open_conns %d, got %d", 15, config.MaxOpenConns)
|
||||
}
|
||||
if config.LogLevel != "info" {
|
||||
t.Fatalf("expected log_level %q, got %q", "info", config.LogLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatabaseConfigWritesDefault(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
config := GetDatabaseConfig()
|
||||
if config.Dialect != Sqlite {
|
||||
t.Fatalf("expected default dialect %q, got %q", Sqlite, config.Dialect)
|
||||
}
|
||||
if _, err := os.Stat(GetDatabaseConfigPath()); err != nil {
|
||||
t.Fatalf("expected default config file to exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatabaseConfigMigratesLegacyJSON(t *testing.T) {
|
||||
t.Setenv("SLIVER_ROOT_DIR", t.TempDir())
|
||||
|
||||
configDir := filepath.Dir(GetDatabaseConfigPath())
|
||||
if err := os.MkdirAll(configDir, 0700); err != nil {
|
||||
t.Fatalf("failed to create config dir: %v", err)
|
||||
}
|
||||
legacyPath := filepath.Join(configDir, databaseLegacyConfigFileName)
|
||||
|
||||
legacy := &DatabaseConfig{
|
||||
Dialect: Postgres,
|
||||
Database: "sliver",
|
||||
Username: "legacy",
|
||||
Password: "pass",
|
||||
Host: "db.internal",
|
||||
Port: 5432,
|
||||
Params: map[string]string{"sslmode": "disable"},
|
||||
Pragmas: map[string]string{},
|
||||
MaxIdleConns: 4,
|
||||
MaxOpenConns: 9,
|
||||
LogLevel: "debug",
|
||||
}
|
||||
data, err := json.MarshalIndent(legacy, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal legacy config: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(legacyPath, data, 0600); err != nil {
|
||||
t.Fatalf("failed to write legacy config: %v", err)
|
||||
}
|
||||
|
||||
config := GetDatabaseConfig()
|
||||
if config.Dialect != Postgres {
|
||||
t.Fatalf("expected dialect %q, got %q", Postgres, config.Dialect)
|
||||
}
|
||||
if config.Database != "sliver" {
|
||||
t.Fatalf("expected database %q, got %q", "sliver", config.Database)
|
||||
}
|
||||
if config.Params["sslmode"] != "disable" {
|
||||
t.Fatalf("expected sslmode param %q, got %q", "disable", config.Params["sslmode"])
|
||||
}
|
||||
if _, err := os.Stat(GetDatabaseConfigPath()); err != nil {
|
||||
t.Fatalf("expected migrated yaml config file to exist: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected legacy config to be renamed: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(legacyBackupPath(legacyPath)); err != nil {
|
||||
t.Fatalf("expected legacy backup file to exist: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func legacyBackupPath(legacyPath string) string {
|
||||
dir := filepath.Dir(legacyPath)
|
||||
base := filepath.Base(legacyPath)
|
||||
name := strings.TrimSuffix(base, filepath.Ext(base))
|
||||
return filepath.Join(dir, "."+name+".json-old")
|
||||
}
|
||||
|
||||
func renameLegacyConfig(legacyPath string) error {
|
||||
if _, err := os.Stat(legacyPath); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
backupPath := legacyBackupPath(legacyPath)
|
||||
if _, err := os.Stat(backupPath); err == nil {
|
||||
if err := os.Remove(backupPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return os.Rename(legacyPath, backupPath)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1494,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
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
+322
-50
@@ -20,8 +20,11 @@ package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/bishopfox/sliver/protobuf/rpcpb"
|
||||
@@ -32,15 +35,16 @@ import (
|
||||
|
||||
var (
|
||||
// SessionID->Tunnels[TunnelID]->Tunnel->Cache map[uint64]*sliverpb.SocksData{}
|
||||
toImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}}
|
||||
toImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}, lastActivity: map[uint64]time.Time{}}
|
||||
|
||||
// SessionID->Tunnels[TunnelID]->Tunnel->Cache
|
||||
fromImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}}
|
||||
fromImplantCacheSocks = socksDataCache{mutex: &sync.RWMutex{}, cache: map[uint64]map[uint64]*sliverpb.SocksData{}, lastActivity: map[uint64]time.Time{}}
|
||||
)
|
||||
|
||||
type socksDataCache struct {
|
||||
mutex *sync.RWMutex
|
||||
cache map[uint64]map[uint64]*sliverpb.SocksData
|
||||
mutex *sync.RWMutex
|
||||
cache map[uint64]map[uint64]*sliverpb.SocksData
|
||||
lastActivity map[uint64]time.Time
|
||||
}
|
||||
|
||||
func (c *socksDataCache) Add(tunnelID uint64, sequence uint64, tunnelData *sliverpb.SocksData) {
|
||||
@@ -72,6 +76,7 @@ func (c *socksDataCache) DeleteTun(tunnelID uint64) {
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
delete(c.cache, tunnelID)
|
||||
delete(c.lastActivity, tunnelID)
|
||||
}
|
||||
|
||||
func (c *socksDataCache) DeleteSeq(tunnelID uint64, sequence uint64) {
|
||||
@@ -85,69 +90,299 @@ func (c *socksDataCache) DeleteSeq(tunnelID uint64, sequence uint64) {
|
||||
delete(c.cache[tunnelID], sequence)
|
||||
}
|
||||
|
||||
func (c *socksDataCache) recordActivity(tunnelID uint64) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.lastActivity[tunnelID] = time.Now()
|
||||
}
|
||||
|
||||
// Socks - Open an in-band port forward
|
||||
|
||||
const (
|
||||
writeTimeout = 5 * time.Second
|
||||
batchSize = 100 // Maximum number of sequences to batch
|
||||
inactivityCheckInterval = 4 * time.Second
|
||||
inactivityTimeout = 15 * time.Second
|
||||
ToImplantTickerInterval = 10 * time.Millisecond // data going towards implant is usually smaller request data
|
||||
ToClientTickerInterval = 5 * time.Millisecond // data coming back from implant is usually larger response data
|
||||
)
|
||||
|
||||
func (s *Server) SocksProxy(stream rpcpb.SliverRPC_SocksProxyServer) error {
|
||||
errChan := make(chan error, 2)
|
||||
defer close(errChan)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
connDone := make(chan struct{})
|
||||
defer close(connDone)
|
||||
|
||||
// Track all goroutines spawned for this session
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
// Track all tunnels created for this session
|
||||
activeTunnels := make(map[uint64]bool)
|
||||
var tunnelMutex sync.Mutex
|
||||
|
||||
// Cleanup all tunnels on SocksProxy closure
|
||||
defer func() {
|
||||
tunnelMutex.Lock()
|
||||
for tunnelID := range activeTunnels {
|
||||
if tunnel := core.SocksTunnels.Get(tunnelID); tunnel != nil {
|
||||
rpcLog.Infof("Cleaning up tunnel %d on proxy closure", tunnelID)
|
||||
close(tunnel.FromImplant)
|
||||
tunnel.Client = nil
|
||||
s.CloseSocks(context.Background(), &sliverpb.Socks{TunnelID: tunnelID})
|
||||
}
|
||||
}
|
||||
tunnelMutex.Unlock()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case err := <-errChan:
|
||||
rpcLog.Errorf("SocksProxy error: %v", err)
|
||||
return err
|
||||
default:
|
||||
}
|
||||
|
||||
fromClient, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
return nil
|
||||
}
|
||||
//fmt.Println("Send Agent 1 ",fromClient.TunnelID,len(fromClient.Data))
|
||||
if err != nil {
|
||||
rpcLog.Warnf("Error on stream recv %s", err)
|
||||
return rpcError(err)
|
||||
}
|
||||
tunnelLog.Debugf("Tunnel %d: From client %d byte(s)",
|
||||
fromClient.TunnelID, len(fromClient.Data))
|
||||
socks := core.SocksTunnels.Get(fromClient.TunnelID)
|
||||
if socks == nil {
|
||||
return nil
|
||||
|
||||
tunnelMutex.Lock()
|
||||
activeTunnels[fromClient.TunnelID] = true // Mark this as an active tunnel
|
||||
tunnelMutex.Unlock()
|
||||
|
||||
tunnel := core.SocksTunnels.Get(fromClient.TunnelID)
|
||||
if tunnel == nil {
|
||||
continue
|
||||
}
|
||||
if socks.Client == nil {
|
||||
socks.Client = stream // Bind client to tunnel
|
||||
// Send Client
|
||||
go func() {
|
||||
for tunnelData := range socks.FromImplant {
|
||||
|
||||
fromImplantCacheSocks.Add(fromClient.TunnelID, tunnelData.Sequence, tunnelData)
|
||||
if tunnel.Client == nil {
|
||||
tunnel.Client = stream
|
||||
tunnel.FromImplant = make(chan *sliverpb.SocksData, 100) // Buffered channel for 100 messages
|
||||
|
||||
for recv, ok := fromImplantCacheSocks.Get(fromClient.TunnelID, socks.FromImplantSequence); ok; recv, ok = fromImplantCacheSocks.Get(fromClient.TunnelID, socks.FromImplantSequence) {
|
||||
rpcLog.Debugf("[socks] agent to (Server To Client) Data Sequence %d , Data Size %d ,Data %v\n", socks.FromImplantSequence, len(recv.Data), recv.Data)
|
||||
socks.Client.Send(&sliverpb.SocksData{
|
||||
CloseConn: recv.CloseConn,
|
||||
TunnelID: recv.TunnelID,
|
||||
Data: recv.Data,
|
||||
})
|
||||
|
||||
fromImplantCacheSocks.DeleteSeq(fromClient.TunnelID, socks.FromImplantSequence)
|
||||
socks.FromImplantSequence++
|
||||
// Monitor tunnel goroutines for inactivity and cleanup
|
||||
wg.Add(1)
|
||||
go func(tunnelID uint64) {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
rpcLog.Errorf("Recovered from panic in monitor: %v", r)
|
||||
errChan <- fmt.Errorf("monitor goroutine panic: %v", r)
|
||||
cancel() // Cancel context in case of a panic
|
||||
}
|
||||
}()
|
||||
|
||||
ticker := time.NewTicker(inactivityCheckInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-connDone:
|
||||
return
|
||||
case <-ticker.C:
|
||||
tunnel := core.SocksTunnels.Get(tunnelID)
|
||||
if tunnel == nil || tunnel.Client == nil {
|
||||
return
|
||||
}
|
||||
session := core.Sessions.Get(tunnel.SessionID)
|
||||
|
||||
// Check both caches for activity
|
||||
toImplantCacheSocks.mutex.RLock()
|
||||
fromImplantCacheSocks.mutex.RLock()
|
||||
toLastActivity := toImplantCacheSocks.lastActivity[tunnelID]
|
||||
fromLastActivity := fromImplantCacheSocks.lastActivity[tunnelID]
|
||||
toImplantCacheSocks.mutex.RUnlock()
|
||||
fromImplantCacheSocks.mutex.RUnlock()
|
||||
|
||||
// Clean up goroutine if both directions have hit the idle threshold
|
||||
if time.Since(toLastActivity) > inactivityTimeout && time.Since(fromLastActivity) > inactivityTimeout {
|
||||
s.CloseSocks(context.Background(), &sliverpb.Socks{TunnelID: tunnelID})
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up goroutine if the client has disconnected early
|
||||
if tunnel.Client == nil || session == nil {
|
||||
s.CloseSocks(context.Background(), &sliverpb.Socks{TunnelID: tunnelID})
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}(fromClient.TunnelID)
|
||||
|
||||
// Send Client
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
rpcLog.Errorf("Recovered from panic in client sender: %v", r)
|
||||
errChan <- fmt.Errorf("client sender panic: %v", r)
|
||||
cancel() // Cancel context in case of a panic
|
||||
}
|
||||
}()
|
||||
|
||||
pendingData := make(map[uint64]*sliverpb.SocksData)
|
||||
ticker := time.NewTicker(ToClientTickerInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-connDone:
|
||||
return
|
||||
case tunnelData, ok := <-tunnel.FromImplant:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if implant is requesting to close the tunnel
|
||||
if tunnelData.CloseConn {
|
||||
// Clean up the tunnel
|
||||
s.CloseSocks(context.Background(), &sliverpb.Socks{TunnelID: fromClient.TunnelID})
|
||||
return
|
||||
}
|
||||
|
||||
sequence := tunnelData.Sequence
|
||||
fromImplantCacheSocks.Add(fromClient.TunnelID, sequence, tunnelData)
|
||||
pendingData[sequence] = tunnelData
|
||||
fromImplantCacheSocks.recordActivity(fromClient.TunnelID)
|
||||
|
||||
case <-ticker.C:
|
||||
if tunnel.Client == nil {
|
||||
return
|
||||
}
|
||||
if len(pendingData) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
expectedSequence := atomic.LoadUint64(&tunnel.FromImplantSequence)
|
||||
processed := 0
|
||||
|
||||
// Perform Batching
|
||||
for i := 0; i < batchSize && processed < len(pendingData); i++ {
|
||||
data, exists := pendingData[expectedSequence]
|
||||
if !exists {
|
||||
break // Stop batching if we don't have the next expected sequence
|
||||
}
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errChan <- fmt.Errorf("Client sender panic: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
err := stream.Send(&sliverpb.SocksData{
|
||||
CloseConn: data.CloseConn,
|
||||
TunnelID: data.TunnelID,
|
||||
Data: data.Data,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
rpcLog.Errorf("Send error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
delete(pendingData, expectedSequence)
|
||||
fromImplantCacheSocks.DeleteSeq(fromClient.TunnelID, expectedSequence)
|
||||
atomic.AddUint64(&tunnel.FromImplantSequence, 1)
|
||||
expectedSequence++
|
||||
processed++
|
||||
}()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Send Agent
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
rpcLog.Errorf("Recovered from panic in agent sender: %v", r)
|
||||
errChan <- fmt.Errorf("agent sender panic: %v", r)
|
||||
cancel() // Cancel context in case of a panic
|
||||
}
|
||||
}()
|
||||
|
||||
pendingData := make(map[uint64]*sliverpb.SocksData)
|
||||
ticker := time.NewTicker(ToImplantTickerInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-connDone:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if tunnel.Client == nil {
|
||||
return
|
||||
}
|
||||
sequence := atomic.LoadUint64(&tunnel.ToImplantSequence)
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
rpcLog.Errorf("Recovered from processing panic: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
recv, ok := toImplantCacheSocks.Get(fromClient.TunnelID, sequence)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
session := core.Sessions.Get(fromClient.Request.SessionID)
|
||||
if session == nil {
|
||||
rpcLog.Error("Session not found")
|
||||
break
|
||||
}
|
||||
|
||||
data, err := proto.Marshal(recv)
|
||||
if err != nil {
|
||||
rpcLog.Errorf("Failed to marshal data: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case session.Connection.Send <- &sliverpb.Envelope{
|
||||
Type: sliverpb.MsgSocksData,
|
||||
Data: data,
|
||||
}:
|
||||
toImplantCacheSocks.DeleteSeq(fromClient.TunnelID, sequence)
|
||||
atomic.AddUint64(&tunnel.ToImplantSequence, 1)
|
||||
sequence++
|
||||
case <-time.After(writeTimeout):
|
||||
rpcLog.Error("Write timeout to implant")
|
||||
pendingData[sequence] = recv
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Send Agent
|
||||
go func() {
|
||||
toImplantCacheSocks.Add(fromClient.TunnelID, fromClient.Sequence, fromClient)
|
||||
|
||||
for recv, ok := toImplantCacheSocks.Get(fromClient.TunnelID, socks.ToImplantSequence); ok; recv, ok = toImplantCacheSocks.Get(fromClient.TunnelID, socks.ToImplantSequence) {
|
||||
rpcLog.Debugf("[socks] Client to (Server To Agent) Data Sequence %d , Data Size %d \n", socks.ToImplantSequence, len(fromClient.Data))
|
||||
data, _ := proto.Marshal(recv)
|
||||
|
||||
session := core.Sessions.Get(fromClient.Request.SessionID)
|
||||
session.Connection.Send <- &sliverpb.Envelope{
|
||||
Type: sliverpb.MsgSocksData,
|
||||
Data: data,
|
||||
}
|
||||
|
||||
toImplantCacheSocks.DeleteSeq(fromClient.TunnelID, socks.ToImplantSequence)
|
||||
socks.ToImplantSequence++
|
||||
}
|
||||
|
||||
}()
|
||||
toImplantCacheSocks.Add(fromClient.TunnelID, fromClient.Sequence, fromClient)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSocks5 - Create requests we close a Socks
|
||||
@@ -169,11 +404,48 @@ func (s *Server) CreateSocks(ctx context.Context, req *sliverpb.Socks) (*sliverp
|
||||
|
||||
// CloseSocks - Client requests we close a Socks
|
||||
func (s *Server) CloseSocks(ctx context.Context, req *sliverpb.Socks) (*commonpb.Empty, error) {
|
||||
err := core.SocksTunnels.Close(req.TunnelID)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
rpcLog.Errorf("Recovered from panic in CloseSocks for tunnel %d: %v", req.TunnelID, r)
|
||||
}
|
||||
}()
|
||||
|
||||
tunnel := core.SocksTunnels.Get(req.TunnelID)
|
||||
if tunnel != nil {
|
||||
// Signal close to implant first
|
||||
if session := core.Sessions.Get(tunnel.SessionID); session != nil {
|
||||
data, _ := proto.Marshal(&sliverpb.SocksData{
|
||||
TunnelID: req.TunnelID,
|
||||
CloseConn: true,
|
||||
})
|
||||
session.Connection.Send <- &sliverpb.Envelope{
|
||||
Type: sliverpb.MsgSocksData,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond) // Delay to allow close message to be sent
|
||||
tunnel.Client = nil // Cleanup the tunnel
|
||||
if tunnel.FromImplant != nil {
|
||||
select {
|
||||
case _, ok := <-tunnel.FromImplant:
|
||||
if ok {
|
||||
close(tunnel.FromImplant)
|
||||
}
|
||||
default:
|
||||
close(tunnel.FromImplant)
|
||||
}
|
||||
tunnel.FromImplant = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up caches
|
||||
toImplantCacheSocks.DeleteTun(req.TunnelID)
|
||||
fromImplantCacheSocks.DeleteTun(req.TunnelID)
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
|
||||
// Remove from core tunnels last
|
||||
if err := core.SocksTunnels.Close(req.TunnelID); err != nil {
|
||||
rpcLog.Errorf("Error closing tunnel %d: %v", req.TunnelID, err)
|
||||
}
|
||||
|
||||
return &commonpb.Empty{}, nil
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
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)
|
||||
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
module(
|
||||
name = "cel-spec",
|
||||
)
|
||||
|
||||
bazel_dep(
|
||||
name = "bazel_skylib",
|
||||
version = "1.7.1",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "gazelle",
|
||||
version = "0.39.1",
|
||||
repo_name = "bazel_gazelle",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "protobuf",
|
||||
version = "27.1",
|
||||
repo_name = "com_google_protobuf",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "rules_cc",
|
||||
version = "0.0.17",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "rules_go",
|
||||
version = "0.53.0",
|
||||
repo_name = "io_bazel_rules_go",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "rules_java",
|
||||
version = "7.6.5",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "rules_proto",
|
||||
version = "7.0.2",
|
||||
)
|
||||
bazel_dep(
|
||||
name = "rules_python",
|
||||
version = "0.35.0",
|
||||
)
|
||||
|
||||
### PYTHON ###
|
||||
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
|
||||
python.toolchain(
|
||||
ignore_root_user_error = True,
|
||||
python_version = "3.11",
|
||||
)
|
||||
|
||||
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
|
||||
go_sdk.download(version = "1.23.0")
|
||||
|
||||
go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
|
||||
go_deps.from_file(go_mod = "//:go.mod")
|
||||
use_repo(
|
||||
go_deps,
|
||||
"org_golang_google_protobuf",
|
||||
)
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
# Common Expression Language
|
||||
|
||||
The Common Expression Language (CEL) implements common semantics for expression
|
||||
evaluation, enabling different applications to more easily interoperate.
|
||||
|
||||
Key Applications
|
||||
|
||||
* Security policy: organizations have complex infrastructure and need common
|
||||
tooling to reason about the system as a whole
|
||||
* Protocols: expressions are a useful data type and require interoperability
|
||||
across programming languages and platforms.
|
||||
|
||||
|
||||
Guiding philosophy:
|
||||
|
||||
1. Keep it small & fast.
|
||||
* CEL evaluates in linear time, is mutation free, and not Turing-complete.
|
||||
This limitation is a feature of the language design, which allows the
|
||||
implementation to evaluate orders of magnitude faster than equivalently
|
||||
sandboxed JavaScript.
|
||||
2. Make it extensible.
|
||||
* CEL is designed to be embedded in applications, and allows for
|
||||
extensibility via its context which allows for functions and data to be
|
||||
provided by the software that embeds it.
|
||||
3. Developer-friendly.
|
||||
* The language is approachable to developers. The initial spec was based
|
||||
on the experience of developing Firebase Rules and usability testing
|
||||
many prior iterations.
|
||||
* The library itself and accompanying toolings should be easy to adopt by
|
||||
teams that seek to integrate CEL into their platforms.
|
||||
|
||||
The required components of a system that supports CEL are:
|
||||
|
||||
* The textual representation of an expression as written by a developer. It is
|
||||
of similar syntax to expressions in C/C++/Java/JavaScript
|
||||
* A representation of the program's abstract syntax tree (AST).
|
||||
* A compiler library that converts the textual representation to the binary
|
||||
representation. This can be done ahead of time (in the control plane) or
|
||||
just before evaluation (in the data plane).
|
||||
* A context containing one or more typed variables, often protobuf messages.
|
||||
Most use-cases will use `attribute_context.proto`
|
||||
* An evaluator library that takes the binary format in the context and
|
||||
produces a result, usually a Boolean.
|
||||
|
||||
For use cases which require persistence or cross-process communcation, it is
|
||||
highly recommended to serialize the type-checked expression as a protocol
|
||||
buffer. The CEL team will maintains canonical protocol buffers for ASTs and
|
||||
will keep these versions identical and wire-compatible in perpetuity:
|
||||
|
||||
* [CEL canonical](https://github.com/google/cel-spec/tree/master/proto/cel/expr)
|
||||
* [CEL v1alpha1](https://github.com/googleapis/googleapis/tree/master/google/api/expr/v1alpha1)
|
||||
|
||||
|
||||
Example of boolean conditions and object construction:
|
||||
|
||||
``` c
|
||||
// Condition
|
||||
account.balance >= transaction.withdrawal
|
||||
|| (account.overdraftProtection
|
||||
&& account.overdraftLimit >= transaction.withdrawal - account.balance)
|
||||
|
||||
// Object construction
|
||||
common.GeoPoint{ latitude: 10.0, longitude: -5.5 }
|
||||
```
|
||||
|
||||
For more detail, see:
|
||||
|
||||
* [Introduction](doc/intro.md)
|
||||
* [Language Definition](doc/langdef.md)
|
||||
|
||||
Released under the [Apache License](LICENSE).
|
||||
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
|
||||
http_archive(
|
||||
name = "io_bazel_rules_go",
|
||||
sha256 = "099a9fb96a376ccbbb7d291ed4ecbdfd42f6bc822ab77ae6f1b5cb9e914e94fa",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.35.0/rules_go-v0.35.0.zip",
|
||||
"https://github.com/bazelbuild/rules_go/releases/download/v0.35.0/rules_go-v0.35.0.zip",
|
||||
],
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "bazel_gazelle",
|
||||
sha256 = "ecba0f04f96b4960a5b250c8e8eeec42281035970aa8852dda73098274d14a1d",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.29.0/bazel-gazelle-v0.29.0.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "rules_proto",
|
||||
sha256 = "e017528fd1c91c5a33f15493e3a398181a9e821a804eb7ff5acdd1d2d6c2b18d",
|
||||
strip_prefix = "rules_proto-4.0.0-3.20.0",
|
||||
urls = [
|
||||
"https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0-3.20.0.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
# googleapis as of 09/16/2024
|
||||
http_archive(
|
||||
name = "com_google_googleapis",
|
||||
strip_prefix = "googleapis-4082d5e51e8481f6ccc384cacd896f4e78f19dee",
|
||||
sha256 = "57319889d47578b3c89bf1b3f34888d796a8913d63b32d750a4cd12ed303c4e8",
|
||||
urls = [
|
||||
"https://github.com/googleapis/googleapis/archive/4082d5e51e8481f6ccc384cacd896f4e78f19dee.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
# protobuf
|
||||
http_archive(
|
||||
name = "com_google_protobuf",
|
||||
sha256 = "8242327e5df8c80ba49e4165250b8f79a76bd11765facefaaecfca7747dc8da2",
|
||||
strip_prefix = "protobuf-3.21.5",
|
||||
urls = ["https://github.com/protocolbuffers/protobuf/archive/v3.21.5.zip"],
|
||||
)
|
||||
|
||||
# googletest
|
||||
http_archive(
|
||||
name = "com_google_googletest",
|
||||
urls = ["https://github.com/google/googletest/archive/master.zip"],
|
||||
strip_prefix = "googletest-master",
|
||||
)
|
||||
|
||||
# gflags
|
||||
http_archive(
|
||||
name = "com_github_gflags_gflags",
|
||||
sha256 = "6e16c8bc91b1310a44f3965e616383dbda48f83e8c1eaa2370a215057b00cabe",
|
||||
strip_prefix = "gflags-77592648e3f3be87d6c7123eb81cbad75f9aef5a",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/gflags/gflags/archive/77592648e3f3be87d6c7123eb81cbad75f9aef5a.tar.gz",
|
||||
"https://github.com/gflags/gflags/archive/77592648e3f3be87d6c7123eb81cbad75f9aef5a.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
# glog
|
||||
http_archive(
|
||||
name = "com_google_glog",
|
||||
sha256 = "1ee310e5d0a19b9d584a855000434bb724aa744745d5b8ab1855c85bff8a8e21",
|
||||
strip_prefix = "glog-028d37889a1e80e8a07da1b8945ac706259e5fd8",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/google/glog/archive/028d37889a1e80e8a07da1b8945ac706259e5fd8.tar.gz",
|
||||
"https://github.com/google/glog/archive/028d37889a1e80e8a07da1b8945ac706259e5fd8.tar.gz",
|
||||
],
|
||||
)
|
||||
|
||||
# absl
|
||||
http_archive(
|
||||
name = "com_google_absl",
|
||||
strip_prefix = "abseil-cpp-master",
|
||||
urls = ["https://github.com/abseil/abseil-cpp/archive/master.zip"],
|
||||
)
|
||||
|
||||
load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains")
|
||||
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
|
||||
load("@com_google_googleapis//:repository_rules.bzl", "switched_rules_by_language")
|
||||
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
|
||||
load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
|
||||
|
||||
switched_rules_by_language(
|
||||
name = "com_google_googleapis_imports",
|
||||
cc = True,
|
||||
)
|
||||
|
||||
# Do *not* call *_dependencies(), etc, yet. See comment at the end.
|
||||
|
||||
# Generated Google APIs protos for Golang
|
||||
# Generated Google APIs protos for Golang 08/26/2024
|
||||
go_repository(
|
||||
name = "org_golang_google_genproto_googleapis_api",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "google.golang.org/genproto/googleapis/api",
|
||||
sum = "h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw=",
|
||||
version = "v0.0.0-20240826202546-f6391c0de4c7",
|
||||
)
|
||||
|
||||
# Generated Google APIs protos for Golang 08/26/2024
|
||||
go_repository(
|
||||
name = "org_golang_google_genproto_googleapis_rpc",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "google.golang.org/genproto/googleapis/rpc",
|
||||
sum = "h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs=",
|
||||
version = "v0.0.0-20240826202546-f6391c0de4c7",
|
||||
)
|
||||
|
||||
# gRPC deps
|
||||
go_repository(
|
||||
name = "org_golang_google_grpc",
|
||||
build_file_proto_mode = "disable_global",
|
||||
importpath = "google.golang.org/grpc",
|
||||
tag = "v1.49.0",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "org_golang_x_net",
|
||||
importpath = "golang.org/x/net",
|
||||
sum = "h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=",
|
||||
version = "v0.0.0-20190311183353-d8887717615a",
|
||||
)
|
||||
|
||||
go_repository(
|
||||
name = "org_golang_x_text",
|
||||
importpath = "golang.org/x/text",
|
||||
sum = "h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=",
|
||||
version = "v0.3.2",
|
||||
)
|
||||
|
||||
# Run the dependencies at the end. These will silently try to import some
|
||||
# of the above repositories but at different versions, so ours must come first.
|
||||
go_rules_dependencies()
|
||||
go_register_toolchains(version = "1.19.1")
|
||||
gazelle_dependencies()
|
||||
rules_proto_dependencies()
|
||||
rules_proto_toolchains()
|
||||
protobuf_deps()
|
||||
Whitespace-only changes.
+1231
File diff suppressed because it is too large.
Load diff
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
steps:
|
||||
- name: 'gcr.io/cloud-builders/bazel:7.3.2'
|
||||
entrypoint: bazel
|
||||
args: ['build', '...']
|
||||
id: bazel-build
|
||||
waitFor: ['-']
|
||||
timeout: 15m
|
||||
options:
|
||||
machineType: 'N1_HIGHCPU_32'
|
||||
+468
@@ -0,0 +1,468 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.10
|
||||
// protoc v5.27.1
|
||||
// source: cel/expr/eval.proto
|
||||
|
||||
package expr
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
anypb "google.golang.org/protobuf/types/known/anypb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type EvalState struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Values []*ExprValue `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
|
||||
Results []*EvalState_Result `protobuf:"bytes,3,rep,name=results,proto3" json:"results,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EvalState) Reset() {
|
||||
*x = EvalState{}
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EvalState) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*EvalState) ProtoMessage() {}
|
||||
|
||||
func (x *EvalState) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use EvalState.ProtoReflect.Descriptor instead.
|
||||
func (*EvalState) Descriptor() ([]byte, []int) {
|
||||
return file_cel_expr_eval_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *EvalState) GetValues() []*ExprValue {
|
||||
if x != nil {
|
||||
return x.Values
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *EvalState) GetResults() []*EvalState_Result {
|
||||
if x != nil {
|
||||
return x.Results
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ExprValue struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Kind:
|
||||
//
|
||||
// *ExprValue_Value
|
||||
// *ExprValue_Error
|
||||
// *ExprValue_Unknown
|
||||
Kind isExprValue_Kind `protobuf_oneof:"kind"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ExprValue) Reset() {
|
||||
*x = ExprValue{}
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ExprValue) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ExprValue) ProtoMessage() {}
|
||||
|
||||
func (x *ExprValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ExprValue.ProtoReflect.Descriptor instead.
|
||||
func (*ExprValue) Descriptor() ([]byte, []int) {
|
||||
return file_cel_expr_eval_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ExprValue) GetKind() isExprValue_Kind {
|
||||
if x != nil {
|
||||
return x.Kind
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExprValue) GetValue() *Value {
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*ExprValue_Value); ok {
|
||||
return x.Value
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExprValue) GetError() *ErrorSet {
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*ExprValue_Error); ok {
|
||||
return x.Error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ExprValue) GetUnknown() *UnknownSet {
|
||||
if x != nil {
|
||||
if x, ok := x.Kind.(*ExprValue_Unknown); ok {
|
||||
return x.Unknown
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isExprValue_Kind interface {
|
||||
isExprValue_Kind()
|
||||
}
|
||||
|
||||
type ExprValue_Value struct {
|
||||
Value *Value `protobuf:"bytes,1,opt,name=value,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExprValue_Error struct {
|
||||
Error *ErrorSet `protobuf:"bytes,2,opt,name=error,proto3,oneof"`
|
||||
}
|
||||
|
||||
type ExprValue_Unknown struct {
|
||||
Unknown *UnknownSet `protobuf:"bytes,3,opt,name=unknown,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*ExprValue_Value) isExprValue_Kind() {}
|
||||
|
||||
func (*ExprValue_Error) isExprValue_Kind() {}
|
||||
|
||||
func (*ExprValue_Unknown) isExprValue_Kind() {}
|
||||
|
||||
type ErrorSet struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Errors []*Status `protobuf:"bytes,1,rep,name=errors,proto3" json:"errors,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *ErrorSet) Reset() {
|
||||
*x = ErrorSet{}
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *ErrorSet) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ErrorSet) ProtoMessage() {}
|
||||
|
||||
func (x *ErrorSet) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ErrorSet.ProtoReflect.Descriptor instead.
|
||||
func (*ErrorSet) Descriptor() ([]byte, []int) {
|
||||
return file_cel_expr_eval_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *ErrorSet) GetErrors() []*Status {
|
||||
if x != nil {
|
||||
return x.Errors
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
Details []*anypb.Any `protobuf:"bytes,3,rep,name=details,proto3" json:"details,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Status) Reset() {
|
||||
*x = Status{}
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Status) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Status) ProtoMessage() {}
|
||||
|
||||
func (x *Status) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Status.ProtoReflect.Descriptor instead.
|
||||
func (*Status) Descriptor() ([]byte, []int) {
|
||||
return file_cel_expr_eval_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *Status) GetCode() int32 {
|
||||
if x != nil {
|
||||
return x.Code
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *Status) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Status) GetDetails() []*anypb.Any {
|
||||
if x != nil {
|
||||
return x.Details
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UnknownSet struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Exprs []int64 `protobuf:"varint,1,rep,packed,name=exprs,proto3" json:"exprs,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UnknownSet) Reset() {
|
||||
*x = UnknownSet{}
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *UnknownSet) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*UnknownSet) ProtoMessage() {}
|
||||
|
||||
func (x *UnknownSet) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use UnknownSet.ProtoReflect.Descriptor instead.
|
||||
func (*UnknownSet) Descriptor() ([]byte, []int) {
|
||||
return file_cel_expr_eval_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *UnknownSet) GetExprs() []int64 {
|
||||
if x != nil {
|
||||
return x.Exprs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type EvalState_Result struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Expr int64 `protobuf:"varint,1,opt,name=expr,proto3" json:"expr,omitempty"`
|
||||
Value int64 `protobuf:"varint,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *EvalState_Result) Reset() {
|
||||
*x = EvalState_Result{}
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *EvalState_Result) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*EvalState_Result) ProtoMessage() {}
|
||||
|
||||
func (x *EvalState_Result) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_cel_expr_eval_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use EvalState_Result.ProtoReflect.Descriptor instead.
|
||||
func (*EvalState_Result) Descriptor() ([]byte, []int) {
|
||||
return file_cel_expr_eval_proto_rawDescGZIP(), []int{0, 0}
|
||||
}
|
||||
|
||||
func (x *EvalState_Result) GetExpr() int64 {
|
||||
if x != nil {
|
||||
return x.Expr
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *EvalState_Result) GetValue() int64 {
|
||||
if x != nil {
|
||||
return x.Value
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_cel_expr_eval_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_cel_expr_eval_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x13cel/expr/eval.proto\x12\bcel.expr\x1a\x19google/protobuf/any.proto\x1a\x14cel/expr/value.proto\"\xa2\x01\n" +
|
||||
"\tEvalState\x12+\n" +
|
||||
"\x06values\x18\x01 \x03(\v2\x13.cel.expr.ExprValueR\x06values\x124\n" +
|
||||
"\aresults\x18\x03 \x03(\v2\x1a.cel.expr.EvalState.ResultR\aresults\x1a2\n" +
|
||||
"\x06Result\x12\x12\n" +
|
||||
"\x04expr\x18\x01 \x01(\x03R\x04expr\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\x03R\x05value\"\x9a\x01\n" +
|
||||
"\tExprValue\x12'\n" +
|
||||
"\x05value\x18\x01 \x01(\v2\x0f.cel.expr.ValueH\x00R\x05value\x12*\n" +
|
||||
"\x05error\x18\x02 \x01(\v2\x12.cel.expr.ErrorSetH\x00R\x05error\x120\n" +
|
||||
"\aunknown\x18\x03 \x01(\v2\x14.cel.expr.UnknownSetH\x00R\aunknownB\x06\n" +
|
||||
"\x04kind\"4\n" +
|
||||
"\bErrorSet\x12(\n" +
|
||||
"\x06errors\x18\x01 \x03(\v2\x10.cel.expr.StatusR\x06errors\"f\n" +
|
||||
"\x06Status\x12\x12\n" +
|
||||
"\x04code\x18\x01 \x01(\x05R\x04code\x12\x18\n" +
|
||||
"\amessage\x18\x02 \x01(\tR\amessage\x12.\n" +
|
||||
"\adetails\x18\x03 \x03(\v2\x14.google.protobuf.AnyR\adetails\"\"\n" +
|
||||
"\n" +
|
||||
"UnknownSet\x12\x14\n" +
|
||||
"\x05exprs\x18\x01 \x03(\x03R\x05exprsB,\n" +
|
||||
"\fdev.cel.exprB\tEvalProtoP\x01Z\fcel.dev/expr\xf8\x01\x01b\x06proto3"
|
||||
|
||||
var (
|
||||
file_cel_expr_eval_proto_rawDescOnce sync.Once
|
||||
file_cel_expr_eval_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_cel_expr_eval_proto_rawDescGZIP() []byte {
|
||||
file_cel_expr_eval_proto_rawDescOnce.Do(func() {
|
||||
file_cel_expr_eval_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_cel_expr_eval_proto_rawDesc), len(file_cel_expr_eval_proto_rawDesc)))
|
||||
})
|
||||
return file_cel_expr_eval_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_cel_expr_eval_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
|
||||
var file_cel_expr_eval_proto_goTypes = []any{
|
||||
(*EvalState)(nil), // 0: cel.expr.EvalState
|
||||
(*ExprValue)(nil), // 1: cel.expr.ExprValue
|
||||
(*ErrorSet)(nil), // 2: cel.expr.ErrorSet
|
||||
(*Status)(nil), // 3: cel.expr.Status
|
||||
(*UnknownSet)(nil), // 4: cel.expr.UnknownSet
|
||||
(*EvalState_Result)(nil), // 5: cel.expr.EvalState.Result
|
||||
(*Value)(nil), // 6: cel.expr.Value
|
||||
(*anypb.Any)(nil), // 7: google.protobuf.Any
|
||||
}
|
||||
var file_cel_expr_eval_proto_depIdxs = []int32{
|
||||
1, // 0: cel.expr.EvalState.values:type_name -> cel.expr.ExprValue
|
||||
5, // 1: cel.expr.EvalState.results:type_name -> cel.expr.EvalState.Result
|
||||
6, // 2: cel.expr.ExprValue.value:type_name -> cel.expr.Value
|
||||
2, // 3: cel.expr.ExprValue.error:type_name -> cel.expr.ErrorSet
|
||||
4, // 4: cel.expr.ExprValue.unknown:type_name -> cel.expr.UnknownSet
|
||||
3, // 5: cel.expr.ErrorSet.errors:type_name -> cel.expr.Status
|
||||
7, // 6: cel.expr.Status.details:type_name -> google.protobuf.Any
|
||||
7, // [7:7] is the sub-list for method output_type
|
||||
7, // [7:7] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_cel_expr_eval_proto_init() }
|
||||
func file_cel_expr_eval_proto_init() {
|
||||
if File_cel_expr_eval_proto != nil {
|
||||
return
|
||||
}
|
||||
file_cel_expr_value_proto_init()
|
||||
file_cel_expr_eval_proto_msgTypes[1].OneofWrappers = []any{
|
||||
(*ExprValue_Value)(nil),
|
||||
(*ExprValue_Error)(nil),
|
||||
(*ExprValue_Unknown)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_cel_expr_eval_proto_rawDesc), len(file_cel_expr_eval_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 6,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_cel_expr_eval_proto_goTypes,
|
||||
DependencyIndexes: file_cel_expr_eval_proto_depIdxs,
|
||||
MessageInfos: file_cel_expr_eval_proto_msgTypes,
|
||||
}.Build()
|
||||
File_cel_expr_eval_proto = out.File
|
||||
file_cel_expr_eval_proto_goTypes = nil
|
||||
file_cel_expr_eval_proto_depIdxs = nil
|
||||
}
|
||||
Loaded 100 of 4359 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user