mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-08 22:44:43 +08:00
修复了一些已知问题
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
@@ -738,10 +739,14 @@ func (m *Manager) ResetSSHPassword(id int, password string) (string, error) {
|
||||
if err := m.EnsureSSH(id); err != nil {
|
||||
return "", err
|
||||
}
|
||||
chpasswdInput, err := chpasswdStdin("root", password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
client, err := ssh.Dial("tcp", net.JoinHostPort(c.IP, "22"), &ssh.ClientConfig{
|
||||
User: "root",
|
||||
Auth: []ssh.AuthMethod{ssh.Password(c.SSHPassword)},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
HostKeyCallback: kvmHostKeyCallback(c),
|
||||
Timeout: 8 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -753,8 +758,8 @@ func (m *Manager) ResetSSHPassword(id int, password string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
defer session.Close()
|
||||
cmd := fmt.Sprintf("printf 'root:%s\\n' | chpasswd", shellQuote(password))
|
||||
if output, err := session.CombinedOutput(cmd); err != nil {
|
||||
session.Stdin = bytes.NewReader(chpasswdInput)
|
||||
if output, err := session.CombinedOutput("chpasswd"); err != nil {
|
||||
return "", fmt.Errorf("failed to reset password: %v, output: %s", err, string(output))
|
||||
}
|
||||
c.SSHPassword = password
|
||||
@@ -2224,7 +2229,7 @@ func (m *Manager) EnsureSSH(id int) error {
|
||||
client, err := ssh.Dial("tcp", net.JoinHostPort(c.IP, "22"), &ssh.ClientConfig{
|
||||
User: "root",
|
||||
Auth: []ssh.AuthMethod{ssh.Password(c.SSHPassword)},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
HostKeyCallback: kvmHostKeyCallback(c),
|
||||
Timeout: 8 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -3190,7 +3195,7 @@ func (m *Manager) applyGuestIPv6OverSSH(c *config.Container) error {
|
||||
client, err := ssh.Dial("tcp", net.JoinHostPort(c.IP, "22"), &ssh.ClientConfig{
|
||||
User: "root",
|
||||
Auth: []ssh.AuthMethod{ssh.Password(c.SSHPassword)},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
HostKeyCallback: kvmHostKeyCallback(c),
|
||||
Timeout: 8 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -3377,6 +3382,46 @@ func shellQuote(value string) string {
|
||||
return "'" + strings.ReplaceAll(value, "'", "'\"'\"'") + "'"
|
||||
}
|
||||
|
||||
func chpasswdStdin(username, password string) ([]byte, error) {
|
||||
if username == "" || strings.ContainsAny(username, ":\n\r") {
|
||||
return nil, fmt.Errorf("invalid chpasswd username")
|
||||
}
|
||||
if strings.ContainsAny(password, "\n\r") {
|
||||
return nil, fmt.Errorf("password cannot contain newlines")
|
||||
}
|
||||
return []byte(username + ":" + password + "\n"), nil
|
||||
}
|
||||
|
||||
func kvmHostKeyCallback(c *config.Container) ssh.HostKeyCallback {
|
||||
return func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||
return verifyKVMHostKey(c, key, config.SaveConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyKVMHostKey(c *config.Container, key ssh.PublicKey, save func() error) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("KVM container is nil")
|
||||
}
|
||||
fingerprint := sshHostKeyFingerprint(key)
|
||||
if c.SSHHostKey != "" && c.SSHHostKey != fingerprint {
|
||||
return fmt.Errorf("KVM SSH host key mismatch")
|
||||
}
|
||||
if c.SSHHostKey == "" {
|
||||
c.SSHHostKey = fingerprint
|
||||
if save != nil {
|
||||
if err := save(); err != nil {
|
||||
return fmt.Errorf("failed to save KVM SSH host key: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sshHostKeyFingerprint(key ssh.PublicKey) string {
|
||||
sum := sha256.Sum256(key.Marshal())
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
func allocateDefaultEqualPorts(c *config.Container, count int) []int {
|
||||
if count <= 0 {
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package kvm
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"clicd/internal/config"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func TestChpasswdStdinPreservesShellMetacharacters(t *testing.T) {
|
||||
password := `pa'";$(touch /tmp/pwned); echo #\\word`
|
||||
got, err := chpasswdStdin("root", password)
|
||||
if err != nil {
|
||||
t.Fatalf("chpasswdStdin returned error: %v", err)
|
||||
}
|
||||
|
||||
want := []byte("root:" + password + "\n")
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("chpasswdStdin = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChpasswdStdinRejectsNewlines(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
password string
|
||||
}{
|
||||
{name: "username newline", username: "root\nadmin", password: "safe"},
|
||||
{name: "username colon", username: "root:admin", password: "safe"},
|
||||
{name: "password newline", username: "root", password: "safe\nroot:evil"},
|
||||
{name: "password carriage return", username: "root", password: "safe\rroot:evil"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if _, err := chpasswdStdin(tc.username, tc.password); err == nil {
|
||||
t.Fatal("chpasswdStdin returned nil error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyKVMHostKeyCapturesAndRejectsMismatch(t *testing.T) {
|
||||
key1 := testSSHPublicKey(t)
|
||||
key2 := testSSHPublicKey(t)
|
||||
|
||||
saves := 0
|
||||
c := &config.Container{}
|
||||
save := func() error {
|
||||
saves++
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := verifyKVMHostKey(c, key1, save); err != nil {
|
||||
t.Fatalf("first host key verification returned error: %v", err)
|
||||
}
|
||||
if c.SSHHostKey == "" {
|
||||
t.Fatal("first host key verification did not capture fingerprint")
|
||||
}
|
||||
if c.SSHHostKey != sshHostKeyFingerprint(key1) {
|
||||
t.Fatalf("captured fingerprint = %q, want %q", c.SSHHostKey, sshHostKeyFingerprint(key1))
|
||||
}
|
||||
if saves != 1 {
|
||||
t.Fatalf("save count = %d, want 1", saves)
|
||||
}
|
||||
|
||||
if err := verifyKVMHostKey(c, key1, save); err != nil {
|
||||
t.Fatalf("same host key verification returned error: %v", err)
|
||||
}
|
||||
if saves != 1 {
|
||||
t.Fatalf("save count after same key = %d, want 1", saves)
|
||||
}
|
||||
|
||||
if err := verifyKVMHostKey(c, key2, save); err == nil {
|
||||
t.Fatal("mismatched host key verification returned nil error")
|
||||
}
|
||||
}
|
||||
|
||||
func testSSHPublicKey(t *testing.T) ssh.PublicKey {
|
||||
t.Helper()
|
||||
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
signer, err := ssh.NewSignerFromKey(privateKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return signer.PublicKey()
|
||||
}
|
||||
Reference in New Issue
Block a user