1 #!@PYTHON@ -tt
2
3 import sys, re
4 import logging
5 import atexit
6 sys.path.append("@FENCEAGENTSLIBDIR@")
7 from fencing import *
8 from fencing import fail, fail_usage, run_delay, EC_STATUS, SyslogLibHandler
9
10 import requests
11 from requests import HTTPError
12
13 try:
14 import boto3
15 from botocore.exceptions import ConnectionError, ClientError, EndpointConnectionError, NoRegionError, ParamValidationError
16 except ImportError:
17 pass
18
19 logger = logging.getLogger()
20 logger.propagate = False
21 logger.setLevel(logging.INFO)
22 logger.addHandler(SyslogLibHandler())
23 logging.getLogger('botocore.vendored').propagate = False
24
25 status = {
26 "running": "on",
27 "stopped": "off",
28 "pending": "unknown",
29 "stopping": "unknown",
30 "shutting-down": "unknown",
31 "terminated": "unknown"
32 }
33
34 def get_instance_id(options):
35 try:
|
(1) Event Sigma main event: |
The Python application creates a connection to the URL using the insecure HTTP protocol. As a result, application data is transmitted over an insecure channel where it can be read and modified by attackers. |
|
(2) Event remediation: |
Modify the URL passed to the `requests` method to use the `https://` protocol. |
36 token = requests.put('http://169.254.169.254/latest/api/token', headers={"X-aws-ec2-metadata-token-ttl-seconds" : "21600"}).content.decode("UTF-8")
37 r = requests.get('http://169.254.169.254/latest/meta-data/instance-id', headers={"X-aws-ec2-metadata-token" : token}).content.decode("UTF-8")
38 return r
39 except HTTPError as http_err:
40 logger.error('HTTP error occurred while trying to access EC2 metadata server: %s', http_err)
41 except Exception as err:
42 if "--skip-race-check" not in options:
43 logger.error('A fatal error occurred while trying to access EC2 metadata server: %s', err)
44 else:
45 logger.debug('A fatal error occurred while trying to access EC2 metadata server: %s', err)
46 return None
47
48
49 def get_nodes_list(conn, options):
50 logger.debug("Starting monitor operation")
51 result = {}
52 try:
53 if "--filter" in options:
54 filter_key = options["--filter"].split("=")[0].strip()
55 filter_value = options["--filter"].split("=")[1].strip()
56 filter = [{ "Name": filter_key, "Values": [filter_value] }]
57 logging.debug("Filter: {}".format(filter))
58
59 for instance in conn.instances.filter(Filters=filter if 'filter' in vars() else []):
60 instance_name = ""
61 for tag in instance.tags or []:
62 if tag.get("Key") == "Name":
63 instance_name = tag["Value"]
64 try:
65 result[instance.id] = (instance_name, status[instance.state["Name"]])
66 except KeyError as e:
67 if options.get("--original-action") == "list-status":
68 logger.error("Unknown status \"{}\" returned for {} ({})".format(instance.state["Name"], instance.id, instance_name))
69 result[instance.id] = (instance_name, "unknown")
70 except ClientError:
71 fail_usage("Failed: Incorrect Access Key or Secret Key.")
72 except EndpointConnectionError:
73 fail_usage("Failed: Incorrect Region.")
74 except ConnectionError as e:
75 fail_usage("Failed: Unable to connect to AWS: " + str(e))
76 except Exception as e:
77 logger.error("Failed to get node list: %s", e)
78 logger.debug("Monitor operation OK: %s",result)
79 return result
80
81 def get_power_status(conn, options):
82 logger.debug("Starting status operation")
83 try:
84 instance = conn.instances.filter(Filters=[{"Name": "instance-id", "Values": [options["--plug"]]}])
85 state = list(instance)[0].state["Name"]
86 logger.debug("Status operation for EC2 instance %s returned state: %s",options["--plug"],state.upper())
87 try:
88 return status[state]
89 except KeyError as e:
90 logger.error("Unknown status \"{}\" returned".format(state))
91 return "unknown"
92 except ClientError:
93 fail_usage("Failed: Incorrect Access Key or Secret Key.")
94 except EndpointConnectionError:
95 fail_usage("Failed: Incorrect Region.")
96 except IndexError:
97 fail(EC_STATUS)
98 except Exception as e:
99 logger.error("Failed to get power status: %s", e)
100 fail(EC_STATUS)
101
102 def get_self_power_status(conn, instance_id):
103 try:
104 instance = conn.instances.filter(Filters=[{"Name": "instance-id", "Values": [instance_id]}])
105 state = list(instance)[0].state["Name"]
106 if state == "running":
107 logger.debug("Captured my (%s) state and it %s - returning OK - Proceeding with fencing",instance_id,state.upper())
108 return "ok"
109 else:
110 logger.debug("Captured my (%s) state it is %s - returning Alert - Unable to fence other nodes",instance_id,state.upper())
111 return "alert"
112
113 except ClientError:
114 fail_usage("Failed: Incorrect Access Key or Secret Key.")
115 except EndpointConnectionError:
116 fail_usage("Failed: Incorrect Region.")
117 except IndexError:
118 return "fail"
119
120 def set_power_status(conn, options):
121 my_instance = get_instance_id(options)
122 try:
123 if options.get("--skip-os-shutdown", "false").lower() in ["1", "yes", "on", "true"]:
124 shutdown_option = {
125 "SkipOsShutdown": True,
126 "Force": True
127 }
128 else:
129 shutdown_option = {
130 "SkipOsShutdown": False,
131 "Force": True
132 }
133 if (options["--action"]=="off"):
134 if "--skip-race-check" in options or get_self_power_status(conn,my_instance) == "ok":
135 conn.instances.filter(InstanceIds=[options["--plug"]]).stop(**shutdown_option)
136 logger.debug("Called StopInstance API call for %s", options["--plug"])
137 else:
138 logger.debug("Skipping fencing as instance is not in running status")
139 elif (options["--action"]=="on"):
140 conn.instances.filter(InstanceIds=[options["--plug"]]).start()
141 except ParamValidationError:
142 if (options["--action"] == "off"):
143 logger.warning(f"SkipOsShutdown not supported with the current boto3 version {boto3.__version__} - falling back to graceful shutdown")
144 conn.instances.filter(InstanceIds=[options["--plug"]]).stop(Force=True)
145 except Exception as e:
146 logger.debug("Failed to power %s %s: %s", \
147 options["--action"], options["--plug"], e)
148 fail(EC_STATUS)
149
150 def define_new_opts():
151 all_opt["region"] = {
152 "getopt" : "r:",
153 "longopt" : "region",
154 "help" : "-r, --region=[region] Region, e.g. us-east-1",
155 "shortdesc" : "Region.",
156 "required" : "0",
157 "order" : 2
158 }
159 all_opt["access_key"] = {
160 "getopt" : "a:",
161 "longopt" : "access-key",
162 "help" : "-a, --access-key=[key] Access Key",
163 "shortdesc" : "Access Key.",
164 "required" : "0",
165 "order" : 3
166 }
167 all_opt["secret_key"] = {
168 "getopt" : "s:",
169 "longopt" : "secret-key",
170 "help" : "-s, --secret-key=[key] Secret Key",
171 "shortdesc" : "Secret Key.",
172 "required" : "0",
173 "order" : 4
174 }
175 all_opt["filter"] = {
176 "getopt" : ":",
177 "longopt" : "filter",
178 "help" : "--filter=[key=value] Filter (e.g. vpc-id=[vpc-XXYYZZAA])",
179 "shortdesc": "Filter for list-action",
180 "required": "0",
181 "order": 5
182 }
183 all_opt["boto3_debug"] = {
184 "getopt" : "b:",
185 "longopt" : "boto3_debug",
186 "help" : "-b, --boto3_debug=[option] Boto3 and Botocore library debug logging",
187 "shortdesc": "Boto Lib debug",
188 "required": "0",
189 "default": "False",
190 "order": 6
191 }
192 all_opt["skip_race_check"] = {
193 "getopt" : "",
194 "longopt" : "skip-race-check",
195 "help" : "--skip-race-check Skip race condition check",
196 "shortdesc": "Skip race condition check",
197 "required": "0",
198 "order": 7
199 }
200 all_opt["skip_os_shutdown"] = {
201 "getopt" : ":",
202 "longopt" : "skip-os-shutdown",
203 "help" : "--skip-os-shutdown=[true|false] Uses SkipOsShutdown flag",
204 "shortdesc" : "Use SkipOsShutdown flag to stop the EC2 instance",
205 "required" : "0",
206 "default" : "true",
207 "order" : 8
208 }
209
210 # Main agent method
211 def main():
212 conn = None
213
214 device_opt = ["port", "no_password", "region", "access_key", "secret_key", "filter", "boto3_debug", "skip_race_check", "skip_os_shutdown"]
215
216 atexit.register(atexit_handler)
217
218 define_new_opts()
219
220 all_opt["power_timeout"]["default"] = "60"
221
222 options = check_input(device_opt, process_input(device_opt))
223
224 docs = {}
225 docs["shortdesc"] = "Fence agent for AWS (Amazon Web Services)"
226 docs["longdesc"] = "fence_aws is a Power Fencing agent for AWS (Amazon Web\
227 Services). It uses the boto3 library to connect to AWS.\
228 \n.P\n\
229 boto3 can be configured with AWS CLI or by creating ~/.aws/credentials.\n\
230 For instructions see: https://boto3.readthedocs.io/en/latest/guide/quickstart.html#configuration"
231 docs["vendorurl"] = "http://www.amazon.com"
232 show_docs(options, docs)
233
234 run_delay(options)
235
236 if "--debug-file" in options:
237 for handler in logger.handlers:
238 if isinstance(handler, logging.FileHandler):
239 logger.removeHandler(handler)
240 lh = logging.FileHandler(options["--debug-file"])
241 logger.addHandler(lh)
242 lhf = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
243 lh.setFormatter(lhf)
244 lh.setLevel(logging.DEBUG)
245
246 if options["--boto3_debug"].lower() not in ["1", "yes", "on", "true"]:
247 boto3.set_stream_logger('boto3',logging.INFO)
248 boto3.set_stream_logger('botocore',logging.CRITICAL)
249 logging.getLogger('botocore').propagate = False
250 logging.getLogger('boto3').propagate = False
251 else:
252 log_format = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
253 logging.getLogger('botocore').propagate = False
254 logging.getLogger('boto3').propagate = False
255 fdh = logging.FileHandler('/var/log/fence_aws_boto3.log')
256 fdh.setFormatter(log_format)
257 logging.getLogger('boto3').addHandler(fdh)
258 logging.getLogger('botocore').addHandler(fdh)
259 logging.debug("Boto debug level is %s and sending debug info to /var/log/fence_aws_boto3.log", options["--boto3_debug"])
260
261 region = options.get("--region")
262 access_key = options.get("--access-key")
263 secret_key = options.get("--secret-key")
264 try:
265 conn = boto3.resource('ec2', region_name=region,
266 aws_access_key_id=access_key,
267 aws_secret_access_key=secret_key)
268 except Exception as e:
269 if not options.get("--action", "") in ["metadata", "manpage", "validate-all"]:
270 fail_usage("Failed: Unable to connect to AWS: " + str(e))
271 else:
272 pass
273
274 # Operate the fencing device
275 result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list)
276 sys.exit(result)
277
278 if __name__ == "__main__":
279 main()
280