mirror of
https://github.com/kubesphere/kubekey.git
synced 2025-12-25 17:12:50 +00:00
feat: feat no root ssh (#2858)
feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh feat: feat no root ssh Signed-off-by: xuesongzuo@yunify.com <xuesongzuo@yunify.com>
This commit is contained in:
parent
c7b0b113c6
commit
d9c699f80a
|
|
@ -130,7 +130,14 @@
|
|||
loop: "{{ .cloud_config.runcmd | toJson }}"
|
||||
command: "{{ .item }}"
|
||||
|
||||
- name: Sync kubeconfig
|
||||
- name: Sync kubeconfig to current user
|
||||
copy:
|
||||
src: >-
|
||||
{{ .cloud_config_dir }}/kubeconfig/value
|
||||
dest: ~/.kube/config
|
||||
mode: 0600
|
||||
|
||||
- name: Sync kubeconfig to root
|
||||
copy:
|
||||
src: >-
|
||||
{{ .cloud_config_dir }}/kubeconfig/value
|
||||
|
|
|
|||
|
|
@ -29,5 +29,6 @@
|
|||
rm -rf /usr/local/bin/kubeadm && rm -rf /usr/local/bin/kubelet && rm -rf /usr/local/bin/kubectl
|
||||
rm -rf /var/lib/kubelet/
|
||||
rm -rf /etc/kubernetes/
|
||||
rm -rf .kube/config
|
||||
rm -rf ~/.kube/config
|
||||
rm -rf /root/.kube/config
|
||||
rm -rf /var/lib/etcd
|
||||
|
|
@ -42,8 +42,14 @@
|
|||
dest: >-
|
||||
{{ .binary_dir }}/kubeconfig
|
||||
|
||||
- name: Kubernetes | Distribute kubeconfig to remote host
|
||||
- name: Kubernetes | Distribute kubeconfig to remote host of current user
|
||||
copy:
|
||||
src: >-
|
||||
{{ .binary_dir }}/kubeconfig
|
||||
dest: /root/.kube/config
|
||||
dest: ~/.kube/config
|
||||
|
||||
- name: Kubernetes | Distribute kubeconfig to remote host of root user
|
||||
copy:
|
||||
src: >-
|
||||
{{ .binary_dir }}/kubeconfig
|
||||
dest: /root/.kube/config
|
||||
|
|
@ -45,9 +45,13 @@
|
|||
|
||||
- name: Init | Copy kubeconfig to default directory
|
||||
command: |
|
||||
if [ ! -d ~/.kube ]; then
|
||||
mkdir -p ~/.kube
|
||||
fi
|
||||
if [ ! -d /root/.kube ]; then
|
||||
mkdir -p /root/.kube
|
||||
fi
|
||||
cp -f /etc/kubernetes/admin.conf ~/.kube/config
|
||||
cp -f /etc/kubernetes/admin.conf /root/.kube/config
|
||||
when: .kubernetes_install_LoadState.stdout | eq "not-found"
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,13 @@
|
|||
command: |
|
||||
/usr/local/bin/kubeadm join --config=/etc/kubernetes/kubeadm-config.yaml --ignore-preflight-errors=FileExisting-crictl,ImagePull
|
||||
|
||||
- name: Join | Synchronize kubeconfig to remote node
|
||||
- name: Join | Synchronize kubeconfig to remote node for current user
|
||||
copy:
|
||||
src: >-
|
||||
{{ .work_dir }}/kubekey/kubeconfig
|
||||
dest: ~/.kube/config
|
||||
|
||||
- name: Join | Synchronize kubeconfig to remote node for root
|
||||
copy:
|
||||
src: >-
|
||||
{{ .work_dir }}/kubekey/kubeconfig
|
||||
|
|
|
|||
|
|
@ -22,5 +22,6 @@
|
|||
# If /var/log/pods/ is not cleaned up, static pods may accumulate unexpected restarts due to lingering log files interfering with their lifecycle.
|
||||
rm -rf /var/log/pods/
|
||||
rm -rf /etc/kubernetes/
|
||||
rm -rf .kube/config
|
||||
rm -rf ~/.kube/config
|
||||
rm -rf /root/.kube/config
|
||||
rm -rf /var/lib/etcd
|
||||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package connector
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
|
|
@ -26,6 +27,7 @@ import (
|
|||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
|
|
@ -120,6 +122,8 @@ type sshConnector struct {
|
|||
shell string
|
||||
|
||||
gatherFacts *cacheGatherFact
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Init connector, get ssh.Client
|
||||
|
|
@ -188,6 +192,33 @@ func (c *sshConnector) Close(context.Context) error {
|
|||
return c.client.Close()
|
||||
}
|
||||
|
||||
func (c *sshConnector) session() (*ssh.Session, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.client == nil {
|
||||
return nil, errors.New("connection closed")
|
||||
}
|
||||
|
||||
sess, err := c.client.NewSession()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 0, // disable echoing
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
|
||||
err = sess.RequestPty("xterm", 100, 50, modes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
// PutFile to remote node. src is the file bytes. dst is the remote filename
|
||||
func (c *sshConnector) PutFile(_ context.Context, src []byte, dst string, mode fs.FileMode) error {
|
||||
// create sftp client
|
||||
|
|
@ -243,72 +274,78 @@ func (c *sshConnector) FetchFile(_ context.Context, src string, dst io.Writer) e
|
|||
return nil
|
||||
}
|
||||
|
||||
// ExecuteCommand in remote host
|
||||
// ExecuteCommand exec cmd with sudo
|
||||
func (c *sshConnector) ExecuteCommand(_ context.Context, cmd string) ([]byte, []byte, error) {
|
||||
cmd = fmt.Sprintf("sudo -SE %s << 'KUBEKEY_EOF'\n%s\nKUBEKEY_EOF\n", c.shell, cmd)
|
||||
klog.V(5).InfoS("exec ssh command", "cmd", cmd, "host", c.Host)
|
||||
// create ssh session
|
||||
session, err := c.client.NewSession()
|
||||
session, err := c.session()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to create ssh session")
|
||||
return nil, nil, err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// get pipe from session
|
||||
stdin, err := session.StdinPipe()
|
||||
cmd = SudoPrefix(c.shell, cmd)
|
||||
|
||||
in, err := session.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to get stdin pipe")
|
||||
}
|
||||
stdout, err := session.StdoutPipe()
|
||||
|
||||
out, err := session.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to get stdout pipe")
|
||||
}
|
||||
|
||||
stderr, err := session.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to get stderr pipe")
|
||||
}
|
||||
// Start the remote command
|
||||
if err := session.Start(cmd); err != nil {
|
||||
|
||||
if err = session.Start(cmd); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to start session")
|
||||
}
|
||||
if c.Password != "" {
|
||||
if _, err := stdin.Write([]byte(c.Password + "\n")); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to write password")
|
||||
var (
|
||||
output []byte
|
||||
line = ""
|
||||
r = bufio.NewReader(out)
|
||||
)
|
||||
|
||||
for {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
output = append(output, b)
|
||||
|
||||
if b == byte('\n') {
|
||||
line = ""
|
||||
continue
|
||||
}
|
||||
|
||||
line += string(b)
|
||||
|
||||
if (strings.HasPrefix(line, "[sudo] password for ") || strings.HasPrefix(line, "Password")) && strings.HasSuffix(line, ": ") {
|
||||
_, err = in.Write([]byte(c.Password + "\n"))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := stdin.Close(); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to close stdin pipe")
|
||||
}
|
||||
|
||||
// Create buffers to store stdout and stderr output
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
|
||||
// When reading large amounts of data from stdout/stderr, the pipe buffer can fill up
|
||||
// and block the remote command from completing if we don't read from it continuously.
|
||||
// To prevent this deadlock scenario, we need to read stdout/stderr asynchronously
|
||||
// in separate goroutines while the command is running.
|
||||
// Create channels to signal when copying is complete
|
||||
stdoutDone := make(chan error, 1)
|
||||
stderrDone := make(chan error, 1)
|
||||
|
||||
// Copy stdout and stderr concurrently to prevent pipe buffer from filling
|
||||
go func() {
|
||||
_, err := io.Copy(&stdoutBuf, stdout)
|
||||
stdoutDone <- err
|
||||
}()
|
||||
go func() {
|
||||
_, err := io.Copy(&stderrBuf, stderr)
|
||||
stderrDone <- err
|
||||
}()
|
||||
|
||||
// Wait for command to complete
|
||||
outStr := strings.TrimPrefix(string(output), fmt.Sprintf("[sudo] password for %s:", c.User))
|
||||
err = session.Wait()
|
||||
var stderrBuffer bytes.Buffer
|
||||
_, _ = io.Copy(&stderrBuffer, stderr)
|
||||
outStr = strings.TrimSpace(outStr)
|
||||
stderrData := stderrBuffer.Bytes()
|
||||
if err != nil {
|
||||
return []byte(outStr), nil, errors.Wrap(err, strings.TrimSpace(string(stderrData)))
|
||||
}
|
||||
return []byte(outStr), stderrData, nil
|
||||
}
|
||||
|
||||
// Wait for stdout and stderr copying to finish to ensure we've captured all output
|
||||
<-stdoutDone
|
||||
<-stderrDone
|
||||
|
||||
return stdoutBuf.Bytes(), stderrBuf.Bytes(), errors.Wrap(err, "failed to execute ssh command")
|
||||
// SudoPrefix returns the prefix for sudo commands.
|
||||
func SudoPrefix(shell, cmd string) string {
|
||||
return fmt.Sprintf("TERM=dumb; export LANG=C.UTF-8;sudo -E %s << 'KUBEKEY_EOF'\n%s\nKUBEKEY_EOF", shell, cmd)
|
||||
}
|
||||
|
||||
// HostInfo from gatherFacts cache
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package modules
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
|
|
@ -251,7 +252,15 @@ func (ca copyArgs) copyAbsoluteDir(ctx context.Context, conn connector.Connector
|
|||
dest = filepath.Join(ca.dest, rel)
|
||||
}
|
||||
|
||||
return conn.PutFile(ctx, data, dest, mode)
|
||||
tmpDest := filepath.Join("/tmp", ca.dest)
|
||||
|
||||
if err = conn.PutFile(ctx, data, tmpDest, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -289,8 +298,16 @@ func (ca copyArgs) copyRelativeDir(ctx context.Context, pj project.Project, relP
|
|||
}
|
||||
dest = filepath.Join(ca.dest, rel)
|
||||
}
|
||||
tmpDest := filepath.Join("/tmp", ca.dest)
|
||||
|
||||
return conn.PutFile(ctx, data, dest, mode)
|
||||
err = conn.PutFile(ctx, data, tmpDest, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -305,7 +322,15 @@ func (ca copyArgs) copyContent(ctx context.Context, mode fs.FileMode, conn conne
|
|||
mode = os.FileMode(*ca.mode)
|
||||
}
|
||||
|
||||
if err := conn.PutFile(ctx, []byte(ca.content), ca.dest, mode); err != nil {
|
||||
tmpDest := filepath.Join("/tmp", ca.dest)
|
||||
|
||||
if err := conn.PutFile(ctx, []byte(ca.content), tmpDest, mode); err != nil {
|
||||
return StdoutFailed, "failed to copy file", err
|
||||
}
|
||||
|
||||
_, _, err := conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(ca.dest), tmpDest, ca.dest))
|
||||
|
||||
if err != nil {
|
||||
return StdoutFailed, "failed to copy file", err
|
||||
}
|
||||
|
||||
|
|
@ -323,8 +348,15 @@ func (ca copyArgs) copyFile(ctx context.Context, data []byte, mode fs.FileMode,
|
|||
if ca.mode != nil {
|
||||
mode = os.FileMode(*ca.mode)
|
||||
}
|
||||
tmpDest := filepath.Join("/tmp", dest)
|
||||
|
||||
return conn.PutFile(ctx, data, dest, mode)
|
||||
if err := conn.PutFile(ctx, data, tmpDest, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err := conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Register the "copy" module at init.
|
||||
|
|
|
|||
|
|
@ -18,9 +18,11 @@ package modules
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
||||
"github.com/kubesphere/kubekey/v4/pkg/variable"
|
||||
|
|
@ -78,6 +80,10 @@ func ModuleFetch(ctx context.Context, options ExecOptions) (string, string, erro
|
|||
if err != nil {
|
||||
return StdoutFailed, "\"dest\" in args should be string", err
|
||||
}
|
||||
tmpDir, err := variable.StringVar(ha, ha, "tmp_dir")
|
||||
if err != nil || tmpDir == "" {
|
||||
tmpDir = "/tmp/kubekey/"
|
||||
}
|
||||
|
||||
// get connector
|
||||
conn, err := options.getConnector(ctx)
|
||||
|
|
@ -99,7 +105,15 @@ func ModuleFetch(ctx context.Context, options ExecOptions) (string, string, erro
|
|||
}
|
||||
defer destFile.Close()
|
||||
|
||||
if err := conn.FetchFile(ctx, srcParam, destFile); err != nil {
|
||||
tmpFetchFileName := filepath.Join(tmpDir, fmt.Sprintf("fetch-%s-%s", options.Task.GetUID(), rand.String(5)))
|
||||
|
||||
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("cp %s %s\nchmod 755 %s", srcParam, tmpFetchFileName, tmpFetchFileName))
|
||||
|
||||
if err != nil {
|
||||
return StdoutFailed, "failed to fetch file", err
|
||||
}
|
||||
|
||||
if err = conn.FetchFile(ctx, tmpFetchFileName, destFile); err != nil {
|
||||
return StdoutFailed, "failed to fetch file", err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package modules
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
|
|
@ -226,8 +227,15 @@ func handleRelativeDir(ctx context.Context, pj project.Project, relPath string,
|
|||
}
|
||||
dest = filepath.Join(ta.dest, rel)
|
||||
}
|
||||
tmpDest := filepath.Join("/tmp", dest)
|
||||
|
||||
return conn.PutFile(ctx, result, dest, mode)
|
||||
if err = conn.PutFile(ctx, result, tmpDest, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -246,8 +254,15 @@ func (ta templateArgs) readFile(ctx context.Context, data string, mode fs.FileMo
|
|||
if ta.mode != nil {
|
||||
mode = os.FileMode(*ta.mode)
|
||||
}
|
||||
tmpDest := filepath.Join("/tmp", dest)
|
||||
|
||||
return conn.PutFile(ctx, result, dest, mode)
|
||||
if err = conn.PutFile(ctx, result, tmpDest, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// absDir when template.src is absolute dir, get all files by os, parse it, and copy to remote.
|
||||
|
|
@ -288,11 +303,15 @@ func (ta templateArgs) absDir(ctx context.Context, conn connector.Connector, var
|
|||
dest = filepath.Join(ta.dest, rel)
|
||||
}
|
||||
|
||||
if err := conn.PutFile(ctx, result, dest, mode); err != nil {
|
||||
return errors.Wrap(err, "failed to put file")
|
||||
tmpDest := filepath.Join("/tmp", dest)
|
||||
|
||||
if err = conn.PutFile(ctx, result, tmpDest, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
_, _, err = conn.ExecuteCommand(ctx, fmt.Sprintf("mkdir -p %s\nmv %s %s", filepath.Dir(dest), tmpDest, dest))
|
||||
|
||||
return err
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to walk dir %q", ta.src)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue