diff --git a/Makefile b/Makefile index f69ea75..08ff589 100644 --- a/Makefile +++ b/Makefile @@ -18,15 +18,16 @@ SSH_MS_SECRET_PATH?=secret/ssh_ms SSH_MS_SYNC_HOST?=localhost SSH_MS_SYNC_PATH?=/usr/share/nginx/html/downloads/ssh_ms/ SSH_MS_USERNAME?=SSH_MS_USERNAME +SSH_MS_VPN_CHECK_NAMES?= DEBUG_BUILD=$(shell test "${DEBUG}" = "1" && echo 1 || echo 0) COMPRESS_BINARY=$(shell test "${XZ_COMPRESS}" = "1" && echo 1 || echo 0) PACKAGE=github.com/cezmunsta/ssh_ms ifeq ($(DEBUG_BUILD), 1) -LDFLAGS=-ldflags "-X ${PACKAGE}/config.EnvBasePath=${SSH_MS_BASEPATH} -X ${PACKAGE}/cmd.Version=${RELEASE_VER} -X ${PACKAGE}/config.EnvSSHUsername=${SSH_MS_USERNAME} -X ${PACKAGE}/config.EnvSSHIdentityFile=${SSH_MS_ID_FILE} -X ${PACKAGE}/config.EnvSSHDefaultUsername=${SSH_MS_DEFAULT_USERNAME} -X ${PACKAGE}/config.EnvVaultAddr=${SSH_MS_DEFAULT_VAULT_ADDR} -X ${PACKAGE}/config.SecretPath=${SSH_MS_SECRET_PATH} -X ${PACKAGE}/vault.RenewThreshold=${SSH_MS_RENEW_THRESHOLD} -X ${PACKAGE}/config.portServiceMappings=${SSH_MS_SECRET_MAP}" +LDFLAGS=-ldflags "-X ${PACKAGE}/config.EnvBasePath=${SSH_MS_BASEPATH} -X ${PACKAGE}/cmd.Version=${RELEASE_VER} -X ${PACKAGE}/config.EnvSSHUsername=${SSH_MS_USERNAME} -X ${PACKAGE}/config.EnvSSHIdentityFile=${SSH_MS_ID_FILE} -X ${PACKAGE}/config.EnvSSHDefaultUsername=${SSH_MS_DEFAULT_USERNAME} -X ${PACKAGE}/config.EnvVaultAddr=${SSH_MS_DEFAULT_VAULT_ADDR} -X ${PACKAGE}/config.SecretPath=${SSH_MS_SECRET_PATH} -X ${PACKAGE}/vault.RenewThreshold=${SSH_MS_RENEW_THRESHOLD} -X ${PACKAGE}/config.portServiceMappings=${SSH_MS_SECRET_MAP} -X ${PACKAGE}/config.undesiredInterfaces=${SSH_MS_VPN_CHECK_NAMES}" else -LDFLAGS=-ldflags "-w -X ${PACKAGE}/config.EnvBasePath=${SSH_MS_BASEPATH} -X ${PACKAGE}/cmd.Version=${RELEASE_VER} -X ${PACKAGE}/config.EnvSSHUsername=${SSH_MS_USERNAME} -X ${PACKAGE}/config.EnvSSHIdentityFile=${SSH_MS_ID_FILE} -X ${PACKAGE}/config.EnvSSHDefaultUsername=${SSH_MS_DEFAULT_USERNAME} -X ${PACKAGE}/config.EnvVaultAddr=${SSH_MS_DEFAULT_VAULT_ADDR} -X ${PACKAGE}/config.SecretPath=${SSH_MS_SECRET_PATH} -X ${PACKAGE}/vault.RenewThreshold=${SSH_MS_RENEW_THRESHOLD} -X ${PACKAGE}/config.portServiceMappings=${SSH_MS_SECRET_MAP}" +LDFLAGS=-ldflags "-w -X ${PACKAGE}/config.EnvBasePath=${SSH_MS_BASEPATH} -X ${PACKAGE}/cmd.Version=${RELEASE_VER} -X ${PACKAGE}/config.EnvSSHUsername=${SSH_MS_USERNAME} -X ${PACKAGE}/config.EnvSSHIdentityFile=${SSH_MS_ID_FILE} -X ${PACKAGE}/config.EnvSSHDefaultUsername=${SSH_MS_DEFAULT_USERNAME} -X ${PACKAGE}/config.EnvVaultAddr=${SSH_MS_DEFAULT_VAULT_ADDR} -X ${PACKAGE}/config.SecretPath=${SSH_MS_SECRET_PATH} -X ${PACKAGE}/vault.RenewThreshold=${SSH_MS_RENEW_THRESHOLD} -X ${PACKAGE}/config.portServiceMappings=${SSH_MS_SECRET_MAP} -X ${PACKAGE}/config.undesiredInterfaces=${SSH_MS_VPN_CHECK_NAMES}" endif VETFLAGS?=( -unusedresult -bools -copylocks -framepointer -httpresponse -json -stdmethods -printf -stringintconv -unmarshal -unsafeptr ) diff --git a/config/main.go b/config/main.go index 87b9d10..095d99c 100644 --- a/config/main.go +++ b/config/main.go @@ -18,21 +18,14 @@ type Settings struct { Debug, RenewWarningOptOut, Simulate, StoredToken, Verbose, Version, VersionCheck bool ConfigComment, ConfigMotd, EnvSSHDefaultUsername, EnvSSHIdentityFile, CustomLocalForward, EnvSSHUsername, EnvVaultAddr, NameSpace, SecretPath, Show, StoragePath, User, VaultAddr, VaultToken, VaultAPIVersion, VaultSDKVersion string - ServiceMap map[string]string + ServiceMap map[string]string + UndesiredInterfaces []string } var ( once sync.Once settings Settings - /* - The following support overrides during builds, which can be done - by setting ldflags, e.g. - - `-ldflags "-X github.com/cezmunsta/ssh_ms/config.EnvSSHUserName=xxx"` - - */ - // EnvBasePath is the parent location used to prefix storage paths, // default value is filepath.Join(os.Getenv("HOME"), ".ssh", "cache") EnvBasePath string @@ -60,7 +53,10 @@ var ( SecretPath = "secret/ssh_ms" portServiceMappings string - serviceMap = make(map[string]string) + undesiredInterfaces string + + serviceMap = make(map[string]string) + undesiredInterfaceNames = []string{} ) func init() { @@ -83,6 +79,15 @@ func init() { serviceMap[p[0]] = p[1] } } + + userByPass := os.Getenv("SSH_MS_BYPASS_INTERFACE_CHECK") + if userByPass == "1" || userByPass == "yes" || userByPass == "true" { + undesiredInterfaces = "" + } + + if len(undesiredInterfaces) > 0 { + undesiredInterfaceNames = strings.Split(undesiredInterfaces, ",") + } } // ToJSON returns the config in JSON format @@ -99,6 +104,7 @@ func (s *Settings) ToJSON() string { func GetConfig() *Settings { once.Do(func() { renewWarningOptOut := false + if EnvBasePath == "" { EnvBasePath = filepath.Join(os.Getenv("HOME"), ".ssh", "cache") } @@ -138,6 +144,7 @@ func GetConfig() *Settings { Simulate: false, StoragePath: EnvBasePath, StoredToken: false, + UndesiredInterfaces: undesiredInterfaceNames, VaultAPIVersion: vaultAPIVersion, VaultSDKVersion: vaultSDKVersion, } diff --git a/config/versions.go b/config/versions.go index f7fb3e3..1bbee1d 100644 --- a/config/versions.go +++ b/config/versions.go @@ -3,5 +3,5 @@ package config const ( vaultAPIVersion = "v1.22.0" - vaultSDKVersion = "v0.20.0" + vaultSDKVersion = "v0.23.0" ) diff --git a/ssh/main.go b/ssh/main.go index 1410ba1..c9caab1 100644 --- a/ssh/main.go +++ b/ssh/main.go @@ -1,17 +1,20 @@ package ssh import ( + "bufio" "bytes" "crypto/sha1" "encoding/json" "errors" "fmt" + "io" "io/ioutil" "maps" "net" "os" "os/exec" "reflect" + "regexp" "slices" "strconv" "strings" @@ -111,6 +114,67 @@ func acquirePort(min uint16, max uint16) (uint16, error) { return 0, errNoFreePort } +// check network interfaces +func checkNetworkInterfaces(denyList []string) ([]string, error) { + var blockedInterfaces []string + var regexPatterns []*regexp.Regexp + + interfaces, err := net.Interfaces() + if err != nil { + return nil, fmt.Errorf("failed to get network interfaces: %w", err) + } + + for _, pattern := range denyList { + re, err := regexp.Compile(pattern) + if err != nil { + return nil, fmt.Errorf("invalid regex pattern '%s': %w", pattern, err) + } + regexPatterns = append(regexPatterns, re) + } + + for _, iface := range interfaces { + allowed := false + for _, re := range regexPatterns { + if !re.MatchString(iface.Name) { + allowed = true + break + } + } + if !allowed { + blockedInterfaces = append(blockedInterfaces, iface.Name) + } + } + + return blockedInterfaces, nil +} + +// action for when undesired network interfaces are required +func handleUndesiredInterfacePresent() { + fmt.Println("Please confirm that you wish to proceed? y/n") + + reader := bufio.NewReader(os.Stdin) + answer, err := reader.ReadString('\n') + if err != nil { + if err == io.EOF { + fmt.Println("\nInput cancelled") + os.Exit(1) + } + fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err) + os.Exit(1) + } + + switch strings.ToLower(strings.TrimSpace(answer)) { + case "y", "yes", "1": + fmt.Println("Proceeding...") + case "n", "no", "0": + fmt.Println("Cancelled") + os.Exit(0) + default: + fmt.Println("Invalid input. Please enter y/n") + os.Exit(1) + } +} + // exists checks to see if a value is already present func (c Connection) exists(lf LocalForward) bool { for _, item := range c.LocalForward { @@ -541,6 +605,16 @@ func Connect(args []string, e UserEnv) { if e.Simulate { log.Println("cmd: ssh", strings.Join(args, " ")) } else { + if len(cfg.UndesiredInterfaces) > 0 { + log.Debug("Interface check enabled, checking before connecting") + + if detectedInterfaces, err := checkNetworkInterfaces(cfg.UndesiredInterfaces); err != nil || len(detectedInterfaces) > 0 { + log.Warningf("detected undesired interfaces: %v", detectedInterfaces) + fmt.Println("Your data may be transferred through undesired interfaces:", detectedInterfaces) + handleUndesiredInterfacePresent() + } + } + cmd := exec.Command("ssh", args...) cmd.Stderr = os.Stderr cmd.Stdin = os.Stdin