blob: 59e94a879d8820f54462f335e731b7fb5b0f97e4 [file] [log] [blame]
giolekva8aa73e82022-07-09 11:34:39 +04001package soft
2
3import (
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04004 "encoding/base64"
giolekva8aa73e82022-07-09 11:34:39 +04005 "fmt"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04006 "golang.org/x/crypto/ssh"
giolekva8aa73e82022-07-09 11:34:39 +04007 "log"
8 "net"
9 "os"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040010 "strings"
giolekva8aa73e82022-07-09 11:34:39 +040011
12 "github.com/go-git/go-billy/v5/memfs"
13 "github.com/go-git/go-git/v5"
giolekva8aa73e82022-07-09 11:34:39 +040014 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
15 "github.com/go-git/go-git/v5/storage/memory"
giolekva8aa73e82022-07-09 11:34:39 +040016)
17
18type Client struct {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040019 IP string
20 port int
21 Signer ssh.Signer
22 log *log.Logger
23 pemBytes []byte
giolekva8aa73e82022-07-09 11:34:39 +040024}
25
26func NewClient(ip string, port int, clientPrivateKey []byte, log *log.Logger) (*Client, error) {
27 signer, err := ssh.ParsePrivateKey(clientPrivateKey)
28 if err != nil {
29 return nil, err
30 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040031 log.SetPrefix("SOFT-SERVE: ")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040032 log.Printf("Created signer")
33 pub := signer.PublicKey().Marshal()
34 b := make([]byte, 100)
35 base64.StdEncoding.Encode(b, pub)
36 log.Printf("%s\n", string(b))
giolekva8aa73e82022-07-09 11:34:39 +040037 return &Client{
38 ip,
39 port,
40 signer,
41 log,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040042 clientPrivateKey,
giolekva8aa73e82022-07-09 11:34:39 +040043 }, nil
44}
45
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040046func (ss *Client) AddUser(name, pubKey string) error {
47 log.Printf("Adding user %s", name)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040048 if err := ss.RunCommand("user", "create", name); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +040049 return err
50 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040051 return ss.AddPublicKey(name, pubKey)
giolekva8aa73e82022-07-09 11:34:39 +040052}
53
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040054func (ss *Client) MakeUserAdmin(name string) error {
55 log.Printf("Making user %s admin", name)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040056 return ss.RunCommand("user", "set-admin", name, "true")
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040057}
58
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040059func (ss *Client) AddPublicKey(user string, pubKey string) error {
60 log.Printf("Adding public key: %s %s\n", user, pubKey)
61 return ss.RunCommand("user", "add-pubkey", user, pubKey)
62}
63
64func (ss *Client) RemovePublicKey(user string, pubKey string) error {
65 log.Printf("Adding public key: %s %s\n", user, pubKey)
66 return ss.RunCommand("user", "remove-pubkey", user, pubKey)
67}
68
69func (ss *Client) RunCommand(args ...string) error {
70 cmd := strings.Join(args, " ")
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040071 log.Printf("Running command %s", cmd)
giolekva8aa73e82022-07-09 11:34:39 +040072 client, err := ssh.Dial("tcp", ss.addressSSH(), ss.sshClientConfig())
73 if err != nil {
74 return err
75 }
76 session, err := client.NewSession()
77 if err != nil {
78 return err
79 }
80 defer session.Close()
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040081 session.Stdout = os.Stdout
82 session.Stderr = os.Stderr
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040083 return session.Run(cmd)
giolekva8aa73e82022-07-09 11:34:39 +040084}
85
86func (ss *Client) AddRepository(name, readme string) error {
87 log.Printf("Adding repository %s", name)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040088 return ss.RunCommand("repo", "create", name, "-d", fmt.Sprintf("\"%s\"", readme))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040089}
90
91func (ss *Client) AddCollaborator(repo, user string) error {
92 log.Printf("Adding collaborator %s %s", repo, user)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040093 return ss.RunCommand("repo", "collab", "add", repo, user)
giolekva8aa73e82022-07-09 11:34:39 +040094}
95
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040096func (ss *Client) GetRepo(name string) (*git.Repository, error) {
97 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040098 URL: ss.GetRepoAddress(name),
99 Auth: ss.authSSH(),
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400100 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400101 ReferenceName: "refs/heads/master",
102 Depth: 1,
103 InsecureSkipTLS: true,
104 Progress: os.Stdout,
105 })
106}
107
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400108func (ss *Client) authSSH() gitssh.AuthMethod {
109 a, err := gitssh.NewPublicKeys("git", ss.pemBytes, "")
giolekva8aa73e82022-07-09 11:34:39 +0400110 if err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400111 panic(err)
giolekva8aa73e82022-07-09 11:34:39 +0400112 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400113 a.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
114 // TODO(giolekva): verify server public key
115 ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
116 return nil
giolekva8aa73e82022-07-09 11:34:39 +0400117 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400118 return a
119 // return &gitssh.PublicKeys{
120 // User: "git",
121 // Signer: ss.Signer,
122 // HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
123 // HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
124 // // TODO(giolekva): verify server public key
125 // ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
126 // return nil
127 // },
128 // },
129 // }
giolekva8aa73e82022-07-09 11:34:39 +0400130}
131
132func (ss *Client) authGit() *gitssh.PublicKeys {
133 return &gitssh.PublicKeys{
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400134 User: "git",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400135 Signer: ss.Signer,
giolekva8aa73e82022-07-09 11:34:39 +0400136 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
137 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
138 // TODO(giolekva): verify server public key
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400139 ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400140 return nil
141 },
142 },
143 }
144}
145
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400146func (ss *Client) GetPublicKey() ([]byte, error) {
147 var ret []byte
148 config := &ssh.ClientConfig{
149 Auth: []ssh.AuthMethod{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400150 ssh.PublicKeys(ss.Signer),
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400151 },
152 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
153 ret = ssh.MarshalAuthorizedKey(key)
154 return nil
155 },
156 }
157 _, err := ssh.Dial("tcp", ss.addressSSH(), config)
158 if err != nil {
159 return nil, err
160 }
161 return ret, nil
162}
163
giolekva8aa73e82022-07-09 11:34:39 +0400164func (ss *Client) sshClientConfig() *ssh.ClientConfig {
165 return &ssh.ClientConfig{
166 Auth: []ssh.AuthMethod{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400167 ssh.PublicKeys(ss.Signer),
giolekva8aa73e82022-07-09 11:34:39 +0400168 },
169 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
170 // TODO(giolekva): verify server public key
171 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400172 fmt.Printf("%s %s %s", hostname, remote, ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400173 return nil
174 },
175 }
176}
177
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400178func (ss *Client) GetRepoAddress(name string) string {
179 return fmt.Sprintf("%s/%s", ss.addressGit(), name)
180}
181
giolekva8aa73e82022-07-09 11:34:39 +0400182func (ss *Client) addressGit() string {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400183 return fmt.Sprintf("ssh://%s", ss.addressSSH())
giolekva8aa73e82022-07-09 11:34:39 +0400184}
185
186func (ss *Client) addressSSH() string {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400187 return fmt.Sprintf("%s:%d", ss.IP, ss.port)
giolekva8aa73e82022-07-09 11:34:39 +0400188}