194 lines
4.7 KiB
Go
194 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"git.deuxfleurs.fr/Deuxfleurs/bagage/s3"
|
|
"git.deuxfleurs.fr/Deuxfleurs/bagage/sftp"
|
|
"github.com/minio/minio-go/v7"
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
"golang.org/x/crypto/ssh"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
)
|
|
|
|
func main() {
|
|
log.Println("=== Starting Bagage ===")
|
|
config := (&Config{}).LoadWithDefault().LoadWithEnv()
|
|
log.Println(config)
|
|
|
|
// Some init
|
|
os.MkdirAll(config.S3Cache, 0755)
|
|
|
|
// Launch our submodules
|
|
done := make(chan error)
|
|
go httpServer(config, done)
|
|
go sshServer(config, done)
|
|
|
|
err := <-done
|
|
if err != nil {
|
|
log.Fatalf("A component failed: %v", err)
|
|
}
|
|
}
|
|
|
|
type s3creds struct {
|
|
accessKey string
|
|
secretKey string
|
|
}
|
|
|
|
var keychain map[string]s3creds
|
|
|
|
func sshServer(dconfig *Config, done chan error) {
|
|
keychain = make(map[string]s3creds)
|
|
|
|
config := &ssh.ServerConfig{
|
|
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
|
log.Printf("Login: %s\n", c.User())
|
|
access_key, secret_key, err := LdapGetS3(dconfig, c.User(), string(pass))
|
|
if err == nil {
|
|
keychain[c.User()] = s3creds{access_key, secret_key}
|
|
}
|
|
return nil, err
|
|
},
|
|
}
|
|
|
|
privateBytes, err := ioutil.ReadFile(dconfig.SSHKey)
|
|
if err != nil {
|
|
log.Fatal("Failed to load private key", err)
|
|
}
|
|
|
|
private, err := ssh.ParsePrivateKey(privateBytes)
|
|
if err != nil {
|
|
log.Fatal("Failed to parse private key", err)
|
|
}
|
|
|
|
config.AddHostKey(private)
|
|
|
|
// Once a ServerConfig has been configured, connections can be
|
|
// accepted.
|
|
listener, err := net.Listen("tcp", "0.0.0.0:2222")
|
|
if err != nil {
|
|
log.Fatal("failed to listen for connection", err)
|
|
}
|
|
log.Printf("Listening on %v\n", listener.Addr())
|
|
|
|
for {
|
|
nConn, err := listener.Accept()
|
|
if err != nil {
|
|
log.Printf("failed to accept incoming connection: ", err)
|
|
continue
|
|
}
|
|
go handleSSHConn(nConn, dconfig, config)
|
|
}
|
|
}
|
|
|
|
func handleSSHConn(nConn net.Conn, dconfig *Config, config *ssh.ServerConfig) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
defer nConn.Close()
|
|
|
|
// Before use, a handshake must be performed on the incoming
|
|
// net.Conn.
|
|
serverConn, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
|
if err != nil {
|
|
log.Printf("failed to handshake: ", err)
|
|
}
|
|
defer serverConn.Conn.Close()
|
|
user := serverConn.Conn.User()
|
|
log.Printf("SSH connection established for %v\n", user)
|
|
|
|
// The incoming Request channel must be serviced.
|
|
go ssh.DiscardRequests(reqs)
|
|
|
|
// Service the incoming Channel channel.
|
|
for newChannel := range chans {
|
|
// Channels have a type, depending on the application level
|
|
// protocol intended. In the case of an SFTP session, this is "subsystem"
|
|
// with a payload string of "<length=4>sftp"
|
|
log.Printf("Incoming channel: %s\n", newChannel.ChannelType())
|
|
if newChannel.ChannelType() != "session" {
|
|
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
|
log.Printf("Unknown channel type: %s\n", newChannel.ChannelType())
|
|
continue
|
|
}
|
|
|
|
channel, requests, err := newChannel.Accept()
|
|
if err != nil {
|
|
log.Print("could not accept channel.", err)
|
|
}
|
|
log.Printf("Channel accepted\n")
|
|
|
|
// Sessions have out-of-band requests such as "shell",
|
|
// "pty-req" and "env". Here we handle only the
|
|
// "subsystem" request.
|
|
go func(in <-chan *ssh.Request) {
|
|
for req := range in {
|
|
log.Printf("Request: %v\n", req.Type)
|
|
ok := false
|
|
switch req.Type {
|
|
case "subsystem":
|
|
log.Printf("Subsystem: %s\n", req.Payload[4:])
|
|
if string(req.Payload[4:]) == "sftp" {
|
|
ok = true
|
|
}
|
|
}
|
|
log.Printf(" - accepted: %v\n", ok)
|
|
req.Reply(ok, nil)
|
|
}
|
|
}(requests)
|
|
|
|
creds := keychain[user]
|
|
mc, err := minio.New(dconfig.Endpoint, &minio.Options{
|
|
Creds: credentials.NewStaticV4(creds.accessKey, creds.secretKey, ""),
|
|
Secure: dconfig.UseSSL,
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
fs := s3.NewS3FS(mc, dconfig.S3Cache)
|
|
server, err := sftp.NewServer(ctx, channel, &fs)
|
|
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if err := server.Serve(); err == io.EOF {
|
|
server.Close()
|
|
log.Print("sftp client exited session.")
|
|
} else if err != nil {
|
|
log.Print("sftp server completed with error:", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func httpServer(config *Config, done chan error) {
|
|
// Assemble components to handle WebDAV requests
|
|
http.Handle(config.DavPath+"/",
|
|
BasicAuthExtract{
|
|
OnNotFound: NotAuthorized{},
|
|
OnCreds: LdapPreAuth{
|
|
WithConfig: config,
|
|
OnWrongPassword: NotAuthorized{},
|
|
OnFailure: InternalError{},
|
|
OnCreds: S3Auth{
|
|
WithConfig: config,
|
|
OnFailure: InternalError{},
|
|
OnMinioClient: WebDav{
|
|
WithConfig: config,
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
if err := http.ListenAndServe(config.HttpListen, nil); err != nil {
|
|
done <- fmt.Errorf("Error with WebDAV server: %v", err)
|
|
} else {
|
|
done <- nil
|
|
}
|
|
}
|