1 #!@PYTHON@ -tt
2
3 # Copyright (c) 2018 Dell Inc. or its subsidiaries. All Rights Reserved.
4
5 # Fence agent for devices that support the Redfish API Specification.
6
7 import sys
8 import re
9 import logging
10 import json
11 import requests
12 import atexit
13 sys.path.append("@FENCEAGENTSLIBDIR@")
14
15 from fencing import *
16 from fencing import fail_usage, run_delay
17
18 GET_HEADERS = {'accept': 'application/json', 'OData-Version': '4.0'}
19 POST_HEADERS = {'content-type': 'application/json', 'accept': 'application/json',
20 'OData-Version': '4.0'}
21
22
23 def get_power_status(conn, options):
24 response = send_get_request(options, options["--systems-uri"])
25 if response['ret'] is False:
26 fail_usage("Couldn't get power information")
27 data = response['data']
28
29 try:
30 logging.debug("PowerState is: " + data[u'PowerState'])
31 except Exception:
32 fail_usage("Unable to get PowerState: " + "https://" + options["--ip"] + ":" + str(options["--ipport"]) + options["--systems-uri"])
33
34 if data[u'PowerState'].strip() == "Off":
35 return "off"
36 else:
37 return "on"
38
39 def set_power_status(conn, options):
40 action = {
41 'on' : "On",
42 'off': "ForceOff",
43 'reboot': "ForceRestart",
44 'diag': "Nmi"
45 }[options.get("original-action") or options["--action"]]
46
47 payload = {'ResetType': action}
48
49 # Search for 'Actions' key and extract URI from it
50 response = send_get_request(options, options["--systems-uri"])
51 if response['ret'] is False:
52 return {'ret': False}
53 data = response['data']
54 action_uri = data["Actions"]["#ComputerSystem.Reset"]["target"]
55
56 response = send_post_request(options, action_uri, payload)
57 if response['ret'] is False:
58 fail_usage("Error sending power command")
59 if options.get("original-action") == "diag":
60 return True
61 return
62
63 def send_get_request(options, uri):
64 full_uri = "https://" + options["--ip"] + ":" + str(options["--ipport"]) + uri
65 try:
66 resp = requests.get(full_uri, verify=not "--ssl-insecure" in options,
67 headers=GET_HEADERS,
68 auth=(options["--username"], options["--password"]))
69 data = resp.json()
70 except Exception as e:
71 fail_usage("Failed: send_get_request: " + str(e))
72 return {'ret': True, 'data': data}
73
74 def send_post_request(options, uri, payload):
75 full_uri = "https://" + options["--ip"] + ":" + str(options["--ipport"]) + uri
76 try:
|
(1) Event Sigma main event: |
The application sets `Basic Authentication` credentials in the HTTP request. This means that an HTTP header containing the Base64 encoded username and password will be included in requests made to the server. This exposes user credentials and prevents token rotation. |
|
(2) Event remediation: |
Modify the application so that it does not rely on `Basic Authentication`. Consider using a modern authentication scheme that that will not leak user credentials easily. |
77 requests.post(full_uri, data=json.dumps(payload),
78 headers=POST_HEADERS, verify=not "--ssl-insecure" in options,
79 auth=(options["--username"], options["--password"]))
80 except Exception as e:
81 fail_usage("Failed: send_post_request: " + str(e))
82 return {'ret': True}
83
84 def find_systems_resource(options):
85 response = send_get_request(options, options["--redfish-uri"])
86 if response['ret'] is False:
87 return {'ret': False}
88 data = response['data']
89
90 if 'Systems' not in data:
91 # Systems resource not found"
92 return {'ret': False}
93 else:
94 response = send_get_request(options, data["Systems"]["@odata.id"])
95 if response['ret'] is False:
96 return {'ret': False}
97 data = response['data']
98
99 # need to be able to handle more than one entry
100 for member in data[u'Members']:
101 system_uri = member[u'@odata.id']
102 return {'ret': True, 'uri': system_uri}
103
104 def define_new_opts():
105 all_opt["redfish-uri"] = {
106 "getopt" : ":",
107 "longopt" : "redfish-uri",
108 "help" : "--redfish-uri=[uri] Base or starting Redfish URI",
109 "required" : "0",
110 "default" : "/redfish/v1",
111 "shortdesc" : "Base or starting Redfish URI",
112 "order": 1
113 }
114 all_opt["systems-uri"] = {
115 "getopt" : ":",
116 "longopt" : "systems-uri",
117 "help" : "--systems-uri=[uri] Redfish Systems resource URI",
118 "required" : "0",
119 "shortdesc" : "Redfish Systems resource URI, i.e. /redfish/v1/Systems/System.Embedded.1",
120 "order": 1
121 }
122
123 def main():
124 atexit.register(atexit_handler)
125 device_opt = ["ipaddr", "login", "passwd", "redfish-uri", "systems-uri",
126 "ssl", "diag"]
127 define_new_opts()
128
129 opt = process_input(device_opt)
130
131 all_opt["ssl"]["default"] = "1"
132 options = check_input(device_opt, opt)
133
134 docs = {}
135 docs["shortdesc"] = "Power Fencing agent for Redfish"
136 docs["longdesc"] = "fence_redfish is a Power Fencing agent which can be used with \
137 Out-of-Band controllers that support Redfish APIs. These controllers provide remote \
138 access to control power on a server."
139 docs["vendorurl"] = "http://www.dmtf.org"
140 show_docs(options, docs)
141 run_delay(options)
142
143 ##
144 ## Operate the fencing device
145 ####
146
147 # Disable insecure-certificate-warning message
148 if "--ssl-insecure" in opt:
149 import urllib3
150 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
151
152 # backwards compatibility for <ip>:<port>
153 if options["--ip"].count(":") == 1:
154 (options["--ip"], options["--ipport"]) = options["--ip"].split(":")
155
156 if "--systems-uri" not in opt:
157 # Systems URI not provided, find it
158 sysresult = find_systems_resource(options)
159 if sysresult['ret'] is False:
160 sys.exit(1)
161 else:
162 options["--systems-uri"] = sysresult["uri"]
163
164 reboot_fn = None
165 if options["--action"] == "diag":
166 # Diag is a special action that can't be verified so we will reuse reboot functionality
167 # to minimize impact on generic library
168 options["original-action"] = options["--action"]
169 options["--action"] = "reboot"
170 options["--method"] = "cycle"
171 reboot_fn = set_power_status
172
173 result = fence_action(None, options, set_power_status, get_power_status, None, reboot_fn)
174 sys.exit(result)
175
176 if __name__ == "__main__":
177 main()
178