blob: 2a820a0f4a582724786cbc1dd369cc2e52cdce05 [file] [log] [blame]
giolekva8aa73e82022-07-09 11:34:39 +04001package soft
2
3import (
4 "fmt"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04005 "golang.org/x/crypto/ssh"
giolekva8aa73e82022-07-09 11:34:39 +04006 "log"
7 "net"
8 "os"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +04009 "strings"
giolekva8aa73e82022-07-09 11:34:39 +040010
11 "github.com/go-git/go-billy/v5/memfs"
12 "github.com/go-git/go-git/v5"
giolekva8aa73e82022-07-09 11:34:39 +040013 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
14 "github.com/go-git/go-git/v5/storage/memory"
giolekva8aa73e82022-07-09 11:34:39 +040015)
16
17type Client struct {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040018 IP string
19 port int
20 Signer ssh.Signer
21 log *log.Logger
22 pemBytes []byte
giolekva8aa73e82022-07-09 11:34:39 +040023}
24
25func NewClient(ip string, port int, clientPrivateKey []byte, log *log.Logger) (*Client, error) {
26 signer, err := ssh.ParsePrivateKey(clientPrivateKey)
27 if err != nil {
28 return nil, err
29 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040030 log.SetPrefix("SOFT-SERVE: ")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040031 log.Printf("Created signer")
giolekva8aa73e82022-07-09 11:34:39 +040032 return &Client{
33 ip,
34 port,
35 signer,
36 log,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040037 clientPrivateKey,
giolekva8aa73e82022-07-09 11:34:39 +040038 }, nil
39}
40
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040041func (ss *Client) AddUser(name, pubKey string) error {
42 log.Printf("Adding user %s", name)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040043 if err := ss.RunCommand("user", "create", name); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +040044 return err
45 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040046 return ss.AddPublicKey(name, pubKey)
giolekva8aa73e82022-07-09 11:34:39 +040047}
48
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040049func (ss *Client) MakeUserAdmin(name string) error {
50 log.Printf("Making user %s admin", name)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040051 return ss.RunCommand("user", "set-admin", name, "true")
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040052}
53
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040054func (ss *Client) AddPublicKey(user string, pubKey string) error {
55 log.Printf("Adding public key: %s %s\n", user, pubKey)
56 return ss.RunCommand("user", "add-pubkey", user, pubKey)
57}
58
59func (ss *Client) RemovePublicKey(user string, pubKey string) error {
60 log.Printf("Adding public key: %s %s\n", user, pubKey)
61 return ss.RunCommand("user", "remove-pubkey", user, pubKey)
62}
63
64func (ss *Client) RunCommand(args ...string) error {
65 cmd := strings.Join(args, " ")
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040066 log.Printf("Running command %s", cmd)
giolekva8aa73e82022-07-09 11:34:39 +040067 client, err := ssh.Dial("tcp", ss.addressSSH(), ss.sshClientConfig())
68 if err != nil {
69 return err
70 }
71 session, err := client.NewSession()
72 if err != nil {
73 return err
74 }
75 defer session.Close()
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040076 session.Stdout = os.Stdout
77 session.Stderr = os.Stderr
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040078 return session.Run(cmd)
giolekva8aa73e82022-07-09 11:34:39 +040079}
80
81func (ss *Client) AddRepository(name, readme string) error {
82 log.Printf("Adding repository %s", name)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040083 return ss.RunCommand("repo", "create", name, "-d", fmt.Sprintf("\"%s\"", readme))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040084}
85
86func (ss *Client) AddCollaborator(repo, user string) error {
87 log.Printf("Adding collaborator %s %s", repo, user)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040088 return ss.RunCommand("repo", "collab", "add", repo, user)
giolekva8aa73e82022-07-09 11:34:39 +040089}
90
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040091func (ss *Client) GetRepo(name string) (*git.Repository, error) {
92 return git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040093 URL: ss.GetRepoAddress(name),
94 Auth: ss.authSSH(),
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +040095 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +040096 ReferenceName: "refs/heads/master",
97 Depth: 1,
98 InsecureSkipTLS: true,
99 Progress: os.Stdout,
100 })
101}
102
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400103func (ss *Client) authSSH() gitssh.AuthMethod {
104 a, err := gitssh.NewPublicKeys("git", ss.pemBytes, "")
giolekva8aa73e82022-07-09 11:34:39 +0400105 if err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400106 panic(err)
giolekva8aa73e82022-07-09 11:34:39 +0400107 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400108 a.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
109 // TODO(giolekva): verify server public key
110 ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
111 return nil
giolekva8aa73e82022-07-09 11:34:39 +0400112 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400113 return a
114 // return &gitssh.PublicKeys{
115 // User: "git",
116 // Signer: ss.Signer,
117 // HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
118 // HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
119 // // TODO(giolekva): verify server public key
120 // ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
121 // return nil
122 // },
123 // },
124 // }
giolekva8aa73e82022-07-09 11:34:39 +0400125}
126
127func (ss *Client) authGit() *gitssh.PublicKeys {
128 return &gitssh.PublicKeys{
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400129 User: "git",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400130 Signer: ss.Signer,
giolekva8aa73e82022-07-09 11:34:39 +0400131 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
132 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
133 // TODO(giolekva): verify server public key
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400134 ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400135 return nil
136 },
137 },
138 }
139}
140
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400141func (ss *Client) GetPublicKey() ([]byte, error) {
142 var ret []byte
143 config := &ssh.ClientConfig{
144 Auth: []ssh.AuthMethod{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400145 ssh.PublicKeys(ss.Signer),
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400146 },
147 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
148 ret = ssh.MarshalAuthorizedKey(key)
149 return nil
150 },
151 }
152 _, err := ssh.Dial("tcp", ss.addressSSH(), config)
153 if err != nil {
154 return nil, err
155 }
156 return ret, nil
157}
158
giolekva8aa73e82022-07-09 11:34:39 +0400159func (ss *Client) sshClientConfig() *ssh.ClientConfig {
160 return &ssh.ClientConfig{
161 Auth: []ssh.AuthMethod{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400162 ssh.PublicKeys(ss.Signer),
giolekva8aa73e82022-07-09 11:34:39 +0400163 },
164 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
165 // TODO(giolekva): verify server public key
166 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400167 fmt.Printf("%s %s %s", hostname, remote, ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400168 return nil
169 },
170 }
171}
172
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400173func (ss *Client) GetRepoAddress(name string) string {
174 return fmt.Sprintf("%s/%s", ss.addressGit(), name)
175}
176
giolekva8aa73e82022-07-09 11:34:39 +0400177func (ss *Client) addressGit() string {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400178 return fmt.Sprintf("ssh://%s", ss.addressSSH())
giolekva8aa73e82022-07-09 11:34:39 +0400179}
180
181func (ss *Client) addressSSH() string {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400182 return fmt.Sprintf("%s:%d", ss.IP, ss.port)
giolekva8aa73e82022-07-09 11:34:39 +0400183}