blob: 4f0bf16e21d0960b7ab4c950cd2f4dc7b525d811 [file] [log] [blame]
giolekva8aa73e82022-07-09 11:34:39 +04001package soft
2
3import (
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +04004 "errors"
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"
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +04009 "net/netip"
giolekva8aa73e82022-07-09 11:34:39 +040010 "os"
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040011 "regexp"
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040012 "strings"
giolekva8aa73e82022-07-09 11:34:39 +040013
14 "github.com/go-git/go-billy/v5/memfs"
15 "github.com/go-git/go-git/v5"
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +040016 "github.com/go-git/go-git/v5/plumbing/transport"
giolekva8aa73e82022-07-09 11:34:39 +040017 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
18 "github.com/go-git/go-git/v5/storage/memory"
giolekva8aa73e82022-07-09 11:34:39 +040019)
20
21type Client struct {
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040022 Addr netip.AddrPort
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040023 Signer ssh.Signer
24 log *log.Logger
25 pemBytes []byte
giolekva8aa73e82022-07-09 11:34:39 +040026}
27
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040028func NewClient(addr netip.AddrPort, clientPrivateKey []byte, log *log.Logger) (*Client, error) {
giolekva8aa73e82022-07-09 11:34:39 +040029 signer, err := ssh.ParsePrivateKey(clientPrivateKey)
30 if err != nil {
31 return nil, err
32 }
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040033 log.SetPrefix("SOFT-SERVE: ")
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040034 log.Printf("Created signer")
giolekva8aa73e82022-07-09 11:34:39 +040035 return &Client{
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040036 addr,
giolekva8aa73e82022-07-09 11:34:39 +040037 signer,
38 log,
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040039 clientPrivateKey,
giolekva8aa73e82022-07-09 11:34:39 +040040 }, nil
41}
42
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040043func (ss *Client) AddUser(name, pubKey string) error {
44 log.Printf("Adding user %s", name)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040045 if err := ss.RunCommand("user", "create", name); err != nil {
giolekva8aa73e82022-07-09 11:34:39 +040046 return err
47 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040048 return ss.AddPublicKey(name, pubKey)
giolekva8aa73e82022-07-09 11:34:39 +040049}
50
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040051func (ss *Client) MakeUserAdmin(name string) error {
52 log.Printf("Making user %s admin", name)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040053 return ss.RunCommand("user", "set-admin", name, "true")
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040054}
55
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040056func (ss *Client) AddPublicKey(user string, pubKey string) error {
57 log.Printf("Adding public key: %s %s\n", user, pubKey)
58 return ss.RunCommand("user", "add-pubkey", user, pubKey)
59}
60
61func (ss *Client) RemovePublicKey(user string, pubKey string) error {
62 log.Printf("Adding public key: %s %s\n", user, pubKey)
63 return ss.RunCommand("user", "remove-pubkey", user, pubKey)
64}
65
66func (ss *Client) RunCommand(args ...string) error {
67 cmd := strings.Join(args, " ")
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040068 log.Printf("Running command %s", cmd)
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040069 client, err := ssh.Dial("tcp", ss.Addr.String(), ss.sshClientConfig())
giolekva8aa73e82022-07-09 11:34:39 +040070 if err != nil {
71 return err
72 }
73 session, err := client.NewSession()
74 if err != nil {
75 return err
76 }
77 defer session.Close()
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040078 session.Stdout = os.Stdout
79 session.Stderr = os.Stderr
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040080 return session.Run(cmd)
giolekva8aa73e82022-07-09 11:34:39 +040081}
82
83func (ss *Client) AddRepository(name, readme string) error {
84 log.Printf("Adding repository %s", name)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040085 return ss.RunCommand("repo", "create", name, "-d", fmt.Sprintf("\"%s\"", readme))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +040086}
87
88func (ss *Client) AddCollaborator(repo, user string) error {
89 log.Printf("Adding collaborator %s %s", repo, user)
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +040090 return ss.RunCommand("repo", "collab", "add", repo, user)
giolekva8aa73e82022-07-09 11:34:39 +040091}
92
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +040093type Repository struct {
94 *git.Repository
95 Addr RepositoryAddress
96}
97
98func (ss *Client) GetRepo(name string) (*Repository, error) {
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +040099 return CloneRepository(RepositoryAddress{ss.Addr, name}, ss.Signer)
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400100}
101
102type RepositoryAddress struct {
103 Addr netip.AddrPort
104 Name string
105}
106
107func ParseRepositoryAddress(addr string) (RepositoryAddress, error) {
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400108 items := regexp.MustCompile(`ssh://(.*)/(.*)`).FindStringSubmatch(addr)
109 if len(items) != 3 {
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400110 return RepositoryAddress{}, fmt.Errorf("Invalid address")
111 }
112 ipPort, err := netip.ParseAddrPort(items[1])
113 if err != nil {
114 return RepositoryAddress{}, err
115 }
116 return RepositoryAddress{ipPort, items[2]}, nil
117}
118
119func (r RepositoryAddress) FullAddress() string {
120 return fmt.Sprintf("ssh://%s/%s", r.Addr, r.Name)
121}
122
Giorgi Lekveishvili57dffb32023-08-07 15:45:43 +0400123func CloneRepository(addr RepositoryAddress, signer ssh.Signer) (*Repository, error) {
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400124 c, err := git.Clone(memory.NewStorage(), memfs.New(), &git.CloneOptions{
125 URL: addr.FullAddress(),
126 Auth: &gitssh.PublicKeys{
127 User: "git",
128 Signer: signer,
129 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
130 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
131 // TODO(giolekva): verify server public key
132 fmt.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
133 return nil
134 },
135 },
136 },
Giorgi Lekveishvili87be4ae2023-06-11 23:41:09 +0400137 RemoteName: "origin",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400138 ReferenceName: "refs/heads/master",
139 Depth: 1,
140 InsecureSkipTLS: true,
141 Progress: os.Stdout,
142 })
Giorgi Lekveishvilia1e77902023-11-06 14:48:27 +0400143 if err != nil && !errors.Is(err, transport.ErrEmptyRemoteRepository) {
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400144 return nil, err
145 }
146 return &Repository{
147 Repository: c,
148 Addr: addr,
149 }, nil
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400150}
151
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400152// TODO(giolekva): dead code
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400153func (ss *Client) authSSH() gitssh.AuthMethod {
154 a, err := gitssh.NewPublicKeys("git", ss.pemBytes, "")
giolekva8aa73e82022-07-09 11:34:39 +0400155 if err != nil {
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400156 panic(err)
giolekva8aa73e82022-07-09 11:34:39 +0400157 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400158 a.HostKeyCallback = func(hostname string, remote net.Addr, key ssh.PublicKey) error {
159 // TODO(giolekva): verify server public key
160 ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
161 return nil
giolekva8aa73e82022-07-09 11:34:39 +0400162 }
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400163 return a
164 // return &gitssh.PublicKeys{
165 // User: "git",
166 // Signer: ss.Signer,
167 // HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
168 // HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
169 // // TODO(giolekva): verify server public key
170 // ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
171 // return nil
172 // },
173 // },
174 // }
giolekva8aa73e82022-07-09 11:34:39 +0400175}
176
177func (ss *Client) authGit() *gitssh.PublicKeys {
178 return &gitssh.PublicKeys{
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400179 User: "git",
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400180 Signer: ss.Signer,
giolekva8aa73e82022-07-09 11:34:39 +0400181 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
182 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
183 // TODO(giolekva): verify server public key
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400184 ss.log.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400185 return nil
186 },
187 },
188 }
189}
190
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400191func (ss *Client) GetPublicKey() ([]byte, error) {
192 var ret []byte
193 config := &ssh.ClientConfig{
194 Auth: []ssh.AuthMethod{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400195 ssh.PublicKeys(ss.Signer),
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400196 },
197 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
198 ret = ssh.MarshalAuthorizedKey(key)
199 return nil
200 },
201 }
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400202 _, err := ssh.Dial("tcp", ss.Addr.String(), config)
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400203 if err != nil {
204 return nil, err
205 }
206 return ret, nil
207}
208
giolekva8aa73e82022-07-09 11:34:39 +0400209func (ss *Client) sshClientConfig() *ssh.ClientConfig {
210 return &ssh.ClientConfig{
211 Auth: []ssh.AuthMethod{
Giorgi Lekveishvili3550b432023-06-09 19:37:51 +0400212 ssh.PublicKeys(ss.Signer),
giolekva8aa73e82022-07-09 11:34:39 +0400213 },
214 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
215 // TODO(giolekva): verify server public key
216 // fmt.Printf("## %s || %s -- \n", serverPubKey, ssh.MarshalAuthorizedKey(key))
Giorgi Lekveishvili23ef7f82023-05-26 11:57:48 +0400217 fmt.Printf("%s %s %s", hostname, remote, ssh.MarshalAuthorizedKey(key))
giolekva8aa73e82022-07-09 11:34:39 +0400218 return nil
219 },
220 }
221}
222
Giorgi Lekveishvili0ccd1482023-06-21 15:02:24 +0400223func (ss *Client) GetRepoAddress(name string) string {
224 return fmt.Sprintf("%s/%s", ss.addressGit(), name)
225}
226
giolekva8aa73e82022-07-09 11:34:39 +0400227func (ss *Client) addressGit() string {
Giorgi Lekveishvili94cda9d2023-07-20 10:16:09 +0400228 return fmt.Sprintf("ssh://%s", ss.Addr)
giolekva8aa73e82022-07-09 11:34:39 +0400229}