blob: 59e94a879d8820f54462f335e731b7fb5b0f97e4 [file] [log] [blame]
package soft
import (
"encoding/base64"
"fmt"
"golang.org/x/crypto/ssh"
"log"
"net"
"os"
"strings"
"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-git/v5"
gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/go-git/go-git/v5/storage/memory"
)
type Client struct {
IP string
port int
Signer ssh.Signer
log *log.Logger
pemBytes []byte
}
func NewClient(ip string, port int, clientPrivateKey []byte, log *log.Logger) (*Client, error) {
signer, err := ssh.ParsePrivateKey(clientPrivateKey)
if err != nil {
return nil, err
}
log.SetPrefix("SOFT-SERVE: ")
log.Printf("Created signer")
pub := signer.PublicKey().Marshal()
b := make([]byte, 100)
base64.StdEncoding.Encode(b, pub)
log.Printf("%s\n", string(b))
return &Client{
ip,
port,
signer,
log,
clientPrivateKey,
}, nil
}
func (ss *Client) AddUser(name, pubKey string) error {
log.Printf("Adding user %s", name)
if err := ss.RunCommand("user", "create", name); err != nil {
return err
}
return ss.AddPublicKey(name, pubKey)
}
func (ss *Client) MakeUserAdmin(name string) error {
log.Printf("Making user %s admin", name)
return ss.RunCommand("user", "set-admin", name, "true")
}
func (ss *Client) AddPublicKey(user string, pubKey string) error {
log.Printf("Adding public key: %s %s\n", user, pubKey)
return ss.RunCommand("user", "add-pubkey", user, pubKey)
}
func (ss *Client) RemovePublicKey(user string, pubKey string) error {
log.Printf("Adding public key: %s %s\n", user, pubKey)
return ss.RunCommand("user", "remove-pubkey", user, pubKey)
}
func (ss *Client) RunCommand(args ...string) error {
cmd := strings.Join(args, " ")
log.Printf("Running command %s", cmd)
client, err := ssh.Dial("tcp", ss.addressSSH(), ss.sshClientConfig())
if err != nil {
return err
}
session, err := client.NewSession()
if err != nil {
return err
}
defer session.Close()
session.Stdout = os.Stdout
session.Stderr = os.Stderr
return session.Run(cmd)
}
func (ss *Client) AddRepository(name, readme string) error {
log.Printf("Adding repository %s", name)
return ss.RunCommand("repo", "create", name, "-d", fmt.Sprintf("\"%s\"", readme))
}
func (ss *Client) AddCollaborator(repo, user string) error {
log.Printf("Adding collaborator %s %s", repo, user)
return ss.RunCommand("repo", "collab", "add", repo, user)
}
func (ss *Client) GetRepo(name string) (*git.Repository, error) {
return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
URL: ss.GetRepoAddress(name),
Auth: ss.authSSH(),
RemoteName: "origin",
ReferenceName: "refs/heads/master",
Depth: 1,
InsecureSkipTLS: true,
Progress: os.Stdout,
})
}
func (ss *Client) authSSH() gitssh.AuthMethod {
a, err := gitssh.NewPublicKeys("git", ss.pemBytes, "")
if err != nil {
panic(err)
}
a.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// TODO(giolekva): verify server public key
ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
return nil
}
return a
// return &gitssh.PublicKeys{
// User: "git",
// Signer: ss.Signer,
// HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
// HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// // TODO(giolekva): verify server public key
// ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
// return nil
// },
// },
// }
}
func (ss *Client) authGit() *gitssh.PublicKeys {
return &gitssh.PublicKeys{
User: "git",
Signer: ss.Signer,
HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// TODO(giolekva): verify server public key
ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
return nil
},
},
}
}
func (ss *Client) GetPublicKey() ([]byte, error) {
var ret []byte
config := &ssh.ClientConfig{
Auth: []ssh.AuthMethod{
ssh.PublicKeys(ss.Signer),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
ret = ssh.MarshalAuthorizedKey(key)
return nil
},
}
_, err := ssh.Dial("tcp", ss.addressSSH(), config)
if err != nil {
return nil, err
}
return ret, nil
}
func (ss *Client) sshClientConfig() *ssh.ClientConfig {
return &ssh.ClientConfig{
Auth: []ssh.AuthMethod{
ssh.PublicKeys(ss.Signer),
},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// TODO(giolekva): verify server public key
// fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
fmt.Printf("%s %s %s", hostname, remote, ssh.MarshalAuthorizedKey(key))
return nil
},
}
}
func (ss *Client) GetRepoAddress(name string) string {
return fmt.Sprintf("%s/%s", ss.addressGit(), name)
}
func (ss *Client) addressGit() string {
return fmt.Sprintf("ssh://%s", ss.addressSSH())
}
func (ss *Client) addressSSH() string {
return fmt.Sprintf("%s:%d", ss.IP, ss.port)
}