1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
package server
import (
"fmt"
"path/filepath"
"regexp"
"strings"
"github.com/mimecast/dtail/internal/config"
"github.com/mimecast/dtail/internal/io/dlog"
"github.com/mimecast/dtail/internal/io/fs"
"github.com/mimecast/dtail/internal/io/fs/permissions"
)
// User represents an end-user which connected to the server via the DTail client.
type User struct {
// The user name.
Name string
// The remote address connected from.
remoteAddress string
// The permissions the user has.
permissions []string
}
// PermissionLookup resolves permissions for a given SSH user.
type PermissionLookup func(string) ([]string, error)
// New returns a new user.
func New(name, remoteAddress string, permissionLookup PermissionLookup) (*User, error) {
var (
permissions []string
err error
)
if permissionLookup != nil {
permissions, err = permissionLookup(name)
if err != nil {
return nil, err
}
}
return &User{
Name: name,
remoteAddress: remoteAddress,
permissions: permissions,
}, nil
}
// String representation of the user.
func (u *User) String() string {
return fmt.Sprintf("%s@%s", u.Name, u.remoteAddress)
}
// HasFilePermission is used to determine whether user is allowed to read a file.
func (u *User) HasFilePermission(filePath, permissionType string) bool {
_, hasPermission := u.ValidateReadTarget(filePath, permissionType)
return hasPermission
}
// ValidateReadTarget resolves and authorizes a file path for server-side reads.
func (u *User) ValidateReadTarget(filePath, permissionType string) (fs.ValidatedReadTarget, bool) {
dlog.Server.Debug(u, filePath, permissionType, "Checking config permissions")
cleanPath, err := filepath.EvalSymlinks(filePath)
if err != nil {
dlog.Server.Error(u, filePath, permissionType,
"Unable to evaluate symlinks", err)
return fs.ValidatedReadTarget{}, false
}
cleanPath, err = filepath.Abs(cleanPath)
if err != nil {
dlog.Server.Error(u, cleanPath, permissionType,
"Unable to make file path absolute", err)
return fs.ValidatedReadTarget{}, false
}
if cleanPath != filePath {
dlog.Server.Info(u, filePath, cleanPath, permissionType,
"Calculated new clean path from original file path (possibly symlink)")
}
if u.Name != config.ScheduleUser && u.Name != config.ContinuousUser {
hasPermission, permissionErr := u.hasFilePermission(cleanPath, permissionType)
if permissionErr != nil {
dlog.Server.Warn(u, cleanPath, permissionErr)
}
if !hasPermission {
return fs.ValidatedReadTarget{}, false
}
}
target, err := fs.NewValidatedReadTarget(cleanPath)
if err != nil {
dlog.Server.Warn(u, cleanPath, permissionType, "Unable to validate read target", err)
return fs.ValidatedReadTarget{}, false
}
return target, true
}
func (u *User) hasFilePermission(cleanPath, permissionType string) (bool, error) {
// First check file system Linux/UNIX permission.
if _, err := permissions.ToRead(u.Name, cleanPath); err != nil {
return false, fmt.Errorf("User without OS file system permissions to read path: %w", err)
}
dlog.Server.Info(u, cleanPath, permissionType,
"User with OS file system permissions to path")
hasPermission, err := u.iteratePaths(cleanPath, permissionType)
if err != nil {
return false, err
}
return hasPermission, nil
}
func (u *User) iteratePaths(cleanPath, permissionType string) (bool, error) {
// By default assume no permissions
hasPermission := false
for _, permission := range u.permissions {
typeStr := "readfiles" // Assume ReadFiles by default.
var regexStr string
var negate bool
splitted := strings.Split(permission, ":")
if len(splitted) > 1 {
typeStr = splitted[0]
permission = strings.Join(splitted[1:], ":")
}
dlog.Server.Debug(u, cleanPath, typeStr, permission)
if typeStr != permissionType {
continue
}
regexStr = permission
if strings.HasPrefix(permission, "!") {
regexStr = permission[1:]
negate = true
}
re, err := regexp.Compile(regexStr)
if err != nil {
return false, fmt.Errorf("Permission test failed, can't compile regex "+
"'%s': %w", regexStr, err)
}
if negate && re.MatchString(cleanPath) {
dlog.Server.Info(u, cleanPath, "Permission test failed partially, "+
"matching negative pattern '%s'", permission)
hasPermission = false
}
if !negate && re.MatchString(cleanPath) {
dlog.Server.Info(u, cleanPath, "Permission test passed partially, "+
"matching positive pattern", permission)
hasPermission = true
}
}
return hasPermission, nil
}
|