1 #!@PYTHON@ -tt
2
3 import sys
4 import atexit
5 import socket
6 import struct
7 import logging
8 sys.path.append("@FENCEAGENTSLIBDIR@")
9 from fencing import *
10 from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT
11
12 INT4 = 4
13
14 def open_socket(options):
15 try:
16 if "--inet6-only" in options:
17 protocol = socket.AF_INET6
18 elif "--inet4-only" in options:
19 protocol = socket.AF_INET
20 else:
21 protocol = 0
22 (_, _, _, _, addr) = socket.getaddrinfo( \
23 options["--ip"], options["--ipport"], protocol,
24 0, socket.IPPROTO_TCP, socket.AI_PASSIVE
25 )[0]
26 except socket.gaierror:
27 fail(EC_LOGIN_DENIED)
28
29 if "--ssl-secure" in options or "--ssl-insecure" in options:
30 import ssl
31 sock = socket.socket()
|
(1) Event Sigma main event: |
Certificate validation has been disabled for the `ssl.SSLContext` instance. This allows for the acceptance of a rogue certificate and the potential for a manipulator-in-the-middle (MITM) attack. |
|
(2) Event remediation: |
Modify the `ssl.SSLContext` instance to enable certificate validation: * Explicitly set `ssl.SSLContext.verify_mode` to `ssl.CERT_REQUIRED` or set `ssl.SSLContext.check_hostname` to `True` as enabling `check_hostname` will automatically change the default from `CERT_NONE` to `CERT_REQUIRED`. * Explicitly set `protocol` to `ssl.PROTOCOL_TLS_SERVER` or `ssl.PROTOCOL_TLS_CLIENT`. |
32 sslcx = ssl.create_default_context()
33 if "--ssl-insecure" in options:
34 sslcx.check_hostname = False
35 sslcx.verify_mode = ssl.CERT_NONE
36 conn = sslcx.wrap_socket(sock, server_hostname=options["--ip"])
37 else:
38 conn = socket.socket()
39 conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
40 conn.settimeout(float(options["--shell-timeout"]) or None)
41 try:
42 conn.connect(addr)
43 except socket.error as e:
44 logging.debug(e)
45 fail(EC_LOGIN_DENIED)
46
47 return conn
48
49 def smapi_pack_string(string):
50 return struct.pack("!i%ds" % (len(string)), len(string), string.encode("UTF-8"))
51
52 def prepare_smapi_command(options, smapi_function, additional_args):
53 packet_size = 3*INT4 + len(smapi_function) + len(options["--username"]) + len(options["--password"])
54 for arg in additional_args:
55 packet_size += INT4 + len(arg)
56
57 command = struct.pack("!i", packet_size)
58 command += smapi_pack_string(smapi_function)
59 command += smapi_pack_string(options["--username"])
60 command += smapi_pack_string(options["--password"])
61 for arg in additional_args:
62 command += smapi_pack_string(arg)
63
64 return command
65
66 def get_power_status(conn, options):
67 del conn
68
69 if options.get("--original-action", None) == "monitor":
70 (return_code, reason_code, images_active) = \
71 get_list_of_images(options, "Check_Authentication", None)
72
73 logging.debug("Check_Authenticate (%d,%d)", return_code, reason_code)
74 if return_code == 0:
75 return {}
76 else:
77 fail(EC_LOGIN_DENIED)
78
79 if options["--action"] == "list":
80 # '*' = list all active images
81 options["--plug"] = "*"
82
83 (return_code, reason_code, images_active) = \
84 get_list_of_images(options, "Image_Status_Query", options["--plug"])
85 logging.debug("Image_Status_Query results are (%d,%d)", return_code, reason_code)
86
87 if not options["--action"] == "list":
88 if (return_code == 0) and (reason_code == 0):
89 return "on"
90 elif (return_code == 0) and (reason_code == 12):
91 # We are running always with --missing-as-off because we can not check if image
92 # is defined or not (look at rhbz#1188750)
93 return "off"
94 else:
95 return "unknown"
96 else:
97 (return_code, reason_code, images_defined) = \
98 get_list_of_images(options, "Image_Name_Query_DM", options["--username"])
99 logging.debug("Image_Name_Query_DM results are (%d,%d)", return_code, reason_code)
100
101 return dict([(i, ("", "on" if i in images_active else "off")) for i in images_defined])
102
103 def set_power_status(conn, options):
104 conn = open_socket(options)
105
106 packet = None
107 if options["--action"] == "on":
108 packet = prepare_smapi_command(options, "Image_Activate", [options["--plug"]])
109 elif options["--action"] == "off":
110 packet = prepare_smapi_command(options, "Image_Deactivate", [options["--plug"], "IMMED"])
111 conn.send(packet)
112
113 request_id = struct.unpack("!i", conn.recv(INT4))[0]
114 (output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4))
115 logging.debug("Image_(De)Activate results are (%d,%d)", return_code, reason_code)
116
117 conn.close()
118 return
119
120 def get_list_of_images(options, command, data_as_plug):
121 conn = open_socket(options)
122
123 if data_as_plug is None:
124 packet = prepare_smapi_command(options, command, [])
125 else:
126 packet = prepare_smapi_command(options, command, [data_as_plug])
127
128 conn.send(packet)
129
130 try:
131 request_id = struct.unpack("!i", conn.recv(INT4))[0]
132 (output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4))
133 except struct.error:
134 logging.debug(sys.exc_info())
135 fail_usage("Failed: Unable to connect to {} port: {} SSL: {} \n".format(options["--ip"], options["--ipport"], bool("--ssl" in options)))
136
137 images = set()
138
139 if output_len > 3*INT4:
140 recvflag = socket.MSG_WAITALL if "--ssl-secure" not in options and "--ssl-insecure" not in options else 0
141 array_len = struct.unpack("!i", conn.recv(INT4))[0]
142 data = ""
143
144 while True:
145 read_data = conn.recv(1024, recvflag).decode("UTF-8")
146 data += read_data
147 if array_len == len(data):
148 break
149 elif not read_data:
150 logging.error("Failed: Not enough data read from socket")
151 fail(EC_TIMED_OUT)
152
153 parsed_len = 0
154 while parsed_len < array_len:
155 string_len = struct.unpack("!i", data[parsed_len:parsed_len+INT4].encode("UTF-8"))[0]
156 parsed_len += INT4
157 image_name = struct.unpack("!%ds" % (string_len), data[parsed_len:parsed_len+string_len].encode("UTF-8"))[0].decode("UTF-8")
158 parsed_len += string_len
159 images.add(image_name)
160
161 conn.close()
162 return (return_code, reason_code, images)
163
164 def define_new_opts():
165 all_opt["disable_ssl"] = {
166 "getopt" : "",
167 "longopt" : "disable-ssl",
168 "help" : "--disable-ssl Don't use SSL connection",
169 "required" : "0",
170 "shortdesc" : "Don't use SSL",
171 "order": 2
172 }
173
174 def main():
175 device_opt = ["ipaddr", "login", "passwd", "port", "method", "missing_as_off",
176 "inet4_only", "inet6_only", "ssl", "disable_ssl"]
177
178 atexit.register(atexit_handler)
179 define_new_opts()
180
181 all_opt["ssl"]["help"] = "-z, --ssl Use SSL connection with verifying certificate (Default)"
182
183 all_opt["ipport"]["default"] = "44444"
184 all_opt["shell_timeout"]["default"] = "5"
185 all_opt["missing_as_off"]["default"] = "1"
186 all_opt["ssl"]["default"] = "1"
187 options = check_input(device_opt, process_input(device_opt), other_conditions=True)
188
189 if "--disable-ssl" in options or options["--ssl"] == "0":
190 for k in ["--ssl", "--ssl-secure", "--ssl-insecure"]:
191 if k in options:
192 del options[k]
193
194 if len(options.get("--plug", "")) > 8:
195 fail_usage("Failed: Name of image can not be longer than 8 characters")
196
197 if options["--action"] == "validate-all":
198 sys.exit(0)
199
200 docs = {}
201 docs["shortdesc"] = "Fence agent for use with z/VM Virtual Machines"
202 docs["longdesc"] = """fence_zvmip is a Power Fencing agent for z/VM \
203 SMAPI service via TCP/IP.
204
205 The z/VM SMAPI service must be configured so that the virtual machine running
206 the agent can connect to the service, access the system's directory manager,
207 and shortly thereafter run image_deactivate and image_activate. This involves
208 updating the VSMWORK1 NAMELIST and VSMWORK1 AUTHLIST VMSYS:VSMWORK1 files.
209
210 The NAMELIST entry assigns all the required functions to one nick and should
211 look similar to this:
212
213 :nick.ZVM_FENCE\n.br\n\
214 :list.\n.br\n\
215 IMAGE_ACTIVATE\n.br\n\
216 IMAGE_DEACTIVATE\n.br\n\
217 IMAGE_STATUS_QUERY\n.br\n\
218 CHECK_AUTHENTICATION\n.br\n\
219 IMAGE_NAME_QUERY_DM
220
221
222 The AUTHLIST entry authorizes the user to perform all the functions associated
223 with the nick, and should look similar to this:
224
225 Column 1 Column 66 Column 131
226
227 | | |\n.br\n\
228 V V V
229
230 XXXXXXXX ALL ZVM_FENCE
231
232 where XXXXXXXX is the name of the user in the authuser field of the request.
233
234 Refer to the official z/VM documentation for complete instructions and
235 reference materials.
236 """
237 docs["vendorurl"] = "http://www.ibm.com"
238 show_docs(options, docs)
239
240 run_delay(options)
241 result = fence_action(None, options, set_power_status, get_power_status, get_power_status)
242 sys.exit(result)
243
244 if __name__ == "__main__":
245 main()
246