diff --git a/CHANGELOG b/CHANGELOG index d424e02..fc2b08b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,12 @@ Change Log ========== +0.5.3 (01/12/2022) +------------------ +- Take `vpn_username` and `vpn_password` as args +- Create log files only when requested +- Notify upon failure and attach logfile in email + 0.5.2 (01/10/2022) ------------------ - Disable printing final config when logged in a file diff --git a/docs/index.html b/docs/index.html index b55dc68..4b41d1e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -66,7 +66,7 @@

Welcome to VPN Server’s documentation!

VPN Server

-class vpn.controller.VPNServer(aws_access_key: Optional[str] = None, aws_secret_key: Optional[str] = None, aws_region_name: str = 'us-west-2', log: str = 'CONSOLE', gmail_user: Optional[str] = None, gmail_pass: Optional[str] = None, phone: Optional[str] = None, recipient: Optional[str] = None)
+class vpn.controller.VPNServer(aws_access_key: Optional[str] = None, aws_secret_key: Optional[str] = None, aws_region_name: str = 'us-west-2', log: str = 'CONSOLE', vpn_username: Optional[str] = None, vpn_password: Optional[str] = None, gmail_user: Optional[str] = None, gmail_pass: Optional[str] = None, phone: Optional[str] = None, recipient: Optional[str] = None)

Initiates VPNServer object to spin up an EC2 instance with a pre-configured AMI which serves as a VPN server.

>>> VPNServer
 
@@ -125,17 +125,17 @@

Welcome to VPN Server’s documentation!
-_configure_vpn(data: dict) tuple
+_configure_vpn(data: dict) bool

Frames a dictionary of anticipated prompts and responses to initiate interactive SSH commands.

Parameters

data – A dictionary with key, value pairs with instance information in it.

Returns
-

A tuple of vpn_username and vpn_password to trigger the notification.

+

A boolean flag to indicate whether the interactive ssh session succeeded.

Return type
-

tuple

+

bool

@@ -266,12 +266,15 @@

Welcome to VPN Server’s documentation!
-_notify(login_details: str) None
+_notify(message: str, attachment: Optional[str] = None) None

Send login details via SMS and Email if the following env vars are present.

gmail_user, gmail_pass and phone [or] recipient

Parameters
-

login_details – Login information that has to be sent as a message/email.

+
    +
  • message – Login information that has to be sent as a message/email.

  • +
  • attachment – Name of the log file in case of a failure.

  • +
@@ -405,8 +408,13 @@

Welcome to VPN Server’s documentation!
-vpn.helper.logging_wrapper() tuple
+vpn.helper.logging_wrapper(file: bool = False) tuple

Wraps logging module to create multiple handlers for different purposes.

+
+
Parameters
+

file – Takes a boolean flag to determine if a file logger should be created.

+
+

See also

    diff --git a/docs/searchindex.js b/docs/searchindex.js index 7cef6ec..46a7698 100644 --- a/docs/searchindex.js +++ b/docs/searchindex.js @@ -1 +1 @@ -Search.setIndex({docnames:["README","index"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["README.md","index.rst"],objects:{"vpn.controller":[[1,1,1,"","VPNServer"]],"vpn.controller.VPNServer":[[1,2,1,"","_authorize_security_group"],[1,2,1,"","_configure_vpn"],[1,2,1,"","_create_ec2_instance"],[1,2,1,"","_create_key_pair"],[1,2,1,"","_create_security_group"],[1,2,1,"","_delete_key_pair"],[1,2,1,"","_delete_security_group"],[1,2,1,"","_get_image_id"],[1,2,1,"","_get_vpc_id"],[1,2,1,"","_instance_info"],[1,2,1,"","_notification_response"],[1,2,1,"","_notify"],[1,2,1,"","_sleeper"],[1,2,1,"","_terminate_ec2_instance"],[1,2,1,"","_tester"],[1,2,1,"","create_vpn_server"],[1,2,1,"","delete_vpn_server"],[1,2,1,"","reconfigure_vpn"],[1,2,1,"","test_vpn"]],"vpn.defaults":[[1,1,1,"","AWSDefaults"]],"vpn.helper":[[1,3,1,"","interactive_ssh"],[1,3,1,"","logging_wrapper"],[1,3,1,"","time_converter"]],vpn:[[1,0,0,"-","controller"],[1,0,0,"-","defaults"],[1,0,0,"-","helper"]]},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:function"},terms:{"0":0,"1194":1,"2":[0,1],"22":1,"30":1,"443":[0,1],"943":[0,1],"945":1,"boolean":1,"class":1,"default":0,"do":0,"float":1,"import":0,"int":1,"new":[0,1],"null":[0,1],"public":1,"return":[0,1],"true":1,"var":[0,1],A:1,If:[0,1],No:0,To:0,Will:0,_:0,_authorize_security_group:1,_configure_vpn:1,_create_ec2_inst:1,_create_key_pair:1,_create_security_group:1,_delete_key_pair:1,_delete_security_group:1,_get_image_id:1,_get_vpc_id:1,_instance_info:1,_notifi:1,_notification_respons:1,_sleeper:1,_terminate_ec2_inst:1,_tester:1,access:[0,1],access_kei:0,account:[0,1],activ:0,add:1,address:[0,1],admin:0,after:1,agreement:0,all:0,alreadi:1,also:1,ami:[0,1],ami_id:1,ami_id_u:0,an:[0,1],anoth:1,anticip:1,api:1,applic:0,appropri:1,ar:[0,1],argument:1,assign:1,authent:[0,1],author:1,avail:0,aws_access_kei:1,aws_region_nam:1,aws_secret_kei:1,awsdefault:1,awsvpn2021:0,befor:1,blank:0,bool:1,boto3:1,cach:0,call:1,can:[0,1],certain:1,channel:1,check:1,client:[0,1],cloudp:0,cluster:1,command:1,commit:0,commun:0,complet:1,config:0,configur:[0,1],confirm:0,connect:[0,1],consol:1,consolelogg:1,continu:0,control:[0,1],convert:1,copyright:1,creat:[0,1],create_vpn_serv:[0,1],creation:0,credenti:1,current:0,daemon:0,dai:1,data:1,db:0,delet:[0,1],delete_vpn_serv:[0,1],demand:0,deploy:1,describe_instance_statu:1,detail:1,determin:1,dict:1,dictionari:1,differ:1,directori:0,disabl:1,displai:1,dn:[0,1],doc:0,done:0,down:1,download:0,durat:1,dure:[0,1],dynam:1,ec2:[0,1],email:[0,1],ensur:0,enter:0,env:1,environ:[0,1],everi:0,exist:[0,1],exit:1,fail:1,failur:1,fals:1,fastest:0,fetch:1,file:[0,1],filelogg:1,filenam:1,firewal:1,flag:1,follow:1,format:1,forward:0,frame:1,from:[0,1],get:[0,1],gmail:[0,1],gmail_pass:1,gmail_us:1,gmailconnector:1,got:0,group:1,ha:[0,1],handler:1,hostnam:1,hour:1,id:[0,1],imag:[0,1],image_id:1,index:1,indic:0,info:[0,1],inform:[0,1],ingress:1,initi:1,instal:1,instanc:[0,1],instance_id:1,interact:1,interactive_ssh:1,interfac:[0,1],intern:0,ip:[0,1],json:1,kei:[0,1],keypair:[0,1],later:0,leav:0,licens:1,link:1,lint:1,list:1,live:[0,1],load:0,local:0,locat:0,log:1,logger:1,logging_wrapp:1,login:[0,1],login_detail:1,look:0,made:1,make:1,manual:[0,1],messag:1,method:1,minut:1,miss:1,mit:0,modifi:1,modul:1,more:0,multipl:1,name:[0,1],nano:1,need:0,network:0,node:0,none:1,notif:[0,1],notifi:1,number:[0,1],object:1,onc:0,onli:1,open:1,openssh:1,openvpn:[0,1],option:[0,1],origin:1,packag:0,page:1,pair:1,paramet:1,partial:1,particular:1,pass:[0,1],password:[0,1],pem:1,pem_fil:1,per:0,phone:[0,1],pick:0,pip:0,platform:1,pleas:0,port:[0,1],pre:[0,1],precommit:0,present:[0,1],previous:1,primari:0,print:1,privat:0,prompt:1,prompts_and_respons:1,purpos:1,python:0,rao:0,re:0,reachabl:1,recipi:[0,1],recommonmark:0,reconfigure_vpn:[0,1],regardless:1,region:[0,1],region_nam:0,remov:1,repo:1,repositori:0,request:1,requir:0,resourc:1,respond:1,respons:1,retri:1,rout:0,rsa:1,run:[0,1],runbook:0,same:1,screen:1,script:1,search:1,second:1,secret:1,secret_kei:0,secur:1,security_group_id:1,securitygroup:[0,1],send:1,sent:[0,1],serv:1,session:1,setup:0,should:[0,1],sinc:0,sivanandha:0,sleep:1,sleep_tim:1,sm:1,specifi:0,sphinx:0,spin:1,ssh:1,start:[0,1],startup:1,stat:1,statu:1,stdout:1,store:1,str:1,subnet:0,succe:1,success:1,successfulli:1,support:1,sure:0,t2:1,take:1,tcp:[0,1],tear:1,termin:1,test:[0,1],test_vpn:[0,1],thi:[0,1],through:0,time:1,time_convert:1,timeout:1,token:1,traffic:0,trigger:1,tupl:1,type:[0,1],udp:1,ui:0,unabl:1,under:0,up:[0,1],updat:1,upgrad:0,us:1,usag:1,user:[0,1],usernam:[0,1],valu:1,variabl:1,via:[0,1],vicki:0,vignesh:0,vm:1,vpc:[0,1],vpn_info:1,vpn_password:1,vpn_server:0,vpn_usernam:1,vpnserver:[0,1],wa:1,want:0,web:[0,1],west:[0,1],when:1,where:[0,1],whether:1,which:[0,1],wish:0,work:0,wrap:1,write:1,ye:0,you:0,your:0},titles:["Platform Supported","Welcome to VPN Server\u2019s documentation!"],titleterms:{"default":1,"function":1,aw:[0,1],copyright:0,deploy:0,document:1,env:0,helper:1,indic:1,instal:0,licens:0,link:0,lint:0,me:1,platform:0,read:1,repo:0,resourc:0,s:1,server:[0,1],stat:0,support:0,tabl:1,us:0,usag:0,variabl:0,vpn:[0,1],welcom:1}}) \ No newline at end of file +Search.setIndex({docnames:["README","index"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":4,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["README.md","index.rst"],objects:{"vpn.controller":[[1,1,1,"","VPNServer"]],"vpn.controller.VPNServer":[[1,2,1,"","_authorize_security_group"],[1,2,1,"","_configure_vpn"],[1,2,1,"","_create_ec2_instance"],[1,2,1,"","_create_key_pair"],[1,2,1,"","_create_security_group"],[1,2,1,"","_delete_key_pair"],[1,2,1,"","_delete_security_group"],[1,2,1,"","_get_image_id"],[1,2,1,"","_get_vpc_id"],[1,2,1,"","_instance_info"],[1,2,1,"","_notification_response"],[1,2,1,"","_notify"],[1,2,1,"","_sleeper"],[1,2,1,"","_terminate_ec2_instance"],[1,2,1,"","_tester"],[1,2,1,"","create_vpn_server"],[1,2,1,"","delete_vpn_server"],[1,2,1,"","reconfigure_vpn"],[1,2,1,"","test_vpn"]],"vpn.defaults":[[1,1,1,"","AWSDefaults"]],"vpn.helper":[[1,3,1,"","interactive_ssh"],[1,3,1,"","logging_wrapper"],[1,3,1,"","time_converter"]],vpn:[[1,0,0,"-","controller"],[1,0,0,"-","defaults"],[1,0,0,"-","helper"]]},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","function","Python function"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:function"},terms:{"0":0,"1194":1,"2":[0,1],"22":1,"30":1,"443":[0,1],"943":[0,1],"945":1,"boolean":1,"case":1,"class":1,"default":0,"do":0,"float":1,"import":0,"int":1,"new":[0,1],"null":[0,1],"public":1,"return":[0,1],"true":1,"var":[0,1],A:1,If:[0,1],No:0,To:0,Will:0,_:0,_authorize_security_group:1,_configure_vpn:1,_create_ec2_inst:1,_create_key_pair:1,_create_security_group:1,_delete_key_pair:1,_delete_security_group:1,_get_image_id:1,_get_vpc_id:1,_instance_info:1,_notifi:1,_notification_respons:1,_sleeper:1,_terminate_ec2_inst:1,_tester:1,access:[0,1],access_kei:0,account:[0,1],activ:0,add:1,address:[0,1],admin:0,after:1,agreement:0,all:0,alreadi:1,also:1,ami:[0,1],ami_id:1,ami_id_u:0,an:[0,1],anoth:1,anticip:1,api:1,applic:0,appropri:1,ar:[0,1],argument:1,assign:1,attach:1,authent:[0,1],author:1,avail:0,aws_access_kei:1,aws_region_nam:1,aws_secret_kei:1,awsdefault:1,awsvpn2021:0,befor:1,blank:0,bool:1,boto3:1,cach:0,call:1,can:[0,1],certain:1,channel:1,check:1,client:[0,1],cloudp:0,cluster:1,command:1,commit:0,commun:0,complet:1,config:0,configur:[0,1],confirm:0,connect:[0,1],consol:1,consolelogg:1,continu:0,control:[0,1],convert:1,copyright:1,creat:[0,1],create_vpn_serv:[0,1],creation:0,credenti:1,current:0,daemon:0,dai:1,data:1,db:0,delet:[0,1],delete_vpn_serv:[0,1],demand:0,deploy:1,describe_instance_statu:1,detail:1,determin:1,dict:1,dictionari:1,differ:1,directori:0,disabl:1,displai:1,dn:[0,1],doc:0,done:0,down:1,download:0,durat:1,dure:[0,1],dynam:1,ec2:[0,1],email:[0,1],ensur:0,enter:0,env:1,environ:[0,1],everi:0,exist:[0,1],exit:1,fail:1,failur:1,fals:1,fastest:0,fetch:1,file:[0,1],filelogg:1,filenam:1,firewal:1,flag:1,follow:1,format:1,forward:0,frame:1,from:[0,1],get:[0,1],gmail:[0,1],gmail_pass:1,gmail_us:1,gmailconnector:1,got:0,group:1,ha:[0,1],handler:1,hostnam:1,hour:1,id:[0,1],imag:[0,1],image_id:1,index:1,indic:0,info:[0,1],inform:[0,1],ingress:1,initi:1,instal:1,instanc:[0,1],instance_id:1,interact:1,interactive_ssh:1,interfac:[0,1],intern:0,ip:[0,1],json:1,kei:[0,1],keypair:[0,1],later:0,leav:0,licens:1,link:1,lint:1,list:1,live:[0,1],load:0,local:0,locat:0,log:1,logger:1,logging_wrapp:1,login:[0,1],look:0,made:1,make:1,manual:[0,1],messag:1,method:1,minut:1,miss:1,mit:0,modifi:1,modul:1,more:0,multipl:1,name:[0,1],nano:1,need:0,network:0,node:0,none:1,notif:[0,1],notifi:1,number:[0,1],object:1,onc:0,onli:1,open:1,openssh:1,openvpn:[0,1],option:[0,1],origin:1,packag:0,page:1,pair:1,paramet:1,partial:1,particular:1,pass:[0,1],password:[0,1],pem:1,pem_fil:1,per:0,phone:[0,1],pick:0,pip:0,platform:1,pleas:0,port:[0,1],pre:[0,1],precommit:0,present:[0,1],previous:1,primari:0,print:1,privat:0,prompt:1,prompts_and_respons:1,purpos:1,python:0,rao:0,re:0,reachabl:1,recipi:[0,1],recommonmark:0,reconfigure_vpn:[0,1],regardless:1,region:[0,1],region_nam:0,remov:1,repo:1,repositori:0,request:1,requir:0,resourc:1,respond:1,respons:1,retri:1,rout:0,rsa:1,run:[0,1],runbook:0,same:1,screen:1,script:1,search:1,second:1,secret:1,secret_kei:0,secur:1,security_group_id:1,securitygroup:[0,1],send:1,sent:[0,1],serv:1,session:1,setup:0,should:[0,1],sinc:0,sivanandha:0,sleep:1,sleep_tim:1,sm:1,specifi:0,sphinx:0,spin:1,ssh:1,start:[0,1],startup:1,stat:1,statu:1,stdout:1,store:1,str:1,subnet:0,succe:1,succeed:1,success:1,successfulli:1,support:1,sure:0,t2:1,take:1,tcp:[0,1],tear:1,termin:1,test:[0,1],test_vpn:[0,1],thi:[0,1],through:0,time:1,time_convert:1,timeout:1,token:1,traffic:0,tupl:1,type:[0,1],udp:1,ui:0,unabl:1,under:0,up:[0,1],updat:1,upgrad:0,us:1,usag:1,user:[0,1],usernam:[0,1],valu:1,variabl:1,via:[0,1],vicki:0,vignesh:0,vm:1,vpc:[0,1],vpn_info:1,vpn_password:1,vpn_server:0,vpn_usernam:1,vpnserver:[0,1],wa:1,want:0,web:[0,1],west:[0,1],when:1,where:[0,1],whether:1,which:[0,1],wish:0,work:0,wrap:1,write:1,ye:0,you:0,your:0},titles:["Platform Supported","Welcome to VPN Server\u2019s documentation!"],titleterms:{"default":1,"function":1,aw:[0,1],copyright:0,deploy:0,document:1,env:0,helper:1,indic:1,instal:0,licens:0,link:0,lint:0,me:1,platform:0,read:1,repo:0,resourc:0,s:1,server:[0,1],stat:0,support:0,tabl:1,us:0,usag:0,variabl:0,vpn:[0,1],welcom:1}}) \ No newline at end of file diff --git a/version.py b/version.py index fdaa4e9..0b53191 100644 --- a/version.py +++ b/version.py @@ -1 +1 @@ -version_info = (0, 5, 2) +version_info = (0, 5, 3) diff --git a/vpn/controller.py b/vpn/controller.py index 689efd9..63915ff 100644 --- a/vpn/controller.py +++ b/vpn/controller.py @@ -33,6 +33,7 @@ class VPNServer: def __init__(self, aws_access_key: str = environ.get('ACCESS_KEY'), aws_secret_key: str = environ.get('SECRET_KEY'), aws_region_name: str = environ.get('REGION_NAME', 'us-west-2'), log: str = 'CONSOLE', + vpn_username: str = environ.get('VPN_USERNAME'), vpn_password: str = environ.get('VPN_PASSWORD'), gmail_user: str = environ.get('gmail_user'), gmail_pass: str = environ.get('gmail_pass'), phone: str = environ.get('phone'), recipient: str = environ.get('recipient')): """Assigns a name to the PEM file, initiates the logger, client and resource for EC2 using ``boto3`` module. @@ -51,14 +52,31 @@ def __init__(self, aws_access_key: str = environ.get('ACCESS_KEY'), aws_secret_k - If no values (for aws authentication) are passed during object initialization, script checks for env vars. - If the environment variables are ``null``, gets the default credentials from ``~/.aws/credentials``. """ + # AWS client and resource setup + self.region = aws_region_name.lower() + if not AWSDefaults.REGIONS.get(self.region): + raise ValueError(f'Incorrect region name. {aws_region_name} does not exist.') + self.ec2_client = client(service_name='ec2', region_name=self.region, + aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) + self.ec2_resource = resource(service_name='ec2', region_name=self.region, + aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) + self.port = int(environ.get('VPN_PORT', 943)) + + # Login credentials setup + self.vpn_username = vpn_username or environ.get('USER', 'openvpn') + self.vpn_password = vpn_password or 'awsVPN2021' + # Logger setup - file_logger, console_logger, hybrid_logger = logging_wrapper() if log.upper() == 'CONSOLE': + file_logger, console_logger, hybrid_logger, log_file = logging_wrapper() self.logger = console_logger elif log.upper() == 'FILE': + file_logger, console_logger, hybrid_logger, log_file = logging_wrapper(file=True) self.logger = file_logger else: + file_logger, console_logger, hybrid_logger, log_file = logging_wrapper(file=True) self.logger = hybrid_logger + self.log_file = log_file # Notification information self.gmail_user = gmail_user @@ -66,16 +84,6 @@ def __init__(self, aws_access_key: str = environ.get('ACCESS_KEY'), aws_secret_k self.recipient = recipient self.phone = phone - # AWS client and resource setup - self.region = aws_region_name.lower() - if not AWSDefaults.REGIONS.get(self.region): - raise ValueError(f'Incorrect region name. {aws_region_name} does not exist.') - self.ec2_client = client(service_name='ec2', region_name=self.region, - aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) - self.ec2_resource = resource(service_name='ec2', region_name=self.region, - aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) - self.port = int(environ.get('VPN_PORT', 943)) - def __del__(self): """Destructor to print the run time at the end.""" self.logger.info(f'Total runtime: {time_converter(perf_counter())}') @@ -425,17 +433,6 @@ def _instance_info(self, instance_id: str) -> tuple or None: instance_info.public_ip_address, instance_info.private_ip_address) - def _notification_response(self, response: Response) -> None: - """Logs the response after sending notifications. - - Args: - response: Takes the response dictionary to log the success/failure message. - """ - if response.ok: - self.logger.info(response.body) - else: - self.logger.error(response.json()) - def _tester(self, data: dict) -> bool: """Tests ``GET`` and ``SSH`` connections on the existing server. @@ -509,9 +506,9 @@ def create_vpn_server(self) -> None: self.logger.warning('Received a second request to spin up a new VPN Server. Proceeding this time.') else: data_exist.update({'RETRY': True}) - self._notify(login_details=f"CURRENTLY SERVING: {data_exist.get('SERVER').lstrip('https://')}\n\n" - f"Username: {data_exist.get('USERNAME')}\n" - f"Password: {data_exist.get('PASSWORD')}") + self._notify(message=f"CURRENTLY SERVING: {data_exist.get('SERVER').lstrip('https://')}\n\n" + f"Username: {data_exist.get('USERNAME')}\n" + f"Password: {data_exist.get('PASSWORD')}") with open(f'{CURRENT_DIR}vpn_info.json', 'w') as file: dump(data_exist, file, indent=2) return @@ -519,10 +516,6 @@ def create_vpn_server(self) -> None: self.logger.error('Existing server is not responding. Creating a new one.') self.delete_vpn_server(partial=True) - if not all([self.gmail_user, self.gmail_pass, self.phone, self.recipient]): - self.logger.warning('Env vars for notifications are missing! ' - 'Credentials will be stored in vpn_info.json file.') - if not (instance_basic := self._create_ec2_instance()): return instance_id, security_group_id = instance_basic @@ -548,36 +541,38 @@ def create_vpn_server(self) -> None: self.logger.info('Waiting for SSH origin to be active.') self._sleeper(sleep_time=15) - vpn_username, vpn_password = self._configure_vpn(data=instance_info) + if not self._configure_vpn(data=instance_info): + self.logger.warning('Unknown error occurred during configuration. Testing connecting to server.') if not self._tester(data=instance_info): - self.logger.error('Unable to connect VPN server. Please check the logs for more information.') + if 'FILE' in str(self.logger): + self._notify(message='Failed to configure VPN server. Please check the logs for more information.', + attachment=self.log_file) return self.logger.info('VPN server has been configured successfully. Details have been stored in vpn_info.json.') url = f"https://{instance_info.get('public_ip')}" - instance_info.update({'SERVER': f"{url}:{self.port}", 'USERNAME': vpn_username, 'PASSWORD': vpn_password}) + instance_info.update({'SERVER': f"{url}:{self.port}", + 'USERNAME': self.vpn_username, + 'PASSWORD': self.vpn_password}) with open(f'{CURRENT_DIR}vpn_info.json', 'w') as file: dump(instance_info, file, indent=2) - self._notify(login_details=f"SERVER: {public_ip}:{self.port}\n\n" - f"Username: {vpn_username}\n" - f"Password: {vpn_password}") + self._notify(message=f"SERVER: {public_ip}:{self.port}\n\n" + f"Username: {self.vpn_username}\n" + f"Password: {self.vpn_password}") - def _configure_vpn(self, data: dict) -> tuple: + def _configure_vpn(self, data: dict) -> bool: """Frames a dictionary of anticipated prompts and responses to initiate interactive SSH commands. Args: data: A dictionary with key, value pairs with instance information in it. Returns: - tuple: - A tuple of ``vpn_username`` and ``vpn_password`` to trigger the notification. + bool: + A boolean flag to indicate whether the interactive ssh session succeeded. """ self.logger.info('Configuring VPN server.') - if not (vpn_username := environ.get('VPN_USERNAME')): - vpn_username = environ.get('USER', 'openvpn') - vpn_password = environ.get('VPN_PASSWORD', 'awsVPN2021') configuration = { "1|Please enter 'yes' to indicate your agreement \\[no\\]: ": ("yes", 10), @@ -590,32 +585,31 @@ def _configure_vpn(self, data: dict) -> tuple: "8|> Press ENTER for default \\[yes\\]: ": ("yes", 2), "9|> Press ENTER for EC2 default \\[yes\\]: ": ("yes", 2), "10|> Press ENTER for default \\[yes\\]: ": ("no", 2), - "11|> Specify the username for an existing user or for the new user account: ": [vpn_username, 2], - f"12|Type the password for the '{vpn_username}' account:": [vpn_password, 2], - f"13|Confirm the password for the '{vpn_username}' account:": [vpn_password, 2], + "11|> Specify the username for an existing user or for the new user account: ": [self.vpn_username, 2], + f"12|Type the password for the '{self.vpn_username}' account:": [self.vpn_password, 2], + f"13|Confirm the password for the '{self.vpn_username}' account:": [self.vpn_password, 2], "14|> Please specify your Activation key \\(or leave blank to specify later\\): ": ("\n", 2) } - interactive_ssh(hostname=data.get('public_dns'), - username='root', - pem_file='OpenVPN.pem', - logger=self.logger, - prompts_and_response=configuration) - - return vpn_username, vpn_password + return interactive_ssh(hostname=data.get('public_dns'), + username='root', + pem_file='OpenVPN.pem', + logger=self.logger, + prompts_and_response=configuration) - def _notify(self, login_details: str) -> None: + def _notify(self, message: str, attachment: str = None) -> None: """Send login details via SMS and Email if the following env vars are present. ``gmail_user``, ``gmail_pass`` and ``phone [or] recipient`` Args: - login_details: Login information that has to be sent as a message/email. + message: Login information that has to be sent as a message/email. + attachment: Name of the log file in case of a failure. """ subject = f"VPN Server::{datetime.now().strftime('%B %d, %Y %I:%M %p')}" if self.phone: sms_response = Messenger(gmail_user=self.gmail_user, gmail_pass=self.gmail_pass, phone=self.phone, - subject=subject, message=login_details).send_sms() + subject=subject, message=message).send_sms() self._notification_response(response=sms_response) else: @@ -623,11 +617,23 @@ def _notify(self, login_details: str) -> None: if self.recipient: email_response = SendEmail(gmail_user=self.gmail_user, gmail_pass=self.gmail_pass, recipient=self.recipient, - subject=subject, body=login_details, sender='VPNServer').send_email() + subject=subject, body=message, sender='VPNServer', + attachment=attachment).send_email() self._notification_response(response=email_response) else: self.logger.warning('ENV vars are not configured for an email notification.') + def _notification_response(self, response: Response) -> None: + """Logs the response after sending notifications. + + Args: + response: Takes the response dictionary to log the success/failure message. + """ + if response.ok: + self.logger.info(response.body) + else: + self.logger.error(response.json()) + def delete_vpn_server(self, partial: bool = False) -> None: """Disables VPN server by terminating the ``EC2`` instance, ``KeyPair``, and the ``SecurityGroup`` created. diff --git a/vpn/helper.py b/vpn/helper.py index e81819c..9772038 100644 --- a/vpn/helper.py +++ b/vpn/helper.py @@ -40,9 +40,12 @@ def time_converter(seconds: float) -> str: return f'{seconds} seconds' -def logging_wrapper() -> tuple: +def logging_wrapper(file: bool = False) -> tuple: """Wraps logging module to create multiple handlers for different purposes. + Args: + file: Takes a boolean flag to determine if a file logger should be created. + See Also: - fileLogger: Writes the log information only to the log file. - consoleLogger: Writes the log information only in stdout. @@ -58,26 +61,27 @@ def logging_wrapper() -> tuple: datefmt=DATETIME_FORMAT ) - log_file = datetime.now().strftime(f'{CURRENT_DIR}logs{path.sep}vpn_server_%d_%m_%Y_%H_%M.log') - - file_logger = logging.getLogger('FILE') console_logger = logging.getLogger('CONSOLE') - hybrid_logger = logging.getLogger('HYBRID') - - file_handler = logging.FileHandler(filename=log_file) - file_handler.setFormatter(fmt=log_formatter) - file_logger.setLevel(level=logging.INFO) - file_logger.addHandler(hdlr=file_handler) - console_handler = logging.StreamHandler() console_handler.setFormatter(fmt=log_formatter) console_logger.setLevel(level=logging.INFO) console_logger.addHandler(hdlr=console_handler) - hybrid_logger.addHandler(hdlr=file_handler) - hybrid_logger.addHandler(hdlr=console_handler) - hybrid_logger.setLevel(level=logging.INFO) - return file_logger, console_logger, hybrid_logger + if file: + file_logger = logging.getLogger('FILE') + log_file = datetime.now().strftime(f'{CURRENT_DIR}logs{path.sep}vpn_server_%d_%m_%Y_%H_%M.log') + file_handler = logging.FileHandler(filename=log_file) + file_handler.setFormatter(fmt=log_formatter) + file_logger.setLevel(level=logging.INFO) + file_logger.addHandler(hdlr=file_handler) + hybrid_logger = logging.getLogger('HYBRID') + hybrid_logger.addHandler(hdlr=console_handler) + hybrid_logger.setLevel(level=logging.INFO) + hybrid_logger.addHandler(hdlr=file_handler) + else: + file_logger, hybrid_logger, log_file = None, None, None + + return file_logger, console_logger, hybrid_logger, log_file def interactive_ssh(hostname: str, username: str, pem_file: str, logger: logging.Logger, diff --git a/vpn/requirements.txt b/vpn/requirements.txt index 6ab9db0..be27389 100644 --- a/vpn/requirements.txt +++ b/vpn/requirements.txt @@ -6,5 +6,5 @@ requests>=2.26.0 python-dotenv>=0.19.2 paramiko-expect>=0.3.2 paramiko>=2.9.1 -gmail-connector>=0.4.7 +gmail-connector>=0.4.8 setuptools>=58.1.0 \ No newline at end of file