-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcockpit-auth-max-pane
executable file
·167 lines (135 loc) · 4.39 KB
/
cockpit-auth-max-pane
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
159
160
161
162
163
164
165
166
167
#! /usr/bin/python2
# This program acts as a external authentication helper for Cockpit.
# It is spawned as part of establishing a new Cockpit session on a
# remote machine.
# This programs needs to talk the Cockpit wire protocol on stdin and
# stdout, see send_control and read_control.
# The normal sequence of events is:
#
# - cockpit-ws receives a request for /=HOST without a session cookie
#
# - cockpit-ws redirects to the max-pane /token endpoint with a
# redirect_uri that points back at Cockpit.
#
# - max-pane find its own session cookie in the request to /token and
# redirects back to Cockpit after adding "?access_token=TOKEN" to
# the URL
#
# - cockpit-ws sets its authorization data to "Bearer TOKEN"
#
# - cockpit-ws spawns "cockput-auth-max-pane HOST"
#
# - cockpit-auth-max-pane gets the token from cockpit-ws with a
# "authorize" control message
#
# - cockpit-auth-max-pane uses that token to get the host address and
# root password from the max-pane /api/host endpoint
#
# - cockpit-auth-max-pane resets the cockpit-ws authorization data to
# "Basic root:PASSWORD" "with a "authorize" control message.
#
# - cockpit-auth-max-pane execs "cockpit-ssh ADDRESS"
#
# - cockpit-ssh retrieves the user name and password with a
# "authorize" control message and logs into the remote host
import sys
import os
import json
import urllib2
import base64
import subprocess
def dbg(str):
sys.stderr.write(str + "\n")
def usage():
sys.stderr.write("usage {} host-id\n".format(sys.argv[0]))
sys.exit(os.EX_USAGE)
# Cockpit protocol, encoding and decoding of control messages.
def send_control(msg):
text = json.dumps(msg)
dbg("-> %s" % text)
os.write(1, "{}\n\n{}".format(len(text)+1, text))
def read_size(fd):
sep = '\n'
size = 0
seen = 0
while True:
t = os.read(fd, 1)
if not t:
return 0
if t == '\n':
break
size = (size * 10) + int(t)
seen = seen + 1
if seen > 7:
raise ValueError("Invalid frame: size too long")
return size
def read_control():
size = read_size(1)
data = ""
while size > 0:
d = os.read(1, size)
size = size - len(d)
data = data + d
dbg("<- %s" % data.strip())
return json.loads(data)
# Specific control messages
def send_auth_challenge(challenge):
send_control({
"command": "authorize",
"cookie": "1234", # must be present
"challenge": challenge
})
def send_auth_response(response):
send_control({
"command": "authorize",
"response": response
})
def read_auth_reply():
cmd = read_control()
response = cmd.get("response")
if cmd.get("command") != "authorize" or not response:
raise ValueError("Did not receive a valid authorize command")
return response
def exit_with_problem(problem, message, auth_methods):
send_control({
"command": "init",
"problem": problem,
"message": message,
"auth-method-results": auth_methods
})
sys.exit(1)
# Talking to the Max Pane API
def get_token_from_auth_data(auth_data):
f = auth_data.split(" ")
return f[1]
def api_call(path, token):
dbg("=> %s" % path)
req = urllib2.Request("http://localhost:8080/api/" + path, None,
{ "Authorization": "token %s" % token })
try:
resp = urllib2.urlopen(req).read()
except urllib2.URLError, err:
if hasattr(err, "code"):
if err.code == 401:
exit_with_problem("authentication-failed",
"Token was not valid",
{ "password": "not-tried", "token": "denied" })
elif err.code == 404:
exit_with_problem("access-denied",
"Host with id %s is not known" % args[1],
None)
raise
dbg("<= %s" % resp)
return json.loads(resp)
# Main
def main(args):
if len(args) != 2:
usage()
send_auth_challenge("*")
token = get_token_from_auth_data(read_auth_reply())
data = api_call("host?id=" + args[1], token)
send_auth_response("Basic " + base64.b64encode("root:%s" % data["password"]))
os.environ["COCKPIT_SSH_ALLOW_UNKNOWN"] = "1"
os.execlpe("/usr/libexec/cockpit-ssh", "/usr/libexec/cockpit-ssh", data["host"], os.environ)
if __name__ == '__main__':
main(sys.argv)