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:
|
CID (unavailable; MK=4de944c99c59a6c394be497a949d29b1) (#1 of 1): Url Manipulation (URL_MANIPULATION): |
|
(14) Event Sigma event: |
calling requests.get sinks full_uri |
|
(15) Event Sigma main event: |
A user-controllable string is used to specify a URL that is used in a connection. An attacker can modify the URL scheme to access a local file. An attacker can also mount a phishing attack by modifying the URL value to point to a malicious website. |
|
(16) Event remediation: |
URL manipulation vulnerabilities can be addressed by proper input validation. Allow listing the allowed scheme and authority, and deny listing characters that allow unsafe path traversal in the path and query parts of the URL can improve security. |
| Also see events: |
[Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event] |
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:
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