up
This commit is contained in:
parent
c53e95fbb6
commit
2952846354
174
main.go
174
main.go
@ -6,6 +6,7 @@ import (
|
|||||||
"canguidev/shelfy/internal/utils"
|
"canguidev/shelfy/internal/utils"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -22,10 +23,25 @@ import (
|
|||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ---------- SFTP (SSH natif) ----------
|
// --------- CONFIG À ADAPTER ----------
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Dossier racine SFTP (tu montes déjà ton volume ici dans Docker)
|
||||||
|
SFTPBaseDir = "upload"
|
||||||
|
|
||||||
|
// Identifiants standards (utilisés si IP non autorisée)
|
||||||
|
LoginUser = "cangui2089"
|
||||||
|
LoginPass = "GHT30k7!"
|
||||||
|
|
||||||
|
// IP(s) autorisées pour connexion SANS mot de passe
|
||||||
|
AllowedIPs = []string{
|
||||||
|
"82.65.73.115",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
|
||||||
func loadOrCreateHostKey(path string) (ssh.Signer, error) {
|
func loadOrCreateHostKey(path string) (ssh.Signer, error) {
|
||||||
// Si une clé PEM existe déjà, on la lit
|
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
b, err := os.ReadFile(path)
|
b, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -33,8 +49,6 @@ func loadOrCreateHostKey(path string) (ssh.Signer, error) {
|
|||||||
}
|
}
|
||||||
return ssh.ParsePrivateKey(b)
|
return ssh.ParsePrivateKey(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sinon on génère une clé ed25519 et on la sauvegarde en PKCS#8 PEM
|
|
||||||
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -51,38 +65,114 @@ func loadOrCreateHostKey(path string) (ssh.Signer, error) {
|
|||||||
return ssh.ParsePrivateKey(pemBytes)
|
return ssh.ParsePrivateKey(pemBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startSFTPServer(uploadBase string) {
|
func ipAllowed(addr string) bool {
|
||||||
absBase, _ := filepath.Abs(uploadBase)
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
host = addr
|
||||||
|
}
|
||||||
|
for _, allow := range AllowedIPs {
|
||||||
|
if host == allow {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func startSFTPServer(base string) {
|
||||||
|
// --- helpers locaux pour charger/générer les clés hôte ---
|
||||||
|
loadOrCreateEd25519 := func(path string) (ssh.Signer, error) {
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return ssh.ParsePrivateKey(b)
|
||||||
|
}
|
||||||
|
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
pkcs8, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8})
|
||||||
|
if err := os.WriteFile(path, pemBytes, 0o600); err != nil { return nil, err }
|
||||||
|
return ssh.ParsePrivateKey(pemBytes)
|
||||||
|
}
|
||||||
|
loadOrCreateRSA := func(path string) (ssh.Signer, error) {
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return ssh.ParsePrivateKey(b)
|
||||||
|
}
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
pkcs1 := x509.MarshalPKCS1PrivateKey(priv)
|
||||||
|
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: pkcs1})
|
||||||
|
if err := os.WriteFile(path, pemBytes, 0o600); err != nil { return nil, err }
|
||||||
|
return ssh.ParsePrivateKey(pemBytes)
|
||||||
|
}
|
||||||
|
ipAllowed := func(addr string) bool {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil { host = addr }
|
||||||
|
for _, allow := range AllowedIPs {
|
||||||
|
if host == allow { return true }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- vérifications et "ancrage" dans le dossier upload ---
|
||||||
|
absBase, _ := filepath.Abs(base)
|
||||||
if fi, err := os.Stat(absBase); err != nil || !fi.IsDir() {
|
if fi, err := os.Stat(absBase); err != nil || !fi.IsDir() {
|
||||||
log.Fatalf("[SFTP] Le dossier %s est manquant ou invalide: %v", absBase, err)
|
log.Fatalf("[SFTP] Le dossier %s est manquant ou invalide: %v", absBase, err)
|
||||||
}
|
}
|
||||||
|
if err := os.Chdir(absBase); err != nil {
|
||||||
// Clé hôte
|
log.Fatalf("[SFTP] Chdir(%s): %v", absBase, err)
|
||||||
signer, err := loadOrCreateHostKey("sftp_host_ed25519.pem")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("[SFTP] Host key error: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config SSH (user/pass)
|
// --- clés hôte : ed25519 (moderne) + RSA (compat) ---
|
||||||
conf := &ssh.ServerConfig{
|
signerED, err := loadOrCreateEd25519("sftp_host_ed25519.pem")
|
||||||
|
if err != nil { log.Fatalf("[SFTP] Host key ed25519 error: %v", err) }
|
||||||
|
signerRSA, err := loadOrCreateRSA("sftp_host_rsa.pem")
|
||||||
|
if err != nil { log.Fatalf("[SFTP] Host key RSA error: %v", err) }
|
||||||
|
|
||||||
|
// --- config SSH avec auth IP-allowlist et user/pass ---
|
||||||
|
cfg := &ssh.ServerConfig{
|
||||||
PasswordCallback: func(meta ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
PasswordCallback: func(meta ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||||
user := meta.User()
|
|
||||||
remote := meta.RemoteAddr().String()
|
remote := meta.RemoteAddr().String()
|
||||||
log.Printf("[SFTP] Auth tentative user=%s from=%s", user, remote)
|
user := meta.User()
|
||||||
if user == "cangui2089" && string(pass) == "GHT30k7!" {
|
|
||||||
|
// IP autorisée → on accepte même mot de passe vide
|
||||||
|
if ipAllowed(remote) {
|
||||||
|
log.Printf("[SFTP] Auth IP-allowlist OK from=%s user=%s (mdp vide accepté)", remote, user)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
// Sinon, exige user+pass
|
||||||
|
if user == LoginUser && string(pass) == LoginPass {
|
||||||
|
log.Printf("[SFTP] Auth OK from=%s user=%s", remote, user)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
log.Printf("[SFTP] Auth FAIL from=%s user=%s", remote, user)
|
||||||
return nil, fmt.Errorf("auth failed")
|
return nil, fmt.Errorf("auth failed")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
conf.AddHostKey(signer)
|
cfg.AddHostKey(signerED)
|
||||||
|
cfg.AddHostKey(signerRSA)
|
||||||
|
|
||||||
// Listener TCP avec keep-alive agressif
|
// (option compat avancée) : décommente seulement si un client antique échoue encore
|
||||||
|
// cfg.Config = ssh.Config{
|
||||||
|
// KeyExchanges: []string{
|
||||||
|
// "curve25519-sha256", "curve25519-sha256@libssh.org",
|
||||||
|
// "diffie-hellman-group14-sha1",
|
||||||
|
// },
|
||||||
|
// Ciphers: []string{
|
||||||
|
// "chacha20-poly1305@openssh.com",
|
||||||
|
// "aes128-ctr","aes192-ctr","aes256-ctr",
|
||||||
|
// "aes128-cbc",
|
||||||
|
// },
|
||||||
|
// MACs: []string{"hmac-sha2-256","hmac-sha2-512","hmac-sha1"},
|
||||||
|
// }
|
||||||
|
|
||||||
|
// --- listener TCP avec keep-alive agressif ---
|
||||||
ln, err := net.Listen("tcp", ":2222")
|
ln, err := net.Listen("tcp", ":2222")
|
||||||
if err != nil {
|
if err != nil { log.Fatalf("[SFTP] Listen: %v", err) }
|
||||||
log.Fatalf("[SFTP] Listen: %v", err)
|
log.Printf("[SFTP] Écoute sur sftp://%s@0.0.0.0:2222 (root=%s)", LoginUser, absBase)
|
||||||
}
|
log.Printf("[SFTP] IP sans mot de passe autorisées: %v", AllowedIPs)
|
||||||
log.Println("[SFTP] Écoute sur sftp://cangui2089@0.0.0.0:2222 (base visible: voir montage)")
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
nConn, err := ln.Accept()
|
nConn, err := ln.Accept()
|
||||||
@ -90,26 +180,21 @@ func startSFTPServer(uploadBase string) {
|
|||||||
log.Printf("[SFTP] Accept err: %v", err)
|
log.Printf("[SFTP] Accept err: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Keep-alive TCP
|
|
||||||
if tc, ok := nConn.(*net.TCPConn); ok {
|
if tc, ok := nConn.(*net.TCPConn); ok {
|
||||||
_ = tc.SetKeepAlive(true)
|
_ = tc.SetKeepAlive(true)
|
||||||
_ = tc.SetKeepAlivePeriod(15 * time.Second)
|
_ = tc.SetKeepAlivePeriod(15 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(conn net.Conn) {
|
go func(conn net.Conn) {
|
||||||
// Handshake SSH
|
sshConn, chans, reqs, err := ssh.NewServerConn(conn, cfg)
|
||||||
sshConn, chans, reqs, err := ssh.NewServerConn(conn, conf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[SFTP] Handshake err: %v", err)
|
log.Printf("[SFTP] Handshake err: %v", err)
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("[SFTP] Client connecté: %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
|
log.Printf("[SFTP] Client connecté: %s (%s)", sshConn.RemoteAddr(), sshConn.ClientVersion())
|
||||||
|
|
||||||
// On ignore les global requests
|
|
||||||
go ssh.DiscardRequests(reqs)
|
go ssh.DiscardRequests(reqs)
|
||||||
|
|
||||||
// Gestion des channels (sessions)
|
|
||||||
for newCh := range chans {
|
for newCh := range chans {
|
||||||
if newCh.ChannelType() != "session" {
|
if newCh.ChannelType() != "session" {
|
||||||
newCh.Reject(ssh.UnknownChannelType, "only session channels are allowed")
|
newCh.Reject(ssh.UnknownChannelType, "only session channels are allowed")
|
||||||
@ -120,15 +205,13 @@ func startSFTPServer(uploadBase string) {
|
|||||||
log.Printf("[SFTP] Accept channel: %v", err)
|
log.Printf("[SFTP] Accept channel: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sur une session, on attend un "subsystem: sftp"
|
|
||||||
go func(in <-chan *ssh.Request) {
|
go func(in <-chan *ssh.Request) {
|
||||||
for req := range in {
|
for req := range in {
|
||||||
switch req.Type {
|
switch req.Type {
|
||||||
case "subsystem":
|
case "subsystem":
|
||||||
if string(req.Payload[4:]) == "sftp" { // payload = string len + "sftp"
|
// payload = uint32(len) + "sftp"
|
||||||
// Lance le serveur SFTP minimal (pas d’options avancées)
|
if len(req.Payload) >= 4 && string(req.Payload[4:]) == "sftp" {
|
||||||
server, err := sftp.NewServer(ch)
|
server, err := sftp.NewServer(ch) // API minimale
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[SFTP] init error: %v", err)
|
log.Printf("[SFTP] init error: %v", err)
|
||||||
_, _ = ch.Stderr().Write([]byte("sftp init error\n"))
|
_, _ = ch.Stderr().Write([]byte("sftp init error\n"))
|
||||||
@ -145,9 +228,6 @@ func startSFTPServer(uploadBase string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = req.Reply(false, nil)
|
_ = req.Reply(false, nil)
|
||||||
case "exec", "shell", "pty-req":
|
|
||||||
// On n'autorise que le sous-système SFTP
|
|
||||||
_ = req.Reply(false, nil)
|
|
||||||
default:
|
default:
|
||||||
_ = req.Reply(false, nil)
|
_ = req.Reply(false, nil)
|
||||||
}
|
}
|
||||||
@ -158,6 +238,7 @@ func startSFTPServer(uploadBase string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ---------- HTTP (Gin) ----------
|
// ---------- HTTP (Gin) ----------
|
||||||
|
|
||||||
func startHTTP() {
|
func startHTTP() {
|
||||||
@ -182,9 +263,24 @@ func startHTTP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// SFTP (recommandé pour VLC)
|
// SFTP sur 2222 (root = ./upload)
|
||||||
go startSFTPServer("upload")
|
go startSFTPServer(SFTPBaseDir)
|
||||||
|
|
||||||
// HTTP (ton app)
|
// HTTP normal
|
||||||
startHTTP()
|
startHTTP()
|
||||||
}
|
}
|
||||||
|
func loadOrCreateRSAHostKey(path string) (ssh.Signer, error) {
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
b, err := os.ReadFile(path)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
return ssh.ParsePrivateKey(b)
|
||||||
|
}
|
||||||
|
// Génère une clé RSA 2048
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil { return nil, err }
|
||||||
|
// Encode en PEM "RSA PRIVATE KEY" (PKCS#1)
|
||||||
|
pkcs1 := x509.MarshalPKCS1PrivateKey(priv)
|
||||||
|
pemBytes := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: pkcs1})
|
||||||
|
if err := os.WriteFile(path, pemBytes, 0o600); err != nil { return nil, err }
|
||||||
|
return ssh.ParsePrivateKey(pemBytes)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user