Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: use kubectl proxy for k8s session #1627

Merged
merged 2 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ WORKDIR /opt/koko
RUN make build -s \
&& set -x && ls -al . \
&& mv /opt/koko/build/koko /opt/koko/koko \
&& mv /opt/koko/build/helm /opt/koko/bin/helm \
&& mv /opt/koko/build/kubectl /opt/koko/bin/kubectl
&& mv /opt/koko/bin/rawhelm /opt/koko/bin/helm \
&& mv /opt/koko/bin/rawkubectl /opt/koko/bin/kubectl

RUN mkdir /opt/koko/release \
&& mv /opt/koko/locale /opt/koko/release \
Expand All @@ -29,6 +29,9 @@ ARG TARGETARCH
ENV LANG=en_US.UTF-8

ARG DEPENDENCIES=" \
bash-completion \
jq \
less \
ca-certificates"

ARG APT_MIRROR=http://deb.debian.org
Expand Down
3 changes: 0 additions & 3 deletions Dockerfile-ee
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ FROM jumpserver/koko:${VERSION}-ce
ARG TARGETARCH

ARG DEPENDENCIES=" \
bash-completion \
curl \
git \
git-lfs \
iputils-ping \
jq \
less \
openssh-client \
telnet \
unzip \
Expand Down
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ endef

build:
GOARCH=$(GOARCH) GOOS=$(GOOS) $(KOKOBUILD) -o $(BUILDDIR)/$(NAME) $(KOKOSRCFILE)
GOARCH=$(GOARCH) GOOS=$(GOOS) $(K8SCMDBUILD) -o $(BUILDDIR)/kubectl $(KUBECTLFILE)
GOARCH=$(GOARCH) GOOS=$(GOOS) $(K8SCMDBUILD) -o $(BUILDDIR)/helm $(HELMFILE)

all: koko-ui
$(call make_artifact_full,darwin,amd64)
Expand Down
8 changes: 8 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ echo "KoKo Version $VERSION, more see https://www.jumpserver.org"
echo "Quit the server with CONTROL-C."
echo

# 创建 server.key server.crt
if [ ! -f /opt/koko/server.key ]; then
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /opt/koko/server.key -out /opt/koko/server.crt -subj "/C=CN/ST=Beijing/L=Beijing/O=JumpServer/OU=JumpServer/CN=JumpServer"
fi

# /opt/koko to 700 disable other user access
chmod 700 /opt/koko

exec "$@"
3 changes: 2 additions & 1 deletion pkg/httpd/tty.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package httpd
import (
"encoding/json"
"errors"
"github.com/jumpserver/koko/pkg/srvconn"
"io"
"sync"

"github.com/jumpserver/koko/pkg/srvconn"

"github.com/gliderlabs/ssh"
"github.com/jumpserver/koko/pkg/exchange"
"github.com/jumpserver/koko/pkg/jms-sdk-go/common"
Expand Down
9 changes: 7 additions & 2 deletions pkg/httpd/userwebsocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import (
"context"
"encoding/json"
"errors"
"github.com/jumpserver/koko/pkg/proxy"
"github.com/jumpserver/koko/pkg/srvconn"
"io"
"time"

"github.com/jumpserver/koko/pkg/proxy"
"github.com/jumpserver/koko/pkg/srvconn"

"github.com/gin-gonic/gin"
gorilla "github.com/gorilla/websocket"

Expand Down Expand Up @@ -118,6 +119,10 @@ func (userCon *UserWebsocket) Run() {
case <-ctx.Done():
}
userCon.handler.CleanUp()
if userCon.k8sClient != nil {
userCon.k8sClient.Close()
}

logger.Infof("Ws[%s] done with exit %s", userCon.Uuid, errMsg)
}

Expand Down
28 changes: 22 additions & 6 deletions pkg/srvconn/conn_k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,15 @@ func NewK8sConnection(ops ...K8sOption) (*K8sCon, error) {
if !IsValidK8sUserToken(args) {
return nil, InValidToken
}
_, err := utils.Encrypt(args.Token, config.CipherKey)
kubeProxy := NewKubectlProxyConn(args)
err := kubeProxy.Start()
if err != nil {
return nil, fmt.Errorf("%w: encrypt k8s token failed %s", InValidToken, err)
logger.Errorf("K8sCon start proxy err: %s", err)
return nil, fmt.Errorf("K8sCon start proxy err: %w", err)
}

lcmd, err := startK8SLocalCommand(args)
envs := kubeProxy.Env()
lcmd, err := startK8SLocalCommand(envs)
if err != nil {
logger.Errorf("K8sCon start local pty err: %s", err)
return nil, fmt.Errorf("K8sCon start local pty err: %w", err)
Expand All @@ -86,10 +89,11 @@ func NewK8sConnection(ops ...K8sOption) (*K8sCon, error) {
_ = lcmd.Close()
return nil, err
}
return &K8sCon{options: args, LocalCommand: lcmd}, nil
return &K8sCon{options: args, LocalCommand: lcmd, proxy: kubeProxy}, nil
}

type K8sCon struct {
proxy *KubectlProxyConn
options *k8sOptions
*localcommand.LocalCommand
}
Expand All @@ -98,12 +102,18 @@ func (k *K8sCon) KeepAlive() error {
return nil
}

func (k *K8sCon) Close() error {
_ = k.LocalCommand.Close()
return k.proxy.Close()
}

type k8sOptions struct {
ClusterServer string // https://172.16.10.51:8443
Username string // user 系统用户名
Token string // 授权token
IsSkipTls bool
ExtraEnv map[string]string
DEBUG bool

win Windows
}
Expand Down Expand Up @@ -141,7 +151,7 @@ func (o *k8sOptions) Env() []string {
}
}

func startK8SLocalCommand(args *k8sOptions) (*localcommand.LocalCommand, error) {
func startK8SLocalCommand(env []string) (*localcommand.LocalCommand, error) {
pwd, _ := os.Getwd()
shPath := filepath.Join(pwd, k8sInitFilename)
argv := []string{
Expand All @@ -150,7 +160,7 @@ func startK8SLocalCommand(args *k8sOptions) (*localcommand.LocalCommand, error)
"--mount-proc",
shPath,
}
return localcommand.New("unshare", argv, localcommand.WithEnv(args.Env()))
return localcommand.New("unshare", argv, localcommand.WithEnv(env))
}

type K8sOption func(*k8sOptions)
Expand Down Expand Up @@ -190,3 +200,9 @@ func K8sPtyWin(win Windows) K8sOption {
args.win = win
}
}

func K8sDebug(debug bool) K8sOption {
return func(args *k8sOptions) {
args.DEBUG = debug
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main differences between this code snippet and the original one from GitHub are:

  1. The Start method no longer exists, since it was replaced with startK8SLocalCommand.
  2. There is now additional functionality to set various options including username/authorization token, use of Windows environment variables, debug mode etc.

For example:

var (
   pwd string
   shPath string = filepath.Join(pwd,k8sInitFilename)
 argv []string{
  "--mount-proc",
  shPath,
}

return localcommand.New("unshare", argv, localcommand.WithEnv(args.Env()))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that there is no major difference between these two code blocks. They contain similar code with different function names but the same logic for initializing and starting a Kubernetes connection via an unshare process. Additionally, they both have comments describing their purpose.

Therefore, I do not see any specific irregularities or need for further adjustments other than to rephrase some parts of the docstrings for clarity when discussing this block as a whole rather than individual functions.

Here's how the docblocks could look under those new conditions:

"NewK8sConnection":

func NewK8sConnection(ops ...K8sOption) (*K8sCon, error) {...}

"The keep alive loop":

func (k *K8sCon) Close() error {...}```

The only actual changes made would be renaming variables and changing variable types if needed throughout the script without altering what it aims to achieve or its overall flow.

132 changes: 132 additions & 0 deletions pkg/srvconn/conn_k8s_proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package srvconn

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"sync"

"github.com/jumpserver/koko/pkg/common"
"github.com/jumpserver/koko/pkg/config"
"github.com/jumpserver/koko/pkg/logger"
)

var k8sProxyDirname = "k8s_proxy"

func GetK8sProxyDir() string {
pwd, _ := os.Getwd()
dirPath := filepath.Join(pwd, k8sProxyDirname)
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
_ = os.Mkdir(dirPath, 0700)
}
return dirPath
}

func NewKubectlProxyConn(opt *k8sOptions) *KubectlProxyConn {
return &KubectlProxyConn{opts: opt, Id: common.UUID()}
}

type KubectlProxyConn struct {
Id string
opts *k8sOptions

proxyCmd *exec.Cmd
configPath string
once sync.Once
}

func (k *KubectlProxyConn) Close() error {
var err error
k.once.Do(func() {
if k.proxyCmd != nil {
err = k.proxyCmd.Process.Kill()
}
if k.configPath != "" {
_ = os.Remove(k.configPath)
}
gloablTokenMaps.Delete(k.Id)
_ = os.Remove(k.UnixPath())
})
return err
}

func (k *KubectlProxyConn) Start() error {
var err error
k.configPath, err = k.CreateKubeConfig(k.opts.ClusterServer, k.opts.Token)
if err != nil {
return err
}

/*
kubetcl proxy --kubeconfig=path --unix-socket=port --api-prefix=/
*/
logger.Infof("kubeconfig: %s", k.configPath)
k.proxyCmd = exec.Command("kubectl", "proxy",
"--disable-filter=true",
fmt.Sprintf("--kubeconfig=%s", k.configPath),
fmt.Sprintf("--unix-socket=%s", k.UnixPath()),
"--api-prefix=/")

err = k.proxyCmd.Start()
go func() {
_ = k.proxyCmd.Wait()
logger.Infof("kubectl proxy id %s %s exit", k.Id, k.opts.ClusterServer)
}()
return err
}

func (k *KubectlProxyConn) UnixPath() string {
k8sDir := GetK8sProxyDir()
return filepath.Join(k8sDir, fmt.Sprintf("proxy-%s.sock", k.Id))
}

func (k *KubectlProxyConn) CreateKubeConfig(server, token string) (string, error) {
k8sDir := GetK8sProxyDir()
configPath := filepath.Join(k8sDir, fmt.Sprintf("config-%s", k.Id))
configContent := fmt.Sprintf(proxyconfigTmpl, server, token)
err := os.WriteFile(configPath, []byte(configContent), 0600)
return configPath, err
}

func (k *KubectlProxyConn) Env() []string {
o := k.opts
skipTls := "true"
if !o.IsSkipTls {
skipTls = "false"
}
clusterServer := k.UnixPath()
gloablTokenMaps.Store(k.Id, clusterServer)
k8sName := strings.Trim(strconv.Quote(o.ExtraEnv["K8sName"]), "\"")
k8sName = strings.ReplaceAll(k8sName, "`", "\\`")
return []string{
fmt.Sprintf("KUBECTL_USER=%s", o.Username),
fmt.Sprintf("KUBECTL_CLUSTER=%s", k8sReverseProxyURL),
fmt.Sprintf("KUBECTL_INSECURE_SKIP_TLS_VERIFY=%s", skipTls),
fmt.Sprintf("KUBECTL_TOKEN=%s", k.Id),
fmt.Sprintf("WELCOME_BANNER=%s", config.KubectlBanner),
fmt.Sprintf("K8S_NAME=%s", k8sName),
}
}

var proxyconfigTmpl = `apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: true
server: %s
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: JumpServer-user
name: kubernetes
current-context: kubernetes
kind: Config
preferences: {}
users:
- name: JumpServer-user
user:
token: %s
`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No known irregularities, issues or optimizations can be found from the provided code snippet as it is perfectly fine. The code defines functions and structs to set up a Kubernetes proxy connection using kubectl's proxy command with options controlled by global settings.

Note that I've removed some lines due to their relevance based on an arbitrary cutoff of knowledge but otherwise there don't seem to be significant modifications or changes that would need attention now compared the latest version available at this time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appears to be no known syntax errors in the provided code. It is written in Go, which is supported by this platform.

Possible Improvements:

  1. Use of strconv package to convert integers from byte slices:
server := strconv.ParseUint(tokenBytes[0], 10, 32)
token = token[:len(token)-bytes.Repeat([]byte{uint8('u')}, uint(len(server)/4))}

It could improve readability.

  1. Add documentation comments or a separate file with usage examples for better guidance.

  2. For the 'gloablTokenMaps' map, use 'strings.ReplaceAll':

var gloablTokenMaps = make(map[string]string)
gloablTokenMaps.Store(k.Id, clusterServer)

In general though, I would say that most changes can either be ignored as they're not relevant to current version control, or are minor stylistic changes such as adding spaces around assignment operators, using uppercase variable names and functions etc. So there's no need to add anything new.

Loading
Loading