@@ -48,6 +48,8 @@ class AgentFieldsSchema(ma.Schema):
4848 links = ma .fields .List (ma .fields .Nested (LinkSchema ), dump_only = True )
4949 pending_contact = ma .fields .String ()
5050
51+ status = ma .fields .String (dump_only = True )
52+
5153 @ma .pre_load
5254 def remove_nulls (self , in_data , ** _ ):
5355 return {k : v for k , v in in_data .items () if v is not None }
@@ -58,6 +60,7 @@ def remove_properties(self, data, **_):
5860 data .pop ('created' , None )
5961 data .pop ('last_seen' , None )
6062 data .pop ('links' , None )
63+ data .pop ('status' , None )
6164 return data
6265
6366
@@ -85,6 +88,20 @@ def unique(self):
8588 def display_name (self ):
8689 return '{}${}' .format (self .host , self .username )
8790
91+ @property
92+ def status (self ):
93+ now = datetime .now (timezone .utc )
94+ untrusted_buffer = int (self .get_config (name = 'agents' , prop = 'untrusted_timer' ))
95+ time_diff = (now - self .last_seen ).total_seconds ()
96+ expired = time_diff > int (self .sleep_max ) + untrusted_buffer
97+ if self ._marked_for_stop :
98+ # If agent hasn't received the stop instruction yet in a beacon response, it's still pending stop
99+ # Otherwise, if agent has received the stop instruction or takes too long to beacon back, mark as dead
100+ return 'dead' if self ._stop_delivered or expired else 'pending kill'
101+ else :
102+ # If agent hasn't beaconed in since max beacon time + untrusted timer, mark as dead
103+ return 'dead' if expired else 'alive'
104+
88105 @classmethod
89106 def is_global_variable (cls , variable ):
90107 if variable .startswith ('payload:' ):
@@ -139,6 +156,8 @@ def __init__(self, sleep_min=30, sleep_max=60, watchdog=0, platform='unknown', s
139156 self .upstream_dest = self .server
140157 self ._executor_change_to_assign = None
141158 self .log = self .create_logger ('agent' )
159+ self ._marked_for_stop = False
160+ self ._stop_delivered = False
142161
143162 def store (self , ram ):
144163 existing = self .retrieve (ram ['agents' ], self .unique )
@@ -213,15 +232,22 @@ async def heartbeat_modification(self, **kwargs):
213232 # Don't update executors if we're waiting to assign an executor change to the agent.
214233 self .update ('executors' , kwargs .get ('executors' ))
215234
235+ # Check if agent has been marked to stop
236+ if self ._marked_for_stop and not self ._stop_delivered :
237+ self ._stop_delivered = True
238+
216239 async def gui_modification (self , ** kwargs ):
217240 loaded = AgentFieldsSchema (only = ('group' , 'trusted' , 'sleep_min' , 'sleep_max' , 'watchdog' , 'pending_contact' )).load (kwargs )
218241 for k , v in loaded .items ():
219242 self .update (k , v )
220243
221244 async def kill (self ):
222245 self .update ('watchdog' , 1 )
223- self .update ('sleep_min' , 60 * 2 )
224- self .update ('sleep_max' , 60 * 2 )
246+ self .update ('sleep_min' , 3 )
247+ self .update ('sleep_max' , 3 )
248+
249+ self ._marked_for_stop = True
250+ self ._stop_delivered = False
225251
226252 def replace (self , encoded_cmd , file_svc ):
227253 decoded_cmd = b64decode (encoded_cmd ).decode ('utf-8' , errors = 'ignore' ).replace ('\n ' , '' )
0 commit comments