Skip to content

Commit e7b2cec

Browse files
committed
Add support of password-auth-script (fix #28)
1 parent dc79b1e commit e7b2cec

File tree

6 files changed

+97
-16
lines changed

6 files changed

+97
-16
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ GLOBAL OPTIONS:
140140

141141
### master (unreleased)
142142

143+
* Support of 'ssh2docker --password-auth-script' options ([#28](https://github.com/moul/ssh2docker/issues/28))
143144
* Add docker support ([#17](https://github.com/moul/ssh2docker/issues/17))
144145
* Add GOXC support to build binaries for multiple architectures ([#18](https://github.com/moul/ssh2docker/issues/18))
145146
* Support of 'ssh2docker --clean-on-startup' ([#23](https://github.com/moul/ssh2docker/issues/23))

auth.go

+43-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package ssh2docker
22

33
import (
4+
"encoding/json"
45
"fmt"
6+
"os/exec"
57

8+
"github.com/Sirupsen/logrus"
69
"github.com/moul/ssh2docker/vendor/golang.org/x/crypto/ssh"
710
)
811

@@ -20,10 +23,48 @@ func (s *Server) ImageIsAllowed(target string) bool {
2023
}
2124

2225
// PasswordCallback is called when the user tries to authenticate using a password
23-
func (s *Server) PasswordCallback(conn ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
24-
image := conn.User()
26+
func (s *Server) PasswordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
27+
username := conn.User()
28+
clientID := conn.RemoteAddr().String()
29+
30+
logrus.Debugf("PasswordCallback: %q %q", username, password)
31+
var image string
32+
33+
if s.PasswordAuthScript != "" {
34+
// Using a hook script
35+
script, err := expandUser(s.PasswordAuthScript)
36+
if err != nil {
37+
logrus.Warnf("Failed to expandUser: %v", err)
38+
return nil, err
39+
}
40+
cmd := exec.Command(script, username, string(password))
41+
output, err := cmd.CombinedOutput()
42+
if err != nil {
43+
logrus.Warnf("Failed to execute password-auth-script: %v", err)
44+
return nil, err
45+
}
46+
47+
var config ClientConfig
48+
err = json.Unmarshal(output, &config)
49+
if err != nil {
50+
logrus.Warnf("Failed to unmarshal json %q: %v", string(output), err)
51+
return nil, err
52+
}
53+
s.ClientConfigs[clientID] = &config
54+
if config.Allowed == false {
55+
logrus.Warnf("Hook returned allowed:false")
56+
return nil, fmt.Errorf("Access not allowed")
57+
}
58+
59+
return nil, nil
60+
} else {
61+
// Default behavior
62+
image = username
63+
}
64+
2565
if s.ImageIsAllowed(image) {
2666
return nil, nil
2767
}
68+
2869
return nil, fmt.Errorf("TEST")
2970
}

client.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,13 @@ type Client struct {
2626
Server *Server
2727
Pty, Tty *os.File
2828
Env Environment
29-
RemoteUser string
30-
ImageName string
29+
Config *ClientConfig
30+
}
31+
32+
type ClientConfig struct {
33+
ImageName string `json:"image-name",omitempty`
34+
RemoteUser string `json:"remote-user",omitempty`
35+
Allowed bool `json:"allowed",omitempty`
3136
}
3237

3338
// NewClient initializes a new client
@@ -39,14 +44,18 @@ func NewClient(conn *ssh.ServerConn, chans <-chan ssh.NewChannel, reqs <-chan *s
3944
Chans: chans,
4045
Reqs: reqs,
4146
Server: server,
42-
ImageName: conn.User(),
43-
RemoteUser: "nobody",
4447
Env: Environment{
4548
"TERM": os.Getenv("TERM"),
4649
"DOCKER_HOST": os.Getenv("DOCKER_HOST"),
4750
"DOCKER_CERT_PATH": os.Getenv("DOCKER_CERT_PATH"),
4851
"DOCKER_TLS_VERIFY": os.Getenv("DOCKER_TLS_VERIFY"),
4952
},
53+
54+
// Default ClientConfig, maybe we should completely remove it
55+
Config: &ClientConfig{
56+
ImageName: conn.User(),
57+
RemoteUser: "anonymous",
58+
},
5059
}
5160

5261
clientCounter++
@@ -124,7 +133,7 @@ func (c *Client) HandleChannelRequests(channel ssh.Channel, requests <-chan *ssh
124133
// checking if a container already exists for this user
125134
existingContainer := ""
126135
if !c.Server.NoJoin {
127-
cmd := exec.Command("docker", "ps", "--filter=label=ssh2docker", fmt.Sprintf("--filter=label=image=%s", c.ImageName), fmt.Sprintf("--filter=label=user=%s", c.RemoteUser), "--quiet", "--no-trunc")
136+
cmd := exec.Command("docker", "ps", "--filter=label=ssh2docker", fmt.Sprintf("--filter=label=image=%s", c.Config.ImageName), fmt.Sprintf("--filter=label=user=%s", c.Config.RemoteUser), "--quiet", "--no-trunc")
128137
buf, err := cmd.CombinedOutput()
129138
if err != nil {
130139
logrus.Warnf("docker ps ... failed: %v", err)
@@ -146,8 +155,8 @@ func (c *Client) HandleChannelRequests(channel ssh.Channel, requests <-chan *ssh
146155
// Creating and attaching to a new container
147156
args := []string{"run"}
148157
args = append(args, c.Server.DockerRunArgs...)
149-
args = append(args, "--label=ssh2docker", fmt.Sprintf("--label=user=%s", c.RemoteUser), fmt.Sprintf("--label=image=%s", c.ImageName))
150-
args = append(args, c.ImageName, c.Server.DefaultShell)
158+
args = append(args, "--label=ssh2docker", fmt.Sprintf("--label=user=%s", c.Config.RemoteUser), fmt.Sprintf("--label=image=%s", c.Config.ImageName))
159+
args = append(args, c.Config.ImageName, c.Server.DefaultShell)
151160
logrus.Debugf("Executing 'docker %s'", strings.Join(args, " "))
152161
cmd = exec.Command("docker", args...)
153162
cmd.Env = c.Env.List()

cmd/ssh2docker/main.go

+5
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ func main() {
9393
Name: "clean-on-startup",
9494
Usage: "Cleanup Docker containers created by ssh2docker on start",
9595
},
96+
cli.StringFlag{
97+
Name: "password-auth-script",
98+
Usage: "Password auth hook file",
99+
},
96100
}
97101

98102
app.Action = Action
@@ -128,6 +132,7 @@ func Action(c *cli.Context) {
128132
server.DockerRunArgs = strings.Split(c.String("docker-run-args"), " ")
129133
server.NoJoin = c.Bool("no-join")
130134
server.CleanOnStartup = c.Bool("clean-on-startup")
135+
server.PasswordAuthScript = c.String("password-auth-script")
131136

132137
// Register the SSH host key
133138
hostKey := c.String("host-key")

server.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import (
1010
// Server is the ssh2docker main structure
1111
type Server struct {
1212
SshConfig *ssh.ServerConfig
13-
// Clients []Client
13+
// Clients map[string]Client
14+
ClientConfigs map[string]*ClientConfig
1415

15-
AllowedImages []string
16-
DefaultShell string
17-
DockerRunArgs []string
18-
NoJoin bool
19-
CleanOnStartup bool
16+
AllowedImages []string
17+
DefaultShell string
18+
DockerRunArgs []string
19+
NoJoin bool
20+
CleanOnStartup bool
21+
PasswordAuthScript string
2022

2123
initialized bool
2224
}
@@ -27,7 +29,7 @@ func NewServer() (*Server, error) {
2729
server.SshConfig = &ssh.ServerConfig{
2830
PasswordCallback: server.PasswordCallback,
2931
}
30-
server.AllowedImages = nil
32+
server.ClientConfigs = make(map[string]*ClientConfig, 0)
3133
server.DefaultShell = "/bin/sh"
3234
server.DockerRunArgs = []string{"-it", "--rm"}
3335
return &server, nil
@@ -64,6 +66,7 @@ func (s *Server) Handle(netConn net.Conn) error {
6466
return err
6567
}
6668
client := NewClient(conn, chans, reqs, s)
69+
client.Config = s.ClientConfigs[conn.RemoteAddr().String()]
6770

6871
// Handle requests
6972
if err = client.HandleRequests(); err != nil {

utils.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package ssh2docker
2+
3+
import (
4+
"errors"
5+
"os"
6+
"strings"
7+
)
8+
9+
func expandUser(path string) (string, error) {
10+
if path[:2] == "~/" {
11+
homeDir := os.Getenv("HOME") // *nix
12+
if homeDir == "" { // Windows
13+
homeDir = os.Getenv("USERPROFILE")
14+
}
15+
if homeDir == "" {
16+
return "", errors.New("user home directory not found")
17+
}
18+
19+
return strings.Replace(path, "~", homeDir, 1), nil
20+
}
21+
return path, nil
22+
}

0 commit comments

Comments
 (0)