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):
(13) Event Sigma event: assigning to full_uri
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][Sigma main event][remediation]
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"]))
(4) Event Sigma event: calling requests.models.Response::json taints <return>
(5) Event Sigma event: calling requests.models.Response::json assigns to <return>
(6) Event Sigma event: assigning to data
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 main event][remediation]
69   	        data = resp.json()
70   	    except Exception as e:
71   	        fail_usage("Failed: send_get_request: " + str(e))
(7) Event Sigma event: returning
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][Sigma main event][remediation]
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):
(1) Event Sigma event: calling send_get_request taints '<return>["data"]'
(2) Event Sigma event: calling send_get_request assigns to <return>
(3) Event Sigma event: calling send_get_request assigns to '<return>["data"]'
(8) Event Sigma event: assigning to response
Also see events: [Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma main event][remediation]
85   	    response = send_get_request(options, options["--redfish-uri"])
86   	    if response['ret'] is False:
87   	        return {'ret': False}
(9) Event Sigma event: reading from response with key data
(10) Event Sigma event: assigning to data
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 main event][remediation]
88   	    data = response['data']
89   	
90   	    if 'Systems' not in data:
91   	        # Systems resource not found"
92   	        return {'ret': False}
93   	    else:
(11) Event Sigma event: reading from data
(12) Event Sigma event: calling send_get_request
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 main event][remediation]
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