-
Notifications
You must be signed in to change notification settings - Fork 0
/
install-cac-agent.py
338 lines (308 loc) · 13.4 KB
/
install-cac-agent.py
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
import os
import subprocess
import urllib.request
import sys
import ctypes
import re
import platform
import tempfile
from io import StringIO
from datetime import datetime
from pathlib import Path
from shutil import copyfile
from shutil import move
# Removes a section from a file, by matching the first/last
# lines of the section.
def remove_section(in_file, out_file, start_pattern, end_pattern):
"""
Removes a section from a file, by matching the first/last line of the section.
:param file in_file: The file to read (looking for a section to remove)
:param file out_file: The file to write to (effectively a copy of in_file)
:param str start_pattern: A regex expression to match the first line of the section
:param str end_pattern: A regex expression to match the last line of the section
"""
inside_section = False
last_line_of_section = False
for line in in_file.readlines():
# Identify if inside section to replace
if re.match(start_pattern, line):
inside_section = True
elif re.match(end_pattern, line):
inside_section = False
last_line_of_section = True
# Echo out line if not inside section to replacecd
if (not inside_section) and (not last_line_of_section):
out_file.write(line)
last_line_of_section = False
def write_wrapper_script(script_file, args):
if platform.system() == "Linux":
with open(str(script_file), "w") as out_file:
out_file.write("#!/bin/bash\n")
out_file.write("\n")
for arg in args:
out_file.write("\"%s\" " % arg)
# Pass through any script args
out_file.write("$*\n")
out_file.write("\n")
elif platform.system() == "Windows":
with open(str(script_file), "w") as out_file:
out_file.write("\n")
for arg in args:
out_file.write("\"%s\" " % arg)
# Pass through any script args
out_file.write("%*\n")
out_file.write("\n")
else:
raise Exception("OS ["+platform.system()+"] not supported")
# Downloads to the url to the target_file.
# Will not result in a partial file (uses a temp file).
def download_file_if_missing(url, target_file):
if target_file.is_file():
return
target_file_temp = target_file.parent / (target_file.name + ".tmp")
print("Downloading ["+str(target_file)+"]")
print("... from ["+url+"]")
urllib.request.urlretrieve(url, str(target_file_temp))
target_file_temp.rename(target_file)
def rmdir(dir):
"""
Recursively deletes the contents of a directory
See: https://stackoverflow.com/a/49782093
:param Path dir: The directory to delete
"""
dir = Path(dir)
for item in dir.iterdir():
if item.is_dir():
rmdir(item)
else:
item.unlink()
dir.rmdir()
def yes_or_no(question):
"""
Prompt for user input of y/n
:param str question: The prompt to print
"""
answer = input(question + " (y/n): ").lower().strip()
print("")
while not(answer == "y" or answer == "yes" or
answer == "n" or answer == "no"):
print("Input yes or no")
answer = input(question + " (y/n): ").lower().strip()
print("")
if answer[0] == "y":
return True
else:
return False
# Verify OS
if platform.system() != "Linux" and platform.system() != "Windows":
raise Exception("OS ["+platform.system()+"] not supported")
# cac-agent version
CAC_AGENT_VERSION = 1.13
# cac-agent profiles we create (one per middleware)
CAC_AGENT_PROFILES = {
"Linux": (
{
"name": "profile-safenet",
"pkcs_library": "/usr/lib/libeTPkcs11.so"
}, {
"name": "profile-opensc",
"pkcs_library": "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"
}, {
"name": "profile-coolkey",
"pkcs_library": "/usr/lib64/pkcs11/libcoolkeypk11.so"
}
),
"Windows": (
{
# This sets no middleware--uses Window's built-in mechanism.
"name": "profile-default",
"pkcs_library": None
}, {
"name": "profile-safenet",
"pkcs_library": "C:\\Windows\\System32\\eTPKCS11.dll"
}, {
"name": "profile-opensc",
"pkcs_library": "C:\\Program Files\\OpenSC Project\\OpenSC\\pkcs11\\opensc-pkcs11.dll"
}
)
}
# Profiles we used at one point, but no longer use (and actively delete)
DEPRECATED_PROFILE_DIRS = ("moesol-safenet", "moesol-opensc", "moesol-coolkey")
# The path of our python scripts
script_dir = Path(os.path.abspath(__file__)).parent
# The path of active user's home
home_dir = Path.home()
# Place to write temporary files
# NOTE: We moved this outside of the script dir so both the active user
# and the Windows Admin could reach the same dir in the event
# that the scripts are on a mounted drive (which the Admin wouldn't see).
# NOTE: This is not a secured location (other users can access).
temp_dir_insecure = Path(tempfile.gettempdir()) / \
Path(os.path.abspath(__file__)).name
os.makedirs(str(temp_dir_insecure), mode=0o755, exist_ok=True)
# The path of the cac-agent dir within the users's home (where we're installing to)
cacagent_dir = home_dir / ".moesol/cac-agent/"
# The bin directory that includes commands in the user's PATH
bin_dir = home_dir / "bin"
# The OS hosts directory
hosts_dir = Path("c:/Windows/System32/drivers/etc")
# The new path to hosts.temp after being moved to to /etc directory
hosts_temp_file_new = hosts_dir / "hosts.temp"
# The path of the temp file we use when generating the new hosts
hosts_temp_file = temp_dir_insecure / "hosts.temp"
# The filename of a backup of the hosts file
hosts_backup_suffix = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
# The OS hosts file
hosts_file = None
if platform.system() == "Linux":
hosts_file = Path("/etc/hosts")
elif platform.system() == "Windows":
hosts_file = Path("c:/Windows/System32/drivers/etc/hosts")
# Delete deprecated cac-agent dirs
for profile in DEPRECATED_PROFILE_DIRS:
target_file = cacagent_dir / profile
if target_file.exists():
print("Deleteing deprecated ["+str(target_file)+"]")
rmdir(target_file)
# Create cac-agent dirs
for profile in CAC_AGENT_PROFILES[platform.system()]:
target_file = cacagent_dir / (profile["name"])
print("Creating ["+str(target_file)+"]")
os.makedirs(str(target_file), mode=0o700, exist_ok=True)
# Download cac-agent
download_file_if_missing(
"https://github.com/MoebiusSolutions/cac-agent.mvn/blob/master/com/github/MoebiusSolutions/cac-jgit/%s/cac-jgit-%s-jar-with-dependencies.jar?raw=true" % (CAC_AGENT_VERSION, CAC_AGENT_VERSION),
cacagent_dir / ("cac-jgit-%s-jar-with-dependencies.jar" % CAC_AGENT_VERSION) )
download_file_if_missing(
"https://github.com/MoebiusSolutions/cac-agent.mvn/blob/master/com/github/MoebiusSolutions/cac-ssl-relay/%s/cac-ssl-relay-%s-jar-with-dependencies.jar?raw=true" % (CAC_AGENT_VERSION, CAC_AGENT_VERSION),
cacagent_dir / ("cac-ssl-relay-%s-jar-with-dependencies.jar" % CAC_AGENT_VERSION) )
# Ensure bin directory exists
print("Ensuring ["+str(bin_dir)+"] exists")
os.makedirs(str(bin_dir), mode=0o700, exist_ok=True)
# Delete deprecated cac-agent scripts
for profile in DEPRECATED_PROFILE_DIRS:
# cac-jgit
target_file = bin_dir / ("cac-jgit."+profile)
if target_file.exists():
print("Deleting deprecated ["+str(target_file)+"] script")
target_file.unlink()
# cac-jgit...bat
target_file = bin_dir / ("cac-jgit."+profile+".bat")
if target_file.exists():
print("Deleting deprecated ["+str(target_file)+"] script")
target_file.unlink()
# cac-ssl-relay
target_file = bin_dir / ("cac-ssl-relay."+profile)
if target_file.exists():
print("Deleting deprecated ["+str(target_file)+"] script")
target_file.unlink()
# cac-ssl-relay...bat
target_file = bin_dir / ("cac-ssl-relay."+profile+".bat")
if target_file.exists():
print("Deleting deprecated ["+str(target_file)+"] script")
target_file.unlink()
# Create cac-agent scripts
for profile in CAC_AGENT_PROFILES[platform.system()]:
target_file_extension = ".bat" if platform.system() == "Windows" else ""
# cac-jgit
target_file = bin_dir / ("cac-jgit."+profile["name"]+target_file_extension)
print("Creating ["+str(target_file)+"] script")
write_wrapper_script(target_file,
["java", "-jar", "-Dcom.moesol.agent.profile="+profile["name"], str(cacagent_dir/("cac-jgit-%s-jar-with-dependencies.jar" % CAC_AGENT_VERSION))])
os.chmod(str(target_file), mode=0o755)
# cac-ssl-relay
target_file = bin_dir / \
("cac-ssl-relay."+profile["name"]+target_file_extension)
print("Creating ["+str(target_file)+"] script")
write_wrapper_script(target_file,
["java", "-jar", "-Dcom.moesol.agent.profile="+profile["name"], str(cacagent_dir/("cac-ssl-relay-%s-jar-with-dependencies.jar" % CAC_AGENT_VERSION))])
os.chmod(str(target_file), mode=0o755)
# Install truststore.jks, pkcs11.cfg, and agent.properties
listen_port = 9090
for profile in CAC_AGENT_PROFILES[platform.system()]:
listen_port += 1
# truststore.jks
file_name = "truststore.jks"
target_file = cacagent_dir / profile["name"] / file_name
print("Installing ["+str(target_file)+"]")
copyfile(str(script_dir / "cac-agent" / file_name), str(target_file))
# agent.properties
file_name = "agent.properties"
target_file = cacagent_dir / profile["name"] / file_name
print("Installing ["+str(target_file)+"]")
with open(str(script_dir / "cac-agent" / file_name), 'r') as in_file, open(str(target_file), 'w') as out_file:
out_file.write(in_file.read().replace("9090", str(listen_port)))
# pkcs11.cfg
if not profile["pkcs_library"] == None:
file_name = "pkcs11.cfg"
target_file = cacagent_dir / profile["name"] / file_name
print("Installing ["+str(target_file)+"]")
with open(str(target_file), 'w') as out_file:
out_file.write("library="+profile["pkcs_library"]+"\n")
out_file.write("name=cac-agent\n")
# Save a backup of the hosts file
hosts_temp_backup_file = temp_dir_insecure / ("hosts_"+hosts_backup_suffix)
with open(str(hosts_file), 'r') as in_file, open(str(hosts_temp_backup_file.parent / ("hosts_"+hosts_backup_suffix)), 'w') as out_file:
out_file.write(in_file.read())
# Remove old section from hosts file`
with open(str(hosts_file), 'r') as in_file, open(str(hosts_temp_file), 'w') as out_file:
remove_section(in_file, out_file, '^#.*==== CAC-AGENT section start ====.*$',
'^#.*==== CAC-AGENT section end ====.*$')
# Generate hosts file section
hosts_file_section = ""
with open(str(script_dir / "cac-agent" / "hosts"), mode='r', newline='\n') as in_file, StringIO() as out_buffer:
# NOTE: Ensure start/end headers are on their own line (for regex matching)
# NOTE: We write everything using '\n' newlines, and the file writer converts to the appropriate
# OS newlines (per the configuration specified when it was opened)
out_buffer.write(
"\n# ==== CAC-AGENT section start ====\n")
out_buffer.write("# This section will be replaced by the installer\n")
for line in in_file.readlines():
out_buffer.write(line)
out_buffer.write(
"\n# ==== CAC-AGENT section end ====\n")
hosts_file_section = out_buffer.getvalue()
# Append new section to hosts file
with open(str(hosts_temp_file), 'a') as out_file:
out_file.write(hosts_file_section)
print("")
print("[[[ === ATTENTION === ]]]")
print("")
print("The following section must be added to your hosts file")
print("("+str(hosts_file)+").")
print("This will require admin/root access to your machine.")
print(hosts_file_section)
print("")
should_update_hosts_file = yes_or_no(
"Should we attempt to do this automatically now?")
# Install the hosts files
if should_update_hosts_file:
if platform.system() == "Linux":
subprocess.run(["sudo", "install", "-o", "root", "-g", "root", "-m", "0644",
str(hosts_temp_backup_file), str(hosts_file)+"_"+hosts_backup_suffix], check=True)
subprocess.run(["sudo", "install", "-o", "root", "-g", "root", "-m", "0644",
str(hosts_temp_file), str(hosts_file)], check=True)
hosts_temp_backup_file.unlink()
hosts_temp_file.unlink()
elif platform.system() == "Windows":
# Save backup of hosts to system dir
origin = str(hosts_temp_backup_file)
to = str(hosts_file)+"_"+hosts_backup_suffix
ctypes.windll.shell32.ShellExecuteW(None, "runas", "cmd.exe",
"/c move \""+origin+"\" \""+to+"\"", None, 1)
# Replace system hosts file
move_command = "-executionpolicy bypass -File \"%s\" \"%s\" \"%s\" \"%s\"" % (
# Command
str(script_dir / "replace-file-preserving-acls.ps1"),
# Arg: source file
str(hosts_temp_file),
# Arg: target file
str(hosts_file),
# Arg: temp file next to target (to use when assigning permissions)
str(hosts_file)+".temp")
print("powershell.exe "+move_command)
ctypes.windll.shell32.ShellExecuteW(
None, "runas", "powershell.exe", move_command, None, 1)
else:
raise Exception("OS ["+platform.system()+"] not supported")
print("Done.")