-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
157 lines (119 loc) · 4.14 KB
/
main.go
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
package main
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"fmt"
"os"
kingpin "gopkg.in/alecthomas/kingpin.v2"
"github.com./aws/aws-sdk-go/aws"
"github.com./davidrjonas/ssh-iam-bridge/directory"
"github.com./davidrjonas/ssh-iam-bridge/unix"
"github.com./kardianos/osext"
)
var version = "1.0.0"
const exitEInval = 22
const exitTempFail = 75
const exitNoPerm = 77
const uidOffset = 2000
func getName() string {
return "ssh-iam-bridge"
}
func getPrefix() string {
return "server-"
}
func awsToUnixID(awsID *string) int {
// Treat the last 2 bytes of a sha256 hash of awsId as an uint and add it to 2000
b := []byte(*awsID)
hasher := sha256.New()
hasher.Write(b)
h := hasher.Sum(nil)
data, _ := binary.ReadUvarint(bytes.NewBuffer(h[len(h)-2:]))
return uidOffset + (int(data) / 2)
}
func getAuthorizedKeys(username string) (*bytes.Buffer, error) {
keys, err := directory.GetActiveSshPublicKeys(username)
if err != nil {
return nil, err
}
var out bytes.Buffer
for _, key := range keys {
body, err := directory.GetSshEncodedPublicKey(key.UserName, key.SSHPublicKeyId)
if err != nil {
return nil, err
}
fmt.Fprintln(&out, "# Key id: ", *key.SSHPublicKeyId)
fmt.Fprintln(&out, *body)
}
return &out, nil
}
func printAuthorizedKeys(username string) error {
buf, err := getAuthorizedKeys(username)
if err != nil {
return err
}
buf.WriteTo(os.Stdout)
return nil
}
func pamCreateUser() {
username := os.Getenv("PAM_USER")
if username == "" {
os.Stderr.WriteString("Unable to find pam user in the environment\n")
os.Exit(exitEInval)
}
// PATH is not set when called from pam.
os.Setenv("PATH", "/usr/bin:/bin:/usr/sbin:/sbin")
if unix.UserExists(username) {
// Supposedly: Terminate the PAM authentication stack. The SSH client
// will fail since the user didn't supply a valid public key.
os.Exit(exitNoPerm)
}
user, err := directory.GetUser(username)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get IAM user '%s'; %s", username, err)
os.Exit(1)
}
err = unix.EnsureUser(username, awsToUnixID(user.UserId), "iam="+aws.StringValue(user.UserId))
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create user '%s'; %s", username, err)
os.Exit(1)
}
syncGroups(getPrefix())
fmt.Println(getName() + ": Your user has been created but you must reconnect to for it to be active.")
fmt.Println(getName() + ": Connect again to log in to your account.")
os.Exit(exitTempFail)
}
func getSelfPath() string {
self, err := osext.Executable()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to determine path to executable; %s", err)
os.Exit(1)
}
return self
}
var (
installCommand = kingpin.Command("install", "Install this program to authenticate SSH connections and create users")
installNoPamFlag = installCommand.Flag("no-pam", "Don't install to PAM (no autocreate user on login, create users on sync)").Bool()
installCommandUser = installCommand.Arg("user", "The user under which to run the AuthorizedKeysCommand, will be created if it doesn't exit").Default("ssh-iam-bridge").String()
authKeysCommand = kingpin.Command("authorized_keys", "Get the authorized_keys from IAM for user")
authKeysCommandUser = authKeysCommand.Arg("user", "The IAM username for which to get keys").Required().String()
syncCommand = kingpin.Command("sync", "Sync the IAM users and groups with the local system")
ignoreMissingUsers = syncCommand.Flag("ignore-missing-users", "Don't create missing system users (same as sync_groups)").Bool()
syncGroupsCommand = kingpin.Command("sync_groups", "Sync only the IAM groups with the local system groups")
pamCreateUserCommand = kingpin.Command("pam_create_user", "Create a user from the env during the sshd pam phase")
)
func main() {
kingpin.Version(version)
switch kingpin.Parse() {
case installCommand.FullCommand():
install(getSelfPath(), *installCommandUser, *installNoPamFlag)
case authKeysCommand.FullCommand():
printAuthorizedKeys(*authKeysCommandUser)
case syncCommand.FullCommand():
sync(getPrefix(), *ignoreMissingUsers)
case syncGroupsCommand.FullCommand():
syncGroups(getPrefix())
case pamCreateUserCommand.FullCommand():
pamCreateUser()
}
}