Skip to content

Commit 9758193

Browse files
committed
Merge branch '91-identity-file-support' into 'master'
# Description - Support an identity file in CLI for ssh authentication. - Use an identity key if specified, otherwise use an ssh-agent See merge request postgres-ai/database-lab!121
2 parents b118a4d + 57d5df6 commit 9758193

File tree

9 files changed

+81
-17
lines changed

9 files changed

+81
-17
lines changed

cmd/cli/commands/client.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const (
2121
InsecureKey = "insecure"
2222
FwServerURLKey = "forwarding-server-url"
2323
FwLocalPortKey = "forwarding-local-port"
24+
IdentityFileKey = "identity-file"
2425
)
2526

2627
// ClientByCLIContext creates a new Database Lab API client.

cmd/cli/commands/clone/actions.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ func forward(cliCtx *cli.Context) error {
261261

262262
wg := &sync.WaitGroup{}
263263

264-
port, err := retrieveClonePort(cliCtx, wg, remoteURL.Host)
264+
port, err := retrieveClonePort(cliCtx, wg, remoteURL)
265265
if err != nil {
266266
return err
267267
}
@@ -270,7 +270,9 @@ func forward(cliCtx *cli.Context) error {
270270

271271
log.Dbg(fmt.Sprintf("The clone port has been retrieved: %s", port))
272272

273-
tunnel, err := commands.BuildTunnel(cliCtx, commands.BuildHostname(remoteURL.Hostname(), port))
273+
remoteURL.Host = commands.BuildHostname(remoteURL.Hostname(), port)
274+
275+
tunnel, err := commands.BuildTunnel(cliCtx, remoteURL)
274276
if err != nil {
275277
return err
276278
}
@@ -288,7 +290,7 @@ func forward(cliCtx *cli.Context) error {
288290
return nil
289291
}
290292

291-
func retrieveClonePort(cliCtx *cli.Context, wg *sync.WaitGroup, remoteHost string) (string, error) {
293+
func retrieveClonePort(cliCtx *cli.Context, wg *sync.WaitGroup, remoteHost *url.URL) (string, error) {
292294
tunnel, err := commands.BuildTunnel(cliCtx, remoteHost)
293295
if err != nil {
294296
return "", err

cmd/cli/commands/config/command_list.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ func CommandList() []*cli.Command {
4545
Name: "forwarding-local-port",
4646
Usage: "local port for forwarding to the Database Lab instance",
4747
},
48+
&cli.StringFlag{
49+
Name: "identity-file",
50+
Usage: "select a file from which the identity (private key) for public key authentication is read",
51+
},
4852
},
4953
},
5054
{
@@ -73,6 +77,10 @@ func CommandList() []*cli.Command {
7377
Name: "forwarding-local-port",
7478
Usage: "local port for forwarding to the Database Lab instance",
7579
},
80+
&cli.StringFlag{
81+
Name: "identity-file",
82+
Usage: "select a file from which the identity (private key) for public key authentication is read",
83+
},
7684
},
7785
},
7886
{

cmd/cli/commands/config/environment.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ type Environment struct {
2828

2929
// Forwarding defines configuration for port forwarding.
3030
type Forwarding struct {
31-
ServerURL string `yaml:"server_url" json:"server_url"`
32-
LocalPort string `yaml:"local_port" json:"local_port"`
31+
ServerURL string `yaml:"server_url" json:"server_url"`
32+
LocalPort string `yaml:"local_port" json:"local_port"`
33+
IdentityFile string `yaml:"identity_file" json:"identity_file"`
3334
}
3435

3536
// AddEnvironmentToConfig adds a new environment to CLIConfig.
@@ -47,8 +48,9 @@ func AddEnvironmentToConfig(c *cli.Context, cfg *CLIConfig, environmentID string
4748
Token: c.String(commands.TokenKey),
4849
Insecure: c.Bool(commands.InsecureKey),
4950
Forwarding: Forwarding{
50-
ServerURL: c.String(commands.FwServerURLKey),
51-
LocalPort: c.String(commands.FwLocalPortKey),
51+
ServerURL: c.String(commands.FwServerURLKey),
52+
LocalPort: c.String(commands.FwLocalPortKey),
53+
IdentityFile: c.String(commands.IdentityFileKey),
5254
},
5355
}
5456

@@ -99,6 +101,10 @@ func updateEnvironmentInConfig(c *cli.Context, cfg *CLIConfig, environmentID str
99101
newEnvironment.Forwarding.LocalPort = c.String(commands.FwLocalPortKey)
100102
}
101103

104+
if c.IsSet(commands.IdentityFileKey) {
105+
newEnvironment.Forwarding.IdentityFile = c.String(commands.IdentityFileKey)
106+
}
107+
102108
if newEnvironment == environment {
103109
return errors.New("config unchanged. Set different option values to update.") // nolint
104110
}

cmd/cli/commands/global/actions.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func forward(cliCtx *cli.Context) error {
6060
return err
6161
}
6262

63-
tunnel, err := commands.BuildTunnel(cliCtx, remoteURL.Host)
63+
tunnel, err := commands.BuildTunnel(cliCtx, remoteURL)
6464
if err != nil {
6565
return err
6666
}

cmd/cli/commands/global/command_list.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ func List() []*cli.Command {
4646
Name: "forwarding-local-port",
4747
Usage: "local port for forwarding to the Database Lab instance",
4848
},
49+
&cli.StringFlag{
50+
Name: "identity-file",
51+
Usage: "select a file from which the identity (private key) for public key authentication is read",
52+
},
4953
},
5054
Action: initCLI,
5155
},

cmd/cli/commands/port_forwarding.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,31 @@ import (
1717
)
1818

1919
// BuildTunnel creates a new instance of SSH tunnel.
20-
func BuildTunnel(cliCtx *cli.Context, remoteHost string) (*portfwd.SSHTunnel, error) {
21-
remoteURL, err := url.Parse(cliCtx.String(URLKey))
20+
func BuildTunnel(cliCtx *cli.Context, remoteHost *url.URL) (*portfwd.SSHTunnel, error) {
21+
localEndpoint := forwardingLocalEndpoint(remoteHost, cliCtx.String(FwLocalPortKey))
22+
23+
serverURL, err := url.Parse(cliCtx.String(FwServerURLKey))
2224
if err != nil {
2325
return nil, err
2426
}
2527

26-
localEndpoint := forwardingLocalEndpoint(remoteURL, cliCtx.String(FwLocalPortKey))
27-
28-
serverURL, err := url.Parse(cliCtx.String(FwServerURLKey))
28+
authMethod, err := getAuthMethod(cliCtx)
2929
if err != nil {
3030
return nil, err
3131
}
3232

3333
sshConfig := &ssh.ClientConfig{
3434
User: serverURL.User.Username(),
3535
Auth: []ssh.AuthMethod{
36-
portfwd.SSHAgent(),
36+
authMethod,
3737
},
3838
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
3939
// Always accept key.
4040
return nil
4141
},
4242
}
4343

44-
tunnel := portfwd.NewTunnel(localEndpoint, serverURL.Host, remoteHost, sshConfig)
44+
tunnel := portfwd.NewTunnel(localEndpoint, serverURL.Host, remoteHost.Host, sshConfig)
4545

4646
return tunnel, nil
4747
}
@@ -54,6 +54,19 @@ func forwardingLocalEndpoint(remoteURL *url.URL, localPort string) string {
5454
return fmt.Sprintf("%s:%s", "127.0.0.1", localPort)
5555
}
5656

57+
func getAuthMethod(cliCtx *cli.Context) (ssh.AuthMethod, error) {
58+
if cliCtx.String(IdentityFileKey) != "" {
59+
authMethod, err := portfwd.ReadAuthFromIdentityFile(cliCtx.String(IdentityFileKey))
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
return authMethod, nil
65+
}
66+
67+
return portfwd.SSHAgent(), nil
68+
}
69+
5770
// BuildHostname builds a hostname string.
5871
func BuildHostname(host, port string) string {
5972
return fmt.Sprintf("%s:%s", host, port)

cmd/cli/main.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,17 @@ func main() {
5858
&cli.StringFlag{
5959
Name: "forwarding-server-url",
6060
Usage: "forwarding server URL of Database Lab instance",
61-
EnvVars: []string{"DBLAB_FORWARDING_SERVER_URL"},
61+
EnvVars: []string{"DBLAB_CLI_FORWARDING_SERVER_URL"},
6262
},
6363
&cli.StringFlag{
6464
Name: "forwarding-local-port",
6565
Usage: "local port for forwarding to the Database Lab instance",
66-
EnvVars: []string{"DBLAB_FORWARDING_LOCAL_PORT"},
66+
EnvVars: []string{"DBLAB_CLI_FORWARDING_LOCAL_PORT"},
67+
},
68+
&cli.StringFlag{
69+
Name: "identity-file",
70+
Usage: "select a file from which the identity (private key) for public key authentication is read",
71+
EnvVars: []string{"DBLAB_CLI_IDENTITY_FILE"},
6772
},
6873
&cli.BoolFlag{
6974
Name: "debug",
@@ -126,6 +131,12 @@ func loadEnvironmentParams(c *cli.Context) error {
126131
return err
127132
}
128133
}
134+
135+
if !c.IsSet(commands.IdentityFileKey) {
136+
if err := c.Set(commands.IdentityFileKey, env.Forwarding.IdentityFile); err != nil {
137+
return err
138+
}
139+
}
129140
}
130141

131142
return nil

pkg/portfwd/sshtunnel.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ package portfwd
77

88
import (
99
"context"
10+
"fmt"
1011
"io"
12+
"io/ioutil"
1113
"net"
1214
"os"
1315

@@ -159,3 +161,20 @@ func SSHAgent() ssh.AuthMethod {
159161

160162
return nil
161163
}
164+
165+
// ReadAuthFromIdentityFile reads an identity file and returns an AuthMethod that uses the given key pairs.
166+
func ReadAuthFromIdentityFile(identityFilename string) (ssh.AuthMethod, error) {
167+
log.Dbg(fmt.Sprintf("Read identity file %q", identityFilename))
168+
169+
privateKey, err := ioutil.ReadFile(identityFilename)
170+
if err != nil {
171+
return nil, errors.Wrap(err, "failed to read an identity file")
172+
}
173+
174+
signer, err := ssh.ParsePrivateKey(privateKey)
175+
if err != nil {
176+
return nil, errors.Wrap(err, "failed to parse a private key")
177+
}
178+
179+
return ssh.PublicKeys(signer), nil
180+
}

0 commit comments

Comments
 (0)