diff --git a/pkg/edgeview/src/basics.go b/pkg/edgeview/src/basics.go index 36cc1aba4b..2c7c874517 100644 --- a/pkg/edgeview/src/basics.go +++ b/pkg/edgeview/src/basics.go @@ -14,6 +14,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strconv" "strings" "time" @@ -143,53 +144,6 @@ func initOpts() { } } -// this script is automatically installed into your /tmp/download/bin -// and can be used to run your kubectl with the download kubeconfig file -// decrypted by the ssh private key -const kubeConfdecrpytScript = `#!/bin/bash - -usage() { - echo "Usage: $0 [-keypath=\"your ssh private key file path\"]" -} - -# Parse command line arguments -while [[ $# -gt 0 ]]; do - key="$1" - - case $key in - -keypath=*) - keypath="${key#*=}" - shift # Shift to the next argument after the keypath value - ;; - *) - # Unknown option or argument - usage - exit 1 - ;; - esac -done - -# Set the private key path based on the provided -keypath or use the default -if [ -n "$keypath" ]; then - privateKeyFile="$keypath" -else - # use default ssh private key - privateKeyFile="$HOME/.ssh/id_rsa" -fi - -# the symmetric key is encrypted by ssh public key -symmetricKeyEncFile="/tmp/download/kube-symmetric-file.enc" -# the kubeconfig file is encrypted by the symmetric key -symmetricEncFile="/tmp/download/kube-config-yaml" - -# Decrypt the symmetric key using the SSH private key -symmetricKey=$(openssl pkeyutl -decrypt -inkey "$privateKeyFile" -in "$symmetricKeyEncFile") - -# decrypt the kube config with openssl -kconfig=$(openssl enc -aes-256-cbc -d -in "$symmetricEncFile" -k "$symmetricKey" 2>/dev/null) - -echo "$kconfig"` - // checkOpts - // a pre-defined sets of 'network', 'system', 'pub' commands are supported, the command options can be // multiple and separated by ',', this function to verify each of the command is valid and supported @@ -632,22 +586,20 @@ func listRecursiveFiles(path, pattern string) ([]string, error) { return jfiles, nil } -// this script is used to run your kubectl with the download kubeconfig file -// decrypted by the ssh private key -func checkAndInstallKubeDecryptScript() error { - scriptfile := filepath.Join(fileCopyDir, "bin", "edgeview-kube-decrypt.sh") - err := os.MkdirAll(filepath.Dir(scriptfile), os.ModePerm) +// get the cluster api-server IP and port from kubeconfig file +func getKubeServerIPandPort(kubeConfigFile string) (string, error) { + content, err := os.ReadFile(kubeConfigFile) if err != nil { - fmt.Println("Error creating directory:", err) - return err + return "", err } - if err := os.WriteFile(scriptfile, []byte(kubeConfdecrpytScript), 0755); err != nil { - fmt.Println("Error writing script to file:", err) - return err + regex := regexp.MustCompile(`\s+server: https://([^ ]+)`) + matches := regex.FindStringSubmatch(string(content)) + if len(matches) != 2 { + return "", fmt.Errorf("failed to find server in kubeconfig") } - return nil + return strings.TrimSpace(matches[1]), nil } var helpStr = `eve-edgeview [ -token ] [ -inst ] diff --git a/pkg/edgeview/src/copyfile.go b/pkg/edgeview/src/copyfile.go index 09551d0ff2..db690a1606 100644 --- a/pkg/edgeview/src/copyfile.go +++ b/pkg/edgeview/src/copyfile.go @@ -7,7 +7,6 @@ import ( "bytes" "encoding/json" "fmt" - "io" "os" "os/exec" "path/filepath" @@ -383,8 +382,6 @@ func recvCopyFile(msg []byte, fstatus *fileCopyStatus, mtype int) { } else { fmt.Printf("\nfile size %d, saved at %s\n", fstatus.currSize, fileCopyDir+fileNameClean) } - } else if fstatus.cType == copyKubeConfig { - splitKubeConfigFiles(fstatus.filename) } transferStr := fmt.Sprintf("\n file %s size %d", fileNameClean, fstatus.currSize) if serverSentSize != 0 && fstatus.currSize != int64(serverSentSize) { @@ -446,77 +443,6 @@ func sendCopyDone(context string, err error) { } } -// split files into key and encrypted files -// since the edgeview copy operation only downloaded a combined file -// you need your ssh private key to descript the symmetric key file, and use that -// to decrypt the kubeconfig file -func splitKubeConfigFiles(combFile string) { - fileStrs := strings.Split(combFile, ".") - if len(fileStrs) != 2 { - fmt.Printf("get file name incorrect %s\n", combFile) - return - } - numBytes := fileStrs[1] - - bytesPlusOne, err := strconv.Atoi(numBytes) - if err != nil { - fmt.Printf("get file name incorrect num %s\n", numBytes) - return - } - - // Open the combined file - combFilePath := filepath.Join(fileCopyDir, combFile) - cleanCombFilePath := filepath.Clean(combFilePath) - // To fix CodeQL warning, Check if the cleaned path is still within the intended directory - if !strings.HasPrefix(cleanCombFilePath, fileCopyDir) { - fmt.Println("potential path traversal attempt detected") - return - } - - combFileHandle, err := os.Open(cleanCombFilePath) - if err != nil { - fmt.Printf("error opening combined file: %v\n", err) - return - } - defer combFileHandle.Close() - - // Create the symKeyClientFile - symFileHandle, err := os.Create(symKeyClientFile) - if err != nil { - fmt.Printf("error creating sym file: %v\n", err) - return - } - defer symFileHandle.Close() - - // Copy the first 'bytesPlusOne' bytes from the combined file to the symKeyClientFile - _, err = io.CopyN(symFileHandle, combFileHandle, int64(bytesPlusOne)) - if err != nil { - fmt.Printf("error copying to sym file: %v\n", err) - return - } - - // Create the kubeClientFile - kubeFileHandle, err := os.Create(kubeClientFile) - if err != nil { - fmt.Printf("error creating kube file: %v\n", err) - return - } - defer kubeFileHandle.Close() - - // Copy the rest of the combined file to the kubeClientFile - _, err = io.Copy(kubeFileHandle, combFileHandle) - if err != nil { - fmt.Printf("error copying to kube file: %v\n", err) - return - } - - // Remove the combined file - err = os.Remove(cleanCombFilePath) - if err != nil { - fmt.Printf("error removing combined file: %v\n", err) - } -} - // untarLogfile - unzip and make into a single .txt // with sequential log entries for dev and each of the apps // this is done only if the tar file size is not too large diff --git a/pkg/edgeview/src/edge-view.go b/pkg/edgeview/src/edge-view.go index d526f515ba..12d4007430 100644 --- a/pkg/edgeview/src/edge-view.go +++ b/pkg/edgeview/src/edge-view.go @@ -222,10 +222,6 @@ func main() { } if kubecfg { fstatus.cType = copyKubeConfig - err := checkAndInstallKubeDecryptScript() - if err != nil { - return - } } pnetopt = pqueryopt } else if strings.HasPrefix(pqueryopt, "cp/") { diff --git a/pkg/edgeview/src/policy.go b/pkg/edgeview/src/policy.go index 8d82621d00..3001f294e1 100644 --- a/pkg/edgeview/src/policy.go +++ b/pkg/edgeview/src/policy.go @@ -255,17 +255,41 @@ func checkAddrKube(addr string) bool { return false } - _, subnet1, err := net.ParseCIDR("10.42.0.0/16") + // part of the cluster --cluster-cidr prefix block for this node + cni0Prefix, err := getCNIPrefix() if err != nil { return false } - _, subnet2, err := net.ParseCIDR("10.43.0.0/16") + // this is the --service-cidr for kubernetes service prefix block, + // and default is 10.43/16 + _, kubeServicePrefix, err := net.ParseCIDR("10.43.0.0/16") if err != nil { return false } + return cni0Prefix.Contains(ipa) || kubeServicePrefix.Contains(ipa) +} + +// get the CNI0 interface IP prefix +func getCNIPrefix() (*net.IPNet, error) { + iface, err := net.InterfaceByName("cni0") + if err != nil { + return nil, err + } + + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + + for _, addr := range addrs { + switch v := addr.(type) { + case *net.IPNet: + return v, nil + } + } - return subnet1.Contains(ipa) || subnet2.Contains(ipa) + return nil, fmt.Errorf("no valid IPNet address found for cni0") } func checkAddrLocal(addr string) bool { diff --git a/pkg/edgeview/src/tcp.go b/pkg/edgeview/src/tcp.go index 3fa0c987ed..1f4bd92d6d 100644 --- a/pkg/edgeview/src/tcp.go +++ b/pkg/edgeview/src/tcp.go @@ -5,16 +5,12 @@ package main import ( "bytes" - "crypto/rand" - "crypto/rsa" - "encoding/hex" "encoding/json" "fmt" "io" "net" "net/http" "os" - "os/exec" "regexp" "strconv" "strings" @@ -23,7 +19,6 @@ import ( "github.com/gorilla/websocket" "github.com/lf-edge/eve/pkg/pillar/types" - "golang.org/x/crypto/ssh" ) type wsMessage struct { @@ -82,13 +77,10 @@ const ( tcpMaxMappingNUM int = 5 tcpIdleTimeoutSec float64 = 1800.0 tcpCheckTimeout time.Duration = 300 * time.Second - tcpKubeEndpoint string = "localhost:6443" kubeYamlFile string = "/run/.kube/k3s/user.yaml" - kubeConfigFile string = "kube-config-yaml" - kubeSymKeyFile string = "kube-symmetric-file.enc" + kubeConfigFile string = "kube-config.yaml" kubeServerFile string = "/tmp/" + kubeConfigFile kubeClientFile string = fileCopyDir + kubeConfigFile - symKeyClientFile string = fileCopyDir + kubeSymKeyFile ) const ( @@ -298,13 +290,14 @@ func setAndStartProxyTCP(opt string) { var ipAddrPort []string var proxySvr *http.Server var kubeport int + var err error proxyServerDone := make(chan struct{}) mappingCnt := 1 ipAddrPort = make([]string, mappingCnt) var hasProxy, hasKube bool - var proxyDNSIP string + var proxyDNSIP, kubeAddrPort string kubenum := edgeviewInstID - 1 if strings.Contains(opt, "/") { var gotProxy, gotKube bool @@ -312,9 +305,12 @@ func setAndStartProxyTCP(opt string) { mappingCnt = len(params) ipAddrPort = make([]string, mappingCnt) for i, ipport := range params { - gotProxy, gotKube, proxyDNSIP = getProxyOpt(ipport) - if !strings.Contains(opt, ":") && !gotProxy && !gotKube { - fmt.Printf("tcp option needs ipaddress:port format, or is 'proxy'\n") + gotProxy, gotKube, proxyDNSIP, kubeAddrPort, err = getProxyOpt(ipport) + if err != nil { + fmt.Printf("tcp option error %v\n", err) + return + } else if !strings.Contains(opt, ":") && !gotProxy && !gotKube { + fmt.Printf("tcp option needs ipaddress:port format, or in either 'proxy' or 'kube'\n") return } ipAddrPort[i] = ipport @@ -327,19 +323,22 @@ func setAndStartProxyTCP(opt string) { } hasKube = true kubeport = 9001 + i + kubenum*types.EdgeviewMaxInstNum - ipAddrPort[i] = tcpKubeEndpoint + ipAddrPort[i] = kubeAddrPort } log.Tracef("setAndStartProxyTCP: (%d) ipport %s", i, ipport) } } else { - hasProxy, hasKube, proxyDNSIP = getProxyOpt(opt) - if !strings.Contains(opt, ":") && !hasProxy && !hasKube { - fmt.Printf("tcp option needs ipaddress:port format, or is 'proxy'\n") + hasProxy, hasKube, proxyDNSIP, kubeAddrPort, err = getProxyOpt(opt) + if err != nil { + fmt.Printf("tcp option error %v\n", err) + return + } else if !strings.Contains(opt, ":") && !hasProxy && !hasKube { + fmt.Printf("tcp option needs ipaddress:port format, or in either 'proxy' or 'kube'\n") return } ipAddrPort[0] = opt if hasKube { - ipAddrPort[0] = tcpKubeEndpoint + ipAddrPort[0] = kubeAddrPort kubeport = 9001 + kubenum*types.EdgeviewMaxInstNum } else { ipAddrPort[0] = opt @@ -369,7 +368,10 @@ func setAndStartProxyTCP(opt string) { log.Errorf("setAndStartProxyTCP: copy kube file error %v", err) } - _ = os.Remove(kubefileName) + err = os.Remove(kubefileName) + if err != nil { + log.Warn(err) + } log.Tracef("setAndStartProxyTCP: copy kube file done, port %d", kubeport) } @@ -439,114 +441,7 @@ func setAndStartProxyTCP(opt string) { } } -// Generate a random 32-byte symmetric key for kubeconfig file encryption -func generateSymmetricKey() ([]byte, error) { - key := make([]byte, 32) // 256-bit key for AES-256 - _, err := rand.Read(key) - if err != nil { - return nil, err - } - hexKey := hex.EncodeToString(key) // 64-character hexadecimal string - - // Generate a random byte - var b [1]byte - _, err = rand.Read(b[:]) - if err != nil { - return nil, err - } - - // Convert the byte to an integer and take the modulus to get it within the range 0-31 - start := int(b[0]) % 32 - - // Return a 32-character substring starting from the random index - return []byte(hexKey[start : start+32]), nil -} - -func encryptSymmetricKey(symKey []byte, publicKeyFile string) ([]byte, error) { - pubKeyData, err := os.ReadFile(publicKeyFile) - if err != nil { - return nil, err - } - - parsed, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pubKeyData)) - if err != nil { - return nil, err - } - parsedCryptoKey := parsed.(ssh.CryptoPublicKey) - pubCrypto := parsedCryptoKey.CryptoPublicKey() - pub := pubCrypto.(*rsa.PublicKey) - - encryptedBytes, err := rsa.EncryptPKCS1v15( - rand.Reader, - pub, - symKey, - ) - if err != nil { - return nil, err - } - - return encryptedBytes, nil -} - -func opensslEncryptConfig(symKey []byte, yamlString string) ([]byte, error) { - // save the config into a tmp file - idStr := strconv.Itoa(edgeviewInstID) - tmpCfgFile := "/tmp/ev-kubecfg-tmp." + idStr - tmpCfgFileEnc := "/tmp/ev-kubecfg-enc-tmp." + idStr - - // openssl encrypt input kubeconfig file - name := "/usr/bin/openssl" - args := []string{"enc", - "-aes-256-cbc", - "-salt", - "-k", - string(symKey), - "-in", - "-", - "-out", - tmpCfgFileEnc, - } - cmd := exec.Command(name, args...) - cmd.Stdin = bytes.NewBuffer([]byte(yamlString)) - err := cmd.Run() - if err != nil { - log.Errorf("opensslEncryptConfig: run openssl, args %v, error %v", args, err) - err1 := os.Remove(tmpCfgFile) - if err1 != nil { - log.Errorf("opensslEncryptConfig: remove tmpCfgFile error %v", err1) - } - err2 := os.Remove(tmpCfgFileEnc) - if err2 != nil { - log.Errorf("opensslEncryptConfig: remove tmpCfgFileEnc error %v", err2) - } - return nil, err - } - - // read content of the encrypted config file - encryptedData, err := os.ReadFile(tmpCfgFileEnc) - if err != nil { - err1 := os.Remove(tmpCfgFile) - if err1 != nil { - log.Errorf("opensslEncryptConfig: remove tmpCfgFile error %v", err1) - } - err2 := os.Remove(tmpCfgFileEnc) - if err2 != nil { - log.Errorf("opensslEncryptConfig: remove tmpCfgFileEnc error %v", err2) - } - return nil, err - } - err = os.Remove(tmpCfgFile) - if err != nil { - log.Errorf("opensslEncryptConfig: remove tmpCfgFile error %v", err) - } - err = os.Remove(tmpCfgFileEnc) - if err != nil { - log.Errorf("opensslEncryptConfig: remove tmpCfgFileEnc error %v", err) - } - return encryptedData, nil -} - -// need to modify the kubeconfig file for maaping the edgeview local port for kubectl +// need to modify the kubeconfig file for mapping the edgeview local port for kubectl func genKubeConfigFile(kubeport int) (string, error) { // this kubeYamlFile is user.yaml, which is not the kubeconfig file // it only identify the user as 'debugging-user', who has only the kubernetes @@ -564,46 +459,14 @@ func genKubeConfigFile(kubeport int) (string, error) { yamlString = pattern.ReplaceAllString(yamlString, newString) - pubkeyfile := "/run/authorized_keys" - fileInfo, err := os.Stat(pubkeyfile) - if err != nil || fileInfo.Size() == 0 { - err = fmt.Errorf("tcp kube requires ssh public key installed, error %v\n", err) - return "", err - } - - // generate symmetric key, 32 bytes - symmetricKey, err := generateSymmetricKey() - if err != nil { - log.Errorf("genKubeConfigFile: symmetric key gen error %v", err) - return "", err - } - - // encrypt kubeconfig file using the symmetric key - encfileBytes, err := opensslEncryptConfig(symmetricKey, yamlString) - if err != nil { - log.Errorf("genKubeConfigFile: openssl, kubeport %d, symkey %v, error %v", kubeport, symmetricKey, err) - return "", err - } - - // encrypt symmetric key using the public key - symKeyBytes, err := encryptSymmetricKey(symmetricKey, pubkeyfile) - if err != nil { - log.Errorf("genKubeConfigFile: encrypt symmetric key file error %v", err) - return "", err - } - - // write the encrypted symmetric key and kubeconfig file to a single file - // since the edgeview copy operation downloads only a single file - combinedBytes := append(symKeyBytes, encfileBytes...) - kubefileName := kubeServerFile + fmt.Sprintf(".%d", len(symKeyBytes)) - err = os.WriteFile(kubefileName, combinedBytes, 0644) + err = os.WriteFile(kubeServerFile, []byte(yamlString), 0644) if err != nil { - log.Errorf("genKubeConfigFile: combined file error %v", err) + log.Errorf("genKubeConfigFile: write file error %v", err) return "", err } - log.Noticef("genKubeConfigFile: convert done") - return kubefileName, nil + log.Noticef("genKubeConfigFile: write done") + return kubeServerFile, nil } func delKubeConfigFile(isKube bool) { @@ -614,10 +477,6 @@ func delKubeConfigFile(isKube bool) { if err != nil { fmt.Printf("delKubeConfigFile: delete file error %v\n", err) } - err = os.Remove(symKeyClientFile) - if err != nil { - fmt.Printf("delKubeConfigFile: delete sym key file error %v\n", err) - } } // each mapping of port with 'idx', and each flow within the mapping in the 'ChnNum' @@ -904,8 +763,8 @@ func tcpClientSendDone() { sendCloseToWss() } -func getProxyOpt(opt string) (bool, bool, string) { - var proxyDNSIP string +func getProxyOpt(opt string) (bool, bool, string, string, error) { + var proxyDNSIP, kubeAPIAddrPort string var hasProxy, hasKube bool if strings.HasPrefix(opt, "proxy") { hasProxy = true @@ -916,9 +775,16 @@ func getProxyOpt(opt string) (bool, bool, string) { } } } else if strings.HasPrefix(opt, "kube") { - hasKube = true + // get kubeConfig server's IP address and port + addrPort, err := getKubeServerIPandPort(kubeYamlFile) + if err == nil { + kubeAPIAddrPort = addrPort + hasKube = true + } else { + return false, false, "", "", fmt.Errorf("tcp kube option get cluster IP and port error %v", err) + } } - return hasProxy, hasKube, proxyDNSIP + return hasProxy, hasKube, proxyDNSIP, kubeAPIAddrPort, nil } func processTCPcmd(opt string, remotePorts map[int]int) (bool, bool, int, map[int]int) {