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
(1) Event Sigma event: calling send_get_request taints all elements in 'options'
Also see events: [Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma main event][remediation]
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   	
(3) Event Sigma event: calling send_post_request sinks all elements in 'options'
Also see events: [Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma main event][remediation]
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:
(2) Event Sigma event: calling requests.get
Also see events: [Sigma event][Sigma event][Sigma event][Sigma event][Sigma event][Sigma main event][remediation]
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):
(4) Event Sigma event: reading from options
(5) Event Sigma event: assigning to full_uri
Also see events: [Sigma event][Sigma event][Sigma event][Sigma event][Sigma main event][remediation]
75   	    full_uri = "https://" + options["--ip"] + ":" + str(options["--ipport"]) + uri
76   	    try:
CID (unavailable; MK=3b4ebd833a428a318b3441036cd8a440) (#1 of 1): Sensitive Data Leak (SENSITIVE_DATA_LEAK):
(6) Event Sigma event: calling requests.post sinks full_uri
(7) Event Sigma main event: Sensitive data (such as a password) is transmitted or stored outside an application without proper sanitization. An attacker can intercept the transmission to steal the sensitive data (like user credentials, personal information) and exploit it.
(8) Event remediation: Validate that either sensitive data does not go outside the application or the sensitive data is properly sanitized/encrypted before it goes outside the application.
Also see events: [Sigma event][Sigma event][Sigma event][Sigma event][Sigma event]
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