1111import os
1212import re
1313import subprocess
14+ import tempfile
1415import uuid
1516
1617from ansible .module_utils .urls import open_url
@@ -207,7 +208,8 @@ def build_multipart(fields, files):
207208class LASClient :
208209 """Client for the LAS (License Activation Service) cloud API."""
209210
210- _BEARER_CACHE = "/tmp/r56_bearer"
211+ # Namespaced by effective user ID to avoid insecure shared /tmp file access.
212+ _BEARER_CACHE = os .path .join (tempfile .gettempdir (), "r56_bearer_{0}" .format (os .geteuid ()))
211213
212214 def __init__ (self , lsguid , secret_file ):
213215 self .endpoint = "netscalerfixedbw"
@@ -317,9 +319,16 @@ def get_blob_from_las(self, newactivationid, lsfingerprint, output_file, bearer,
317319# ---------------------------------------------------------------------------
318320
319321
320- def sftp_get (ip , username , password , remote_path , local_path , loglines ):
322+ def sftp_get (ip , username , password , remote_path , local_path , loglines , host_key_checking = True ):
321323 ssh = paramiko .SSHClient ()
322- ssh .set_missing_host_key_policy (paramiko .AutoAddPolicy ())
324+ ssh .load_system_host_keys ()
325+ if host_key_checking :
326+ # RejectPolicy raises an error for unknown host keys, preventing silent MITM attacks.
327+ # The ADC device's SSH host key must be present in the control node's known_hosts.
328+ ssh .set_missing_host_key_policy (paramiko .RejectPolicy ())
329+ else :
330+ # User explicitly opted out of host key checking (host_key_checking=false).
331+ ssh .set_missing_host_key_policy (paramiko .AutoAddPolicy ())
323332 sftp = None
324333 try :
325334 ssh .connect (ip , username = username , password = password )
@@ -334,9 +343,16 @@ def sftp_get(ip, username, password, remote_path, local_path, loglines):
334343 ssh .close ()
335344
336345
337- def sftp_put (ip , username , password , local_path , remote_path , loglines ):
346+ def sftp_put (ip , username , password , local_path , remote_path , loglines , host_key_checking = True ):
338347 ssh = paramiko .SSHClient ()
339- ssh .set_missing_host_key_policy (paramiko .AutoAddPolicy ())
348+ ssh .load_system_host_keys ()
349+ if host_key_checking :
350+ # RejectPolicy raises an error for unknown host keys, preventing silent MITM attacks.
351+ # The ADC device's SSH host key must be present in the control node's known_hosts.
352+ ssh .set_missing_host_key_policy (paramiko .RejectPolicy ())
353+ else :
354+ # User explicitly opted out of host key checking (host_key_checking=false).
355+ ssh .set_missing_host_key_policy (paramiko .AutoAddPolicy ())
340356 sftp = None
341357 try :
342358 ssh .connect (ip , port = 22 , username = username , password = password )
@@ -427,7 +443,7 @@ def check_if_new_api(mapping, release, major, minor):
427443# ---------------------------------------------------------------------------
428444
429445
430- def get_offline_request_package (nitro , ip , username , password , local_dir , new_api , loglines ):
446+ def get_offline_request_package (nitro , ip , username , password , local_dir , new_api , loglines , host_key_checking = True ):
431447 """Trigger NITRO to generate the NS offline activation request tgz, then SFTP it to local_dir."""
432448 resource = "nslicenseactivationdata?args=usehostname:true" if new_api else "nslicenseactivationdata"
433449 o = nitro .get (resource )
@@ -438,7 +454,7 @@ def get_offline_request_package(nitro, ip, username, password, local_dir, new_ap
438454 return ""
439455
440456 local_path = os .path .join (local_dir , src_file )
441- sftp_get (ip , username , password , "/nsconfig/license/" + src_file , local_path , loglines )
457+ sftp_get (ip , username , password , "/nsconfig/license/" + src_file , local_path , loglines , host_key_checking )
442458 return src_file
443459
444460
@@ -449,7 +465,13 @@ def get_offline_request_package(nitro, ip, username, password, local_dir, new_ap
449465
450466def extract_lsguid (file_path , loglines ):
451467 dest_dir = os .path .dirname (file_path )
468+ # Validate that file_path is within dest_dir to guard against path traversal.
469+ real_file_path = os .path .realpath (file_path )
470+ real_dest_dir = os .path .realpath (dest_dir )
471+ if not real_file_path .startswith (real_dest_dir + os .sep ):
472+ raise RuntimeError ("Invalid file path outside temp directory: {0}" .format (file_path ))
452473 json_file = "ns_offline_activation_request.json"
474+ # shell=False ensures no shell metacharacter interpretation; all args are controlled internally.
453475 cmd = [
454476 "tar" ,
455477 "-xvf" ,
@@ -479,12 +501,12 @@ def extract_lsguid(file_path, loglines):
479501
480502 try :
481503 os .remove (json_path )
482- except Exception :
483- pass
504+ except Exception as e :
505+ loglines . append ( "DEBUG: Could not remove temp file {0}: {1}" . format ( json_path , str ( e )))
484506 try :
485507 os .remove (os .path .join (dest_dir , "lasData.tgz" ))
486- except Exception :
487- pass
508+ except Exception as e :
509+ loglines . append ( "DEBUG: Could not remove temp file lasData.tgz: {0}" . format ( str ( e )))
488510
489511 lsguid = data ["lsguid" ]
490512 loglines .append ("INFO: Extracted lsguid: {0}" .format (lsguid ))
@@ -496,8 +518,8 @@ def extract_lsguid(file_path, loglines):
496518# ---------------------------------------------------------------------------
497519
498520
499- def apply_license_blob_ns (nitro , ip , username , password , fname , loglines ):
500- sftp_put (ip , username , password , fname , "/nsconfig/license/" + fname , loglines )
521+ def apply_license_blob_ns (nitro , ip , username , password , fname , loglines , host_key_checking = True ):
522+ sftp_put (ip , username , password , fname , "/nsconfig/license/" + fname , loglines , host_key_checking )
501523 payload = {
502524 "params" : {"action" : "apply" , "warning" : "YES" },
503525 "nslaslicense" : {"filename" : fname , "filelocation" : "/nsconfig/license" , "fixedbandwidth" : True },
0 commit comments