-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathssh_force_command.go
More file actions
141 lines (114 loc) · 2.93 KB
/
ssh_force_command.go
File metadata and controls
141 lines (114 loc) · 2.93 KB
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
package main
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"os/user"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v3"
)
const authCnfFile = "~/.ssh/authorized_forced_commands.yml"
type authCommands struct {
Commands []struct {
Description string `json:"description"`
Env []string `json:"env"`
Path string `json:"path"`
} `json:"commands"`
Tag string `json:"tag"`
}
func checkPermissions(cnfFile string) error {
user, err := user.Current()
if err != nil {
return errors.New("can't get current user")
}
uid, _ := strconv.Atoi(user.Uid)
gid, _ := strconv.Atoi(user.Gid)
fileStat, err := os.Stat(cnfFile)
if err != nil {
return errors.New("can't stat cnf file")
}
if fileStat.Mode()&(1<<2) != 0 {
return errors.New("cnf file should not be accesible by others group")
}
fstat := fileStat.Sys().(*syscall.Stat_t)
if fstat == nil {
return errors.New("can't get cnf file ownership")
}
if uid != int(fstat.Uid) {
return errors.New("user must be cnf owner")
}
if gid != int(fstat.Gid) {
return errors.New("user group must be cnf group")
}
return nil
}
func main() {
var authCommands authCommands
var commandIndex int
isAllowed := false
if runtime.GOOS == "windows" {
fmt.Fprintln(os.Stderr, "windows not supported")
os.Exit(1)
}
sshOriginalCommand, exits := os.LookupEnv("SSH_ORIGINAL_COMMAND")
if !exits || sshOriginalCommand == "" {
fmt.Fprintln(os.Stderr, "SSH_ORIGINAL_COMMAND not set")
os.Exit(1)
}
expandedCnfPath, err := homedir.Expand(authCnfFile)
if err != nil {
fmt.Fprintln(os.Stderr, "can't expand cnf path: ", err)
os.Exit(1)
}
err = checkPermissions(expandedCnfPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
cnfFileReader, err := ioutil.ReadFile(expandedCnfPath)
if err != nil {
fmt.Fprintln(os.Stderr, err, authCnfFile)
os.Exit(1)
}
err = yaml.Unmarshal(cnfFileReader, &authCommands)
if err != nil {
fmt.Fprintln(os.Stderr, "conf file not valid: ", err)
os.Exit(1)
}
commandFields := strings.Fields(sshOriginalCommand)
command := commandFields[0]
commandArgs := commandFields[1:]
for commandIndex = range authCommands.Commands {
if authCommands.Commands[commandIndex].Path == command {
isAllowed = true
break
}
}
if !isAllowed {
fmt.Fprintln(os.Stderr, "command not allowed: ", command)
os.Exit(1)
}
expandedCommandPath, err := homedir.Expand(command)
if err != nil {
fmt.Fprintln(os.Stderr, "can't expand command path: ", err)
os.Exit(1)
}
cmd := exec.Command(expandedCommandPath, commandArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if len(authCommands.Commands[commandIndex].Env) > 0 {
cmd.Env = append(os.Environ(), authCommands.Commands[commandIndex].Env...)
}
if err = cmd.Run(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
fmt.Fprintln(os.Stderr, "execution error: ", err)
os.Exit(exitError.ExitCode())
}
}
}