diff --git a/cmd/upterm/command/host.go b/cmd/upterm/command/host.go index 14ef5c0e8..ae983a32a 100644 --- a/cmd/upterm/command/host.go +++ b/cmd/upterm/command/host.go @@ -18,7 +18,6 @@ import ( "github.com/owenthereal/upterm/utils" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh" ) var ( @@ -74,7 +73,7 @@ private key. To authorize client connections, specify a authorized_key file with cmd.PersistentFlags().StringSliceVarP(&flagPrivateKeys, "private-key", "i", defaultPrivateKeys(homeDir), "Specify private key files for public key authentication with the upterm server (required).") cmd.PersistentFlags().StringVarP(&flagKnownHostsFilename, "known-hosts", "", defaultKnownHost(homeDir), "Specify a file containing known keys for remote hosts (required).") cmd.PersistentFlags().StringVar(&flagAuthorizedKeys, "authorized-keys", "", "Specify a authorize_keys file listing authorized public keys for connection.") - cmd.PersistentFlags().StringSliceVar(&flagGitHubUsers, "github-user", nil, "Authorize specified GitHub users by allowing their public keys to connect.") + cmd.PersistentFlags().StringSliceVar(&flagGitHubUsers, "github-user", nil, "Authorize specified GitHub users by allowing their public keys to connect. Configure GitHub CLI environment variables as needed; see https://cli.github.com/manual/gh_help_environment for details.") cmd.PersistentFlags().StringSliceVar(&flagGitLabUsers, "gitlab-user", nil, "Authorize specified GitLab users by allowing their public keys to connect.") cmd.PersistentFlags().StringSliceVar(&flagSourceHutUsers, "srht-user", nil, "Authorize specified SourceHut users by allowing their public keys to connect.") cmd.PersistentFlags().BoolVar(&flagAccept, "accept", false, "Automatically accept client connections without prompts.") @@ -142,29 +141,39 @@ func shareRunE(c *cobra.Command, args []string) error { } } - var authorizedKeys []ssh.PublicKey - if flagAuthorizedKeys != "" { - authorizedKeys, err = host.AuthorizedKeys(flagAuthorizedKeys) - } + lf, err := utils.OpenHostLogFile() if err != nil { - return fmt.Errorf("error reading authorized keys: %w", err) + return err + } + defer lf.Close() + + logger := log.New() + logger.SetOutput(lf) + + var authorizedKeys []*host.AuthorizedKey + if flagAuthorizedKeys != "" { + aks, err := host.AuthorizedKeysFromFile(flagAuthorizedKeys) + if err != nil { + return fmt.Errorf("error reading authorized keys: %w", err) + } + authorizedKeys = append(authorizedKeys, aks) } if flagGitHubUsers != nil { - gitHubUserKeys, err := host.GitHubUserKeys(flagGitHubUsers) + gitHubUserKeys, err := host.GitHubUserAuthorizedKeys(flagGitHubUsers, logger) if err != nil { return fmt.Errorf("error reading GitHub user keys: %w", err) } authorizedKeys = append(authorizedKeys, gitHubUserKeys...) } if flagGitLabUsers != nil { - gitLabUserKeys, err := host.GitLabUserKeys(flagGitLabUsers) + gitLabUserKeys, err := host.GitLabUserAuthorizedKeys(flagGitLabUsers) if err != nil { return fmt.Errorf("error reading GitLab user keys: %w", err) } authorizedKeys = append(authorizedKeys, gitLabUserKeys...) } if flagSourceHutUsers != nil { - sourceHutUserKeys, err := host.SourceHutUserKeys(flagSourceHutUsers) + sourceHutUserKeys, err := host.SourceHutUserAuthorizedKeys(flagSourceHutUsers) if err != nil { return fmt.Errorf("error reading SourceHut user keys: %w", err) } @@ -184,15 +193,6 @@ func shareRunE(c *cobra.Command, args []string) error { return err } - lf, err := utils.OpenHostLogFile() - if err != nil { - return err - } - defer lf.Close() - - logger := log.New() - logger.SetOutput(lf) - h := &host.Host{ Host: flagServer, Command: args, diff --git a/cmd/upterm/command/session.go b/cmd/upterm/command/session.go index 20f6695b8..da8825802 100644 --- a/cmd/upterm/command/session.go +++ b/cmd/upterm/command/session.go @@ -237,6 +237,7 @@ func displaySession(session *api.GetSessionResponse) error { {"Command:", strings.Join(session.Command, " ")}, {"Force Command:", naIfEmpty(strings.Join(session.ForceCommand, " "))}, {"Host:", u.Scheme + "://" + hostPort}, + {"Authorized Keys:", naIfEmpty(displayAuthorizedKeys(session.AuthorizedKeys))}, {"SSH Session:", sshCmd}, } @@ -297,6 +298,19 @@ func validateCurrentRequiredFlags(c *cobra.Command, args []string) error { return nil } +func displayAuthorizedKeys(keys []*api.AuthorizedKey) string { + var aks []string + for _, ak := range keys { + var fps []string + for _, fp := range ak.PublicKeyFingerprints { + fps = append(fps, fmt.Sprintf("- %s", fp)) + } + aks = append(aks, fmt.Sprintf("%s:\n%s", ak.Comment, strings.Join(fps, "\n"))) + } + + return strings.Join(aks, "\n") +} + func naIfEmpty(s string) string { if s == "" { return "n/a" diff --git a/ftests/ftests_test.go b/ftests/ftests_test.go index 5e91959fc..0aadd8866 100644 --- a/ftests/ftests_test.go +++ b/ftests/ftests_test.go @@ -285,13 +285,13 @@ func (c *Host) Share(url string) error { } // permit client public key - var authorizedKeys []ssh.PublicKey + var authorizedKeys []*host.AuthorizedKey if c.PermittedClientPublicKey != "" { pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(c.PermittedClientPublicKey)) if err != nil { return err } - authorizedKeys = append(authorizedKeys, pk) + authorizedKeys = append(authorizedKeys, &host.AuthorizedKey{PublicKeys: []ssh.PublicKey{pk}}) } if c.AdminSocketFile == "" { diff --git a/go.mod b/go.mod index fd407b72c..b60b1f4a3 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/go-kit/kit v0.13.0 github.com/google/go-cmp v0.5.9 github.com/google/go-querystring v1.1.0 // indirect - github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/gorilla/websocket v1.5.0 github.com/gosuri/uilive v0.0.4 // indirect github.com/gosuri/uiprogress v0.0.1 // indirect @@ -30,7 +30,7 @@ require ( github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // indirect github.com/jpillora/chisel v1.9.1 github.com/klauspost/pgzip v1.2.5 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/oklog/run v1.1.1-0.20200508094559-c7096881717e github.com/olebedev/emitter v0.0.0-20190110104742-e8d1457e6aee github.com/olekukonko/tablewriter v0.0.5 @@ -48,6 +48,7 @@ require ( ) require ( + github.com/cli/go-gh/v2 v2.3.0 github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 github.com/google/go-github/v48 v48.2.0 github.com/stretchr/testify v1.8.4 @@ -57,8 +58,11 @@ require ( require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 // indirect + github.com/aymanbagabas/go-osc52 v1.0.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cli/safeexec v1.0.0 // indirect + github.com/cli/shurcooL-graphql v0.0.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -68,21 +72,26 @@ require ( github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/henvic/httpretty v0.0.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jpillora/sizestr v1.0.0 // indirect github.com/klauspost/compress v1.14.4 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/muesli/termenv v0.13.0 // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/rollbar/rollbar-go v1.0.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect + github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect diff --git a/go.sum b/go.sum index 60e4c9177..0848e4876 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= +github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8= @@ -26,6 +28,12 @@ github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c h1:aprLqMn7gSPT+vd github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c/go.mod h1:Ie6SubJv/NTO9Q0UBH0QCl3Ve50lu9hjbi5YJUw03TE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cli/go-gh/v2 v2.3.0 h1:FAQAP4PaWSAJf4VSxFEIYDQ1oBIs+bKB4GXQAiRr2sQ= +github.com/cli/go-gh/v2 v2.3.0/go.mod h1:6WBUuf7LUVAc+eXYYX/nYTYURRc6M03K9cJNwBKvwT0= +github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= +github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= +github.com/cli/shurcooL-graphql v0.0.3 h1:CtpPxyGDs136/+ZeyAfUKYmcQBjDlq5aqnrDCW5Ghh8= +github.com/cli/shurcooL-graphql v0.0.3/go.mod h1:tlrLmw/n5Q/+4qSvosT+9/W5zc8ZMjnJeYBxSdb4nWA= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.19-0.20220421211855-0d412c9fbeb1 h1:Tw0uuY+3UWYiSbR0+wsrJ30vY3zMFZ4JNPkSp9XdFyA= @@ -71,8 +79,8 @@ github.com/google/go-github/v48 v48.2.0 h1:68puzySE6WqUY9KWmpOsDEQfDZsso98rT6pZc github.com/google/go-github/v48 v48.2.0/go.mod h1:dDlehKBDo850ZPvCTK0sEqTCVWcrGl2LcDiajkYi89Y= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= -github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -80,10 +88,14 @@ github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY= github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI= github.com/gosuri/uiprogress v0.0.1 h1:0kpv/XY/qTmFWl/SkaJykZXrBBzwwadmW8fRb7RJSxw= github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/henvic/httpretty v0.0.6 h1:JdzGzKZBajBfnvlMALXXMVQWxWMF/ofTy8C3/OSUTxs= +github.com/henvic/httpretty v0.0.6/go.mod h1:X38wLjWXHkXT7r2+uK8LjCMne9rsuNaBLJ+5cU2/Pmo= github.com/heroku/rollrus v0.2.0 h1:b3AgcXJKFJNUwbQOC2S69/+mxuTpe4laznem9VJdPEo= github.com/heroku/rollrus v0.2.0/go.mod h1:B3MwEcr9nmf4xj0Sr5l9eSht7wLKMa1C+9ajgAU79ek= github.com/hooklift/assert v0.1.0 h1:UZzFxx5dSb9aBtvMHTtnPuvFnBvcEhHTPb9+0+jpEjs= @@ -115,18 +127,24 @@ github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3x github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/oklog/run v1.1.1-0.20200508094559-c7096881717e h1:bxQ+jj+8fdl9112bovUjD/14jj/uboMqjyVoFkqrdGg= @@ -156,6 +174,9 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -192,6 +213,8 @@ github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af h1:6yITBqGTE2lEeTPG0 github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af/go.mod h1:4F09kP5F+am0jAwlQLddpoMDM+iewkxxt6nxUQ5nq5o= github.com/tg123/sshpiper.crypto v0.13.0-20230910 h1:2JLnLTMT6Mo1zhGC3IVaGbZBh7/SXOkVQkQlZgklfTo= github.com/tg123/sshpiper.crypto v0.13.0-20230910/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e h1:BuzhfgfWQbX0dWzYzT1zsORLnHRv3bcRcsaUk0VmXA8= +github.com/thlib/go-timezone-local v0.0.0-20210907160436-ef149e42d28e/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI= github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0= github.com/tj/assert v0.0.1/go.mod h1:lsg+GHQ0XplTcWKGxFLf/XPcPxWO8x2ut5jminoR2rA= github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk= @@ -216,6 +239,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= @@ -234,13 +258,16 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= @@ -277,6 +304,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/host/api/api.pb.go b/host/api/api.pb.go index 7d176d480..973d92d21 100644 --- a/host/api/api.pb.go +++ b/host/api/api.pb.go @@ -63,7 +63,7 @@ func (x Identifier_Type) Number() protoreflect.EnumNumber { // Deprecated: Use Identifier_Type.Descriptor instead. func (Identifier_Type) EnumDescriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{3, 0} + return file_api_proto_rawDescGZIP(), []int{4, 0} } type GetSessionRequest struct { @@ -109,12 +109,13 @@ type GetSessionResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` - Command []string `protobuf:"bytes,2,rep,name=command,proto3" json:"command,omitempty"` - ForceCommand []string `protobuf:"bytes,3,rep,name=force_command,json=forceCommand,proto3" json:"force_command,omitempty"` - Host string `protobuf:"bytes,4,opt,name=host,proto3" json:"host,omitempty"` - NodeAddr string `protobuf:"bytes,5,opt,name=node_addr,json=nodeAddr,proto3" json:"node_addr,omitempty"` - ConnectedClients []*Client `protobuf:"bytes,6,rep,name=connected_clients,json=connectedClients,proto3" json:"connected_clients,omitempty"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + Command []string `protobuf:"bytes,2,rep,name=command,proto3" json:"command,omitempty"` + ForceCommand []string `protobuf:"bytes,3,rep,name=force_command,json=forceCommand,proto3" json:"force_command,omitempty"` + Host string `protobuf:"bytes,4,opt,name=host,proto3" json:"host,omitempty"` + NodeAddr string `protobuf:"bytes,5,opt,name=node_addr,json=nodeAddr,proto3" json:"node_addr,omitempty"` + ConnectedClients []*Client `protobuf:"bytes,6,rep,name=connected_clients,json=connectedClients,proto3" json:"connected_clients,omitempty"` + AuthorizedKeys []*AuthorizedKey `protobuf:"bytes,7,rep,name=authorized_keys,json=authorizedKeys,proto3" json:"authorized_keys,omitempty"` } func (x *GetSessionResponse) Reset() { @@ -191,6 +192,68 @@ func (x *GetSessionResponse) GetConnectedClients() []*Client { return nil } +func (x *GetSessionResponse) GetAuthorizedKeys() []*AuthorizedKey { + if x != nil { + return x.AuthorizedKeys + } + return nil +} + +type AuthorizedKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PublicKeyFingerprints []string `protobuf:"bytes,1,rep,name=public_key_fingerprints,json=publicKeyFingerprints,proto3" json:"public_key_fingerprints,omitempty"` + Comment string `protobuf:"bytes,2,opt,name=comment,proto3" json:"comment,omitempty"` +} + +func (x *AuthorizedKey) Reset() { + *x = AuthorizedKey{} + if protoimpl.UnsafeEnabled { + mi := &file_api_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AuthorizedKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AuthorizedKey) ProtoMessage() {} + +func (x *AuthorizedKey) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AuthorizedKey.ProtoReflect.Descriptor instead. +func (*AuthorizedKey) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{2} +} + +func (x *AuthorizedKey) GetPublicKeyFingerprints() []string { + if x != nil { + return x.PublicKeyFingerprints + } + return nil +} + +func (x *AuthorizedKey) GetComment() string { + if x != nil { + return x.Comment + } + return "" +} + type Client struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -205,7 +268,7 @@ type Client struct { func (x *Client) Reset() { *x = Client{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[2] + mi := &file_api_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -218,7 +281,7 @@ func (x *Client) String() string { func (*Client) ProtoMessage() {} func (x *Client) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[2] + mi := &file_api_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -231,7 +294,7 @@ func (x *Client) ProtoReflect() protoreflect.Message { // Deprecated: Use Client.ProtoReflect.Descriptor instead. func (*Client) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{2} + return file_api_proto_rawDescGZIP(), []int{3} } func (x *Client) GetId() string { @@ -275,7 +338,7 @@ type Identifier struct { func (x *Identifier) Reset() { *x = Identifier{} if protoimpl.UnsafeEnabled { - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -288,7 +351,7 @@ func (x *Identifier) String() string { func (*Identifier) ProtoMessage() {} func (x *Identifier) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -301,7 +364,7 @@ func (x *Identifier) ProtoReflect() protoreflect.Message { // Deprecated: Use Identifier.ProtoReflect.Descriptor instead. func (*Identifier) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{3} + return file_api_proto_rawDescGZIP(), []int{4} } func (x *Identifier) GetId() string { @@ -330,7 +393,7 @@ var File_api_proto protoreflect.FileDescriptor var file_api_proto_rawDesc = []byte{ 0x0a, 0x09, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xdd, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x53, 0x65, 0x73, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9a, 0x02, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, @@ -344,7 +407,17 @@ var file_api_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x43, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x7c, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, + 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3b, 0x0a, 0x0f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x4b, + 0x65, 0x79, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x4b, 0x65, + 0x79, 0x73, 0x22, 0x61, 0x0a, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, + 0x4b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x17, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x46, + 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, + 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x7c, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, @@ -384,24 +457,26 @@ func file_api_proto_rawDescGZIP() []byte { } var file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_api_proto_goTypes = []interface{}{ (Identifier_Type)(0), // 0: api.Identifier.Type (*GetSessionRequest)(nil), // 1: api.GetSessionRequest (*GetSessionResponse)(nil), // 2: api.GetSessionResponse - (*Client)(nil), // 3: api.Client - (*Identifier)(nil), // 4: api.Identifier + (*AuthorizedKey)(nil), // 3: api.AuthorizedKey + (*Client)(nil), // 4: api.Client + (*Identifier)(nil), // 5: api.Identifier } var file_api_proto_depIdxs = []int32{ - 3, // 0: api.GetSessionResponse.connected_clients:type_name -> api.Client - 0, // 1: api.Identifier.type:type_name -> api.Identifier.Type - 1, // 2: api.AdminService.GetSession:input_type -> api.GetSessionRequest - 2, // 3: api.AdminService.GetSession:output_type -> api.GetSessionResponse - 3, // [3:4] is the sub-list for method output_type - 2, // [2:3] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 4, // 0: api.GetSessionResponse.connected_clients:type_name -> api.Client + 3, // 1: api.GetSessionResponse.authorized_keys:type_name -> api.AuthorizedKey + 0, // 2: api.Identifier.type:type_name -> api.Identifier.Type + 1, // 3: api.AdminService.GetSession:input_type -> api.GetSessionRequest + 2, // 4: api.AdminService.GetSession:output_type -> api.GetSessionResponse + 4, // [4:5] is the sub-list for method output_type + 3, // [3:4] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_api_proto_init() } @@ -435,7 +510,7 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Client); i { + switch v := v.(*AuthorizedKey); i { case 0: return &v.state case 1: @@ -447,6 +522,18 @@ func file_api_proto_init() { } } file_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Client); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Identifier); i { case 0: return &v.state @@ -465,7 +552,7 @@ func file_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_api_proto_rawDesc, NumEnums: 1, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/host/api/api.proto b/host/api/api.proto index b4987315f..bc4dd1d55 100644 --- a/host/api/api.proto +++ b/host/api/api.proto @@ -5,35 +5,40 @@ package api; option go_package = "github.com/owenthereal/upterm/host/api"; service AdminService { - rpc GetSession (GetSessionRequest) returns (GetSessionResponse) {} + rpc GetSession(GetSessionRequest) returns (GetSessionResponse) {} } -message GetSessionRequest { -} +message GetSessionRequest {} message GetSessionResponse { - string session_id = 1; - repeated string command = 2; - repeated string force_command = 3; - string host = 4; - string node_addr = 5; - repeated Client connected_clients = 6; + string session_id = 1; + repeated string command = 2; + repeated string force_command = 3; + string host = 4; + string node_addr = 5; + repeated Client connected_clients = 6; + repeated AuthorizedKey authorized_keys = 7; +} + +message AuthorizedKey { + repeated string public_key_fingerprints = 1; + string comment = 2; } message Client { - string id = 1; - string version = 2; - string addr = 3; - string public_key_fingerprint = 4; + string id = 1; + string version = 2; + string addr = 3; + string public_key_fingerprint = 4; } message Identifier { - string id = 1; - Type type = 2; - string node_addr = 3; - - enum Type { - HOST = 0; - CLIENT = 1; - } + string id = 1; + Type type = 2; + string node_addr = 3; + + enum Type { + HOST = 0; + CLIENT = 1; + } } diff --git a/host/authorizedkeys.go b/host/authorizedkeys.go new file mode 100644 index 000000000..51875c1fc --- /dev/null +++ b/host/authorizedkeys.go @@ -0,0 +1,153 @@ +package host + +import ( + "fmt" + "io" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/cli/go-gh/v2/pkg/api" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" +) + +const ( + gitHubKeysUrlFmt = "https://github.com/%s" + gitLabKeysUrlFmt = "https://gitlab.com/%s" + sourceHutKeysUrlFmt = "https://meta.sr.ht/~%s" +) + +type AuthorizedKey struct { + PublicKeys []ssh.PublicKey + Comment string +} + +func AuthorizedKeysFromFile(file string) (*AuthorizedKey, error) { + authorizedKeysBytes, err := os.ReadFile(file) + if err != nil { + return nil, nil + } + + return parseAuthorizedKeys(authorizedKeysBytes, file) +} + +func GitHubUserAuthorizedKeys(usernames []string, logger *logrus.Logger) ([]*AuthorizedKey, error) { + var ( + authorizedKeys []*AuthorizedKey + seen = make(map[string]bool) + ) + for _, username := range usernames { + if _, found := seen[username]; !found { + seen[username] = true + + pks, err := githubUserPublicKeys(username, logger) + if err != nil { + return nil, err + } + + aks, err := parseAuthorizedKeys(pks, username) + if err != nil { + return nil, err + } + + authorizedKeys = append(authorizedKeys, aks) + } + } + + return authorizedKeys, nil +} + +func GitLabUserAuthorizedKeys(usernames []string) ([]*AuthorizedKey, error) { + return usersPublicKeys(gitLabKeysUrlFmt, usernames) +} + +func SourceHutUserAuthorizedKeys(usernames []string) ([]*AuthorizedKey, error) { + return usersPublicKeys(sourceHutKeysUrlFmt, usernames) +} + +func parseAuthorizedKeys(keysBytes []byte, comment string) (*AuthorizedKey, error) { + var authorizedKeys []ssh.PublicKey + for len(keysBytes) > 0 { + pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(keysBytes) + if err != nil { + return nil, err + } + + authorizedKeys = append(authorizedKeys, pubKey) + keysBytes = rest + } + + return &AuthorizedKey{ + PublicKeys: authorizedKeys, + Comment: comment, + }, nil +} + +func githubUserPublicKeys(username string, logger *logrus.Logger) ([]byte, error) { + client, err := api.DefaultRESTClient() + if err != nil { + if strings.Contains(err.Error(), "authentication token not found for host") { + // fallback to use the public GH API + logger.WithError(err).Warn("no GitHub token found, falling back to public API") + return userPublicKeys(gitHubKeysUrlFmt, username) + } + + return nil, err + } + + keys := []struct { + Key string `json:"key"` + }{} + if err := client.Get(fmt.Sprintf("users/%s/keys", url.PathEscape(username)), &keys); err != nil { + return nil, err + } + + var authorizedKeys []string + for _, key := range keys { + authorizedKeys = append(authorizedKeys, key.Key) + } + + return []byte(strings.Join(authorizedKeys, "\n")), nil +} + +func usersPublicKeys(urlFmt string, usernames []string) ([]*AuthorizedKey, error) { + var ( + authorizedKeys []*AuthorizedKey + seen = make(map[string]bool) + ) + for _, username := range usernames { + if _, found := seen[username]; !found { + seen[username] = true + + keyBytes, err := userPublicKeys(urlFmt, username) + if err != nil { + return nil, fmt.Errorf("[%s]: %s", username, err) + } + userKeys, err := parseAuthorizedKeys(keyBytes, username) + if err != nil { + return nil, fmt.Errorf("[%s]: %s", username, err) + } + + authorizedKeys = append(authorizedKeys, userKeys) + } + } + return authorizedKeys, nil +} + +func userPublicKeys(urlFmt string, username string) ([]byte, error) { + path := url.PathEscape(fmt.Sprintf("%s.keys", username)) + + client := http.Client{ + Timeout: 5 * time.Second, + } + resp, err := client.Get(fmt.Sprintf(urlFmt, path)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return io.ReadAll(resp.Body) +} diff --git a/host/host.go b/host/host.go index 983555905..e91c389b4 100644 --- a/host/host.go +++ b/host/host.go @@ -155,7 +155,7 @@ type Host struct { ForceCommand []string Signers []ssh.Signer HostKeyCallback ssh.HostKeyCallback - AuthorizedKeys []ssh.PublicKey + AuthorizedKeys []*AuthorizedKey AdminSocketFile string SessionCreatedCallback func(*api.GetSessionResponse) error ClientJoinedCallback func(*api.Client) @@ -179,6 +179,11 @@ func (c *Host) Run(ctx context.Context) error { c.Stdout = os.Stdout } + var aks []ssh.PublicKey + for _, ak := range c.AuthorizedKeys { + aks = append(aks, ak.PublicKeys...) + } + logger := c.Logger.WithField("server", u) logger.Info("Etablishing reverse tunnel") @@ -186,7 +191,7 @@ func (c *Host) Run(ctx context.Context) error { Host: u, Signers: c.Signers, HostKeyCallback: c.HostKeyCallback, - AuthorizedKeys: c.AuthorizedKeys, + AuthorizedKeys: aks, KeepAliveDuration: c.KeepAliveDuration, Logger: c.Logger.WithField("com", "reverse-tunnel"), } @@ -211,11 +216,12 @@ func (c *Host) Run(ctx context.Context) error { logger.Info("Established reverse tunnel") session := &api.GetSessionResponse{ - SessionId: sessResp.SessionID, - Host: u.String(), - NodeAddr: sessResp.NodeAddr, - Command: c.Command, - ForceCommand: c.ForceCommand, + SessionId: sessResp.SessionID, + Host: u.String(), + NodeAddr: sessResp.NodeAddr, + Command: c.Command, + ForceCommand: c.ForceCommand, + AuthorizedKeys: toApiAuthorizedKeys(c.AuthorizedKeys), } if c.SessionCreatedCallback != nil { @@ -302,7 +308,7 @@ func (c *Host) Run(ctx context.Context) error { CommandEnv: []string{fmt.Sprintf("%s=%s", upterm.HostAdminSocketEnvVar, c.AdminSocketFile)}, ForceCommand: c.ForceCommand, Signers: c.Signers, - AuthorizedKeys: c.AuthorizedKeys, + AuthorizedKeys: aks, EventEmitter: eventEmitter, KeepAliveDuration: c.KeepAliveDuration, Stdin: c.Stdin, @@ -342,3 +348,20 @@ func createFileIfNotExist(file string) error { return nil } + +func toApiAuthorizedKeys(aks []*AuthorizedKey) []*api.AuthorizedKey { + var apiAks []*api.AuthorizedKey + for _, ak := range aks { + var fps []string + for _, pk := range ak.PublicKeys { + fps = append(fps, utils.FingerprintSHA256(pk)) + } + + apiAks = append(apiAks, &api.AuthorizedKey{ + PublicKeyFingerprints: fps, + Comment: ak.Comment, + }) + } + + return apiAks +} diff --git a/host/internal/adminserver.go b/host/internal/adminserver.go index af03d59f8..f68131a67 100644 --- a/host/internal/adminserver.go +++ b/host/internal/adminserver.go @@ -56,6 +56,7 @@ func (s *adminServiceServer) GetSession(ctx context.Context, in *api.GetSessionR NodeAddr: s.Session.NodeAddr, Command: s.Session.Command, ForceCommand: s.Session.ForceCommand, + AuthorizedKeys: s.Session.AuthorizedKeys, ConnectedClients: s.ClientRepo.Clients(), }, nil } diff --git a/host/signer.go b/host/signer.go index d516339da..3adee4490 100644 --- a/host/signer.go +++ b/host/signer.go @@ -5,10 +5,7 @@ import ( "crypto/x509" "errors" "fmt" - "io" "net" - "net/http" - "net/url" "os" "strings" "syscall" @@ -21,9 +18,6 @@ import ( const ( errCannotDecodeEncryptedPrivateKeys = "cannot decode encrypted private keys" - gitHubKeysUrlFmt = "https://github.com/%s" - gitLabKeysUrlFmt = "https://gitlab.com/%s" - sourceHutKeysUrlFmt = "https://meta.sr.ht/~%s" ) type errDescryptingPrivateKey struct { @@ -34,73 +28,6 @@ func (e *errDescryptingPrivateKey) Error() string { return fmt.Sprintf("error decrypting private key %s", e.file) } -func parseKeys(keysBytes []byte) ([]ssh.PublicKey, error) { - var authorizedKeys []ssh.PublicKey - for len(keysBytes) > 0 { - pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(keysBytes) - if err != nil { - return nil, err - } - - authorizedKeys = append(authorizedKeys, pubKey) - keysBytes = rest - } - - return authorizedKeys, nil -} - -func AuthorizedKeys(file string) ([]ssh.PublicKey, error) { - authorizedKeysBytes, err := os.ReadFile(file) - if err != nil { - return nil, nil - } - - return parseKeys(authorizedKeysBytes) -} - -func getUserPublicKeys(urlFmt string, username string) ([]byte, error) { - path := url.PathEscape(fmt.Sprintf("%s.keys", username)) - resp, err := http.Get(fmt.Sprintf(urlFmt, path)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - return io.ReadAll(resp.Body) -} - -func getPublicKeys(urlFmt string, usernames []string) ([]ssh.PublicKey, error) { - var authorizedKeys []ssh.PublicKey - seen := make(map[string]bool) - for _, username := range usernames { - if _, found := seen[username]; !found { - seen[username] = true - keyBytes, err := getUserPublicKeys(urlFmt, username) - if err != nil { - return nil, fmt.Errorf("[%s]: %s", username, err) - } - userKeys, err := parseKeys(keyBytes) - if err != nil { - return nil, fmt.Errorf("[%s]: %s", username, err) - } - authorizedKeys = append(authorizedKeys, userKeys...) - } - } - return authorizedKeys, nil -} - -func GitHubUserKeys(usernames []string) ([]ssh.PublicKey, error) { - return getPublicKeys(gitHubKeysUrlFmt, usernames) -} - -func GitLabUserKeys(usernames []string) ([]ssh.PublicKey, error) { - return getPublicKeys(gitLabKeysUrlFmt, usernames) -} - -func SourceHutUserKeys(usernames []string) ([]ssh.PublicKey, error) { - return getPublicKeys(sourceHutKeysUrlFmt, usernames) -} - // Signers return signers based on the folllowing conditions: // If SSH agent is running and has keys, it returns signers from SSH agent, otherwise return signers from private keys; // If neither works, it generates a signer on the fly.