Skip to content

Commit fdd774a

Browse files
committed
Support permissions
1 parent b9cb99a commit fdd774a

File tree

5 files changed

+200
-25
lines changed

5 files changed

+200
-25
lines changed

cmd/sshproxy/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func main() {
4949
}
5050
svc.ServerConfig.AddHostKey(key)
5151
}
52-
if username != "" {
52+
if username != "" && password != "" {
5353
svc.ServerConfig.PasswordCallback = func(conn ssh.ConnMetadata, pwd []byte) (*ssh.Permissions, error) {
5454
if conn.User() == username && password == string(pwd) {
5555
return nil, nil
@@ -64,8 +64,8 @@ func main() {
6464
return
6565
}
6666
svc.ServerConfig.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
67-
k := string(key.Marshal())
68-
if _, ok := keys[k]; ok {
67+
ok, _ := keys.Allow(key)
68+
if ok {
6969
return nil, nil
7070
}
7171
return nil, fmt.Errorf("denied")

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/wzshiming/sshproxy
33
go 1.17
44

55
require (
6-
github.com/wzshiming/sshd v0.1.8
6+
github.com/wzshiming/sshd v0.2.0
77
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
88
)
99

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
2-
github.com/wzshiming/sshd v0.1.8 h1:Rct0s8+i3ehzEA0CV/B9Qms4FF8KgwuDoWV2zmAfiho=
3-
github.com/wzshiming/sshd v0.1.8/go.mod h1:7+xrQ+XMfrKmInssZuqw1HDHGper+yWKnCAi4uljLns=
2+
github.com/wzshiming/sshd v0.2.0 h1:ULFRM4HOm58TzOzt46LZUQibZl6sjOAZjpRCKKfd8iw=
3+
github.com/wzshiming/sshd v0.2.0/go.mod h1:7+xrQ+XMfrKmInssZuqw1HDHGper+yWKnCAi4uljLns=
44
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
55
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
66
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=

permissions/permissions.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package permissions
2+
3+
import (
4+
"encoding/json"
5+
"github.com/wzshiming/sshd"
6+
"os"
7+
"sync"
8+
"time"
9+
)
10+
11+
type Permissions map[string]Permission
12+
13+
func (p Permissions) Allow(req string, args string) bool {
14+
permission, ok := p[req]
15+
if !ok {
16+
return false
17+
}
18+
return permission.Allow(req, args)
19+
}
20+
21+
type Permission struct {
22+
Default bool `json:"default,omitempty"`
23+
Allows []string `json:"allows,omitempty"`
24+
Blocks []string `json:"blocks,omitempty"`
25+
}
26+
27+
func (p Permission) Allow(req string, args string) bool {
28+
if p.Allows != nil {
29+
for _, item := range p.Allows {
30+
if item == args {
31+
return true
32+
}
33+
}
34+
return false
35+
}
36+
if p.Blocks != nil {
37+
for _, item := range p.Blocks {
38+
if item == args {
39+
return false
40+
}
41+
}
42+
return true
43+
}
44+
return p.Default
45+
}
46+
47+
type PermissionsFromFile struct {
48+
permissions *Permissions
49+
path string
50+
period time.Duration
51+
latestTime time.Time
52+
mut sync.RWMutex
53+
}
54+
55+
func NewPermissionsFromFile(file string, period time.Duration) sshd.Permissions {
56+
if period < time.Second {
57+
period = time.Second
58+
}
59+
return &PermissionsFromFile{
60+
path: file,
61+
period: period,
62+
}
63+
}
64+
65+
func (s *PermissionsFromFile) Allow(req string, args string) bool {
66+
perm, ok := s.check()
67+
if !ok {
68+
return false
69+
}
70+
ok = perm.Allow(req, args)
71+
return ok
72+
}
73+
74+
func (s *PermissionsFromFile) get() (*Permissions, time.Time) {
75+
s.mut.RLock()
76+
defer s.mut.RUnlock()
77+
return s.permissions, s.latestTime
78+
}
79+
80+
func (s *PermissionsFromFile) check() (*Permissions, bool) {
81+
perm, latest := s.get()
82+
if perm == nil || time.Since(latest) > s.period {
83+
if !s.update() {
84+
return nil, false
85+
}
86+
perm, latest = s.get()
87+
if perm == nil || time.Since(latest) > s.period {
88+
return nil, false
89+
}
90+
}
91+
return perm, true
92+
}
93+
94+
func (s *PermissionsFromFile) update() bool {
95+
var perm *Permissions
96+
f, err := os.ReadFile(s.path)
97+
if err != nil {
98+
if os.IsNotExist(err) {
99+
return false
100+
}
101+
} else {
102+
err = json.Unmarshal(f, &perm)
103+
if err != nil {
104+
perm = nil
105+
}
106+
}
107+
s.mut.Lock()
108+
defer s.mut.Unlock()
109+
s.permissions = perm
110+
s.latestTime = time.Now()
111+
return true
112+
}

simple_server.go

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import (
66
"fmt"
77
"net"
88
"net/url"
9+
"path"
910
"strconv"
11+
"time"
1012

1113
"github.com/wzshiming/sshd"
14+
"github.com/wzshiming/sshproxy/permissions"
1215
"golang.org/x/crypto/ssh"
1316
)
1417

@@ -24,14 +27,15 @@ type SimpleServer struct {
2427

2528
// NewSimpleServer creates a new NewSimpleServer
2629
func NewSimpleServer(addr string) (*SimpleServer, error) {
27-
user, pwd, host, config, err := serverConfig(addr)
30+
user, pwd, host, config, userPermissions, err := serverConfig(addr)
2831
if err != nil {
2932
return nil, err
3033
}
3134

3235
s := &SimpleServer{
3336
Server: Server{
34-
ServerConfig: *config,
37+
ServerConfig: *config,
38+
UserPermissions: userPermissions,
3539
},
3640
Network: "tcp",
3741
Address: host,
@@ -41,10 +45,10 @@ func NewSimpleServer(addr string) (*SimpleServer, error) {
4145
return s, nil
4246
}
4347

44-
func serverConfig(addr string) (host, user, pwd string, config *ssh.ServerConfig, err error) {
48+
func serverConfig(addr string) (host, user, pwd string, config *ssh.ServerConfig, userPermissions func(user string) sshd.Permissions, err error) {
4549
ur, err := url.Parse(addr)
4650
if err != nil {
47-
return "", "", "", nil, err
51+
return "", "", "", nil, nil, err
4852
}
4953

5054
isPwd := false
@@ -71,45 +75,104 @@ func serverConfig(addr string) (host, user, pwd string, config *ssh.ServerConfig
7175

7276
hostkeyDatas, err := getQuery(ur.Query()["hostkey_data"], ur.Query()["hostkey_file"])
7377
if err != nil {
74-
return "", "", "", nil, err
78+
return "", "", "", nil, nil, err
7579
}
7680
if len(hostkeyDatas) == 0 {
7781
key, err := sshd.RandomHostkey()
7882
if err != nil {
79-
return "", "", "", nil, err
83+
return "", "", "", nil, nil, err
8084
}
8185
config.AddHostKey(key)
8286
} else {
8387
for _, data := range hostkeyDatas {
8488
key, err := sshd.ParseHostkey(data)
8589
if err != nil {
86-
return "", "", "", nil, err
90+
return "", "", "", nil, nil, err
8791
}
8892
config.AddHostKey(key)
8993
}
9094
}
9195

96+
pks := []func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error){}
9297
authorizedDatas, err := getQuery(ur.Query()["authorized_data"], ur.Query()["authorized_file"])
9398
if err != nil {
94-
return "", "", "", nil, err
99+
return "", "", "", nil, nil, err
95100
}
96-
allKeys := map[string]string{}
97-
for _, data := range authorizedDatas {
98-
keys, err := sshd.ParseAuthorized(bytes.NewBuffer(data))
101+
if len(authorizedDatas) != 0 {
102+
keys, err := sshd.ParseAuthorized(bytes.NewBuffer(bytes.Join(authorizedDatas, []byte{'\n'})))
99103
if err != nil {
100-
return "", "", "", nil, err
104+
return "", "", "", nil, nil, err
101105
}
102-
for k, v := range keys {
103-
allKeys[k] = v
106+
if len(keys.Data) != 0 {
107+
pks = append(pks, func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
108+
ok, _ := keys.Allow(key)
109+
if ok {
110+
return nil, nil
111+
}
112+
return nil, fmt.Errorf("denied")
113+
})
104114
}
105115
}
106-
if len(allKeys) != 0 {
107-
config.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
108-
k := string(key.Marshal())
109-
if _, ok := allKeys[k]; ok {
116+
117+
homeDirs := ur.Query()["home_dir"]
118+
if len(homeDirs) != 0 && homeDirs[0] != "" {
119+
homeDir := homeDirs[0]
120+
sshDirName := ".ssh"
121+
sshDirNames := ur.Query()["ssh_dir_name"]
122+
if len(sshDirNames) != 0 {
123+
sshDirName = sshDirNames[0]
124+
}
125+
authorizedFileName := "authorized_keys"
126+
authorizedFileNames := ur.Query()["authorized_file_name"]
127+
if len(authorizedFileNames) != 0 {
128+
authorizedFileName = authorizedFileNames[0]
129+
}
130+
pks = append(pks, func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
131+
file := path.Join(homeDir, conn.User(), sshDirName, authorizedFileName)
132+
keys, err := sshd.GetAuthorizedFile(file)
133+
if err != nil {
134+
return nil, fmt.Errorf("denied")
135+
}
136+
ok, _ := keys.Allow(key)
137+
if ok {
110138
return nil, nil
111139
}
112140
return nil, fmt.Errorf("denied")
141+
})
142+
143+
// Other sshd implementations do not have such fine-grained permissions control,
144+
// and this is a fine-grained set of permissions control files defined by the project itself
145+
permissionsFileName := ""
146+
permissionsFileNames := ur.Query()["permissions_file_name"]
147+
if len(permissionsFileNames) != 0 {
148+
permissionsFileName = permissionsFileNames[0]
149+
}
150+
if permissionsFileName != "" {
151+
permissionsFileUpdatePeriod := time.Duration(0)
152+
permissionsFileUpdatePeriods := ur.Query()["permissions_file_update_period"]
153+
if len(permissionsFileUpdatePeriods) != 0 {
154+
permissionsFileUpdatePeriod, _ = time.ParseDuration(permissionsFileUpdatePeriods[0])
155+
}
156+
userPermissions = func(user string) sshd.Permissions {
157+
file := path.Join(homeDir, user, sshDirName, permissionsFileName)
158+
return permissions.NewPermissionsFromFile(file, permissionsFileUpdatePeriod)
159+
}
160+
}
161+
}
162+
163+
if len(pks) != 0 {
164+
if len(pks) == 1 {
165+
config.PublicKeyCallback = pks[0]
166+
} else {
167+
config.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (p *ssh.Permissions, err error) {
168+
for _, pk := range pks {
169+
p, err = pk(conn, key)
170+
if err == nil {
171+
break
172+
}
173+
}
174+
return
175+
}
113176
}
114177
}
115178

@@ -125,7 +188,7 @@ func serverConfig(addr string) (host, user, pwd string, config *ssh.ServerConfig
125188
port = "22"
126189
}
127190
host = net.JoinHostPort(host, port)
128-
return user, pwd, host, config, nil
191+
return user, pwd, host, config, userPermissions, nil
129192
}
130193

131194
// Run the server

0 commit comments

Comments
 (0)