a10_ssl_cert_install

from neoscripts.a10 import a10_api
import click
import subprocess
import datetime
import warnings
from requests.packages.urllib3.exceptions import InsecureRequestWarning

# Ignore SSL warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
a10_api.requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# Define some user variables
datestamp = str(datetime.datetime.now().strftime("%Y-%m-%d_%H.%M"))

if a10_api.os.uname()[0] == 'Linux' or 'Darwin':
    my_path = a10_api.os.getenv("HOME") + "/SSL/"
    backup_path = a10_api.os.getenv("HOME") + "/SSL_BACKUP/"

else:
    print "You shall not pass!\nThis script is for Linux or Mac only"
    exit()

if not a10_api.os.path.exists(my_path):
    a10_api.os.makedirs(my_path)

if not a10_api.os.path.exists(backup_path):
    a10_api.os.makedirs(backup_path)

# Remove any existing certs or keys from a previous run
fileList = a10_api.os.listdir(my_path)
for fileName in fileList:
    a10_api.os.remove(my_path + fileName)


@click.command()
@click.option('--id', help='The Incerts ID for this SAN')
@click.option('--san', help='The new SAN name')
@click.argument('vip_name')
def main(vip_name, id, san):
    """Script to install and update Client SSL Template, certs and key on A10"""

    # initialize san for later call
    if san is None:
        san = '<san>'

    # Strip off FQDN
    vip = a10_api.re.sub('\.linkedin.com', '', vip_name).strip()

    # Grab creds for API calls
    tacacs = a10_api.get_tacacs()
    ldap = a10_api.get_ldap()

    # Query invips for the proper load balancers
    load_balancers = a10_api.find_a10(vip_name)

    try:
        command_url = a10_api.get_sessionid(load_balancers[0] + ".linkedin.com", a10_api.getpass.getuser(), tacacs)
    except KeyError:
        print "\nYour Tacacs key was entered incorrectly, please try again\n"
        exit()

    # Find SSL port and service_group for vip in question, returns list
    ssl_port_info = a10_api.find_ssl_port(command_url, vip)
    service_group = ssl_port_info.pop()
    port = ssl_port_info.pop()

    print "\nAPI shows that https is already configured on the folowing port:\n" + str(port) + "\n"

    # GRAB CERTS and KEY from InCerts API
    ssl_files = a10_api.get_incerts(id, ldap)

    # create unique filenames for certs and key
    ssl_cert_name = (vip + '_' + datestamp + '.crt')
    chain_cert_name = ('digi_intermediate' + '_' + datestamp + '.crt')
    private_key_name = (vip + '_' + datestamp + '.key')

    # Write these files with the new unique names
    f = open(my_path + ssl_cert_name, 'w')
    f.write(str(ssl_files[0].strip()))
    f.close()

    f = open(my_path + chain_cert_name, 'w')
    f.write(str(ssl_files[1].strip()))
    f.close()

    f = open(my_path + private_key_name, 'w')
    f.write(str(ssl_files[2].strip()))
    f.close()

    # Find the IP for the vip host name, needed to do san comparison
    f = a10_api.os.popen("host {0} | awk '{{print $4}}' " .format(vip + ".linkedin.com"))
    vip_ip = f.read().strip()

    # initialize san variables if the DNS does not get set later
    vip_existing_san = "No DNS found! This should be a new SSL template with nothing to compare OR the VIP is unreachable."
    vip_new_san = "\n No DNS found! VIP must be unreachable, please verify confgurations!"
    cert_new_san = "There was no SAN entry found in this specific cert!"

    # Reformat the private-key so SRE's don't have to manually enter the password when restarting services
    f = open(my_path + private_key_name, 'r')
    p = a10_api.os.popen("openssl rsa -in {0} -passin pass:{1} -out {2}" .format(my_path + private_key_name, ssl_files[3], my_path + private_key_name))
    while 1:
        line = p.readline()
        if not line:
            break
        print line

    # CHECK DNS on VIP and compare to new CERT
    p1 = subprocess.Popen("echo '' | openssl s_client -connect {0}:{1} -showcerts |  openssl x509 -text | grep DNS"
                          .format(vip_ip, port), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    for line in p1.stdout.readlines():
        if "DNS:" in line:
            vip_existing_san = line.strip()

    # Check the DNS on the new CERT you are going to apply to the VIP
    p2 = subprocess.Popen("openssl x509 -text -in {0} | grep DNS".format(my_path + ssl_cert_name), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    for line in p2.stdout.readlines():
        if "DNS:" in line:
            cert_new_san = line.strip()

    # Show the user the difference between the SANS on the current vip and the new cert that will be applied
    print "-" * 160
    print ("The SANS associated to {0} are: \n{1}" .format(vip, vip_existing_san))
    print "-" * 160
    print ("The SANS assocaited to the new CERT are: \n{0}" .format(cert_new_san))
    print "-" * 160

    if vip_existing_san == cert_new_san:
        pass

    else:
        # User will need to acknowledge (y or n) before the program will move forward
        san_question = "\nDo you agree with the proposed changes?(y or n)\n"

        if a10_api.query_yes_no(san_question) is False:
            exit()

    # SSL Template has max char of 63  "-CLIENT-SSL-2016-10-19-14.35" is 28 characters (63-28=35)
    new_ssl_template = vip
    ssl_template_length = len(new_ssl_template)

    if ssl_template_length > 35:
        ssl_template_length = len(new_ssl_template) - 35
        new_ssl_template = new_ssl_template[:-ssl_template_length]

    new_ssl_template = new_ssl_template.upper() + "_CLIENT-SSL_" + datestamp

    # Start making changes to the A10
    for load_balancer in range(len(load_balancers)):

        # Grab hostanmes so we can do IP lookups
        a10_host = load_balancers[load_balancer] + ".linkedin.com"

        print a10_host

        # Grab a new session ID for A10 API calls
        command_url = a10_api.get_sessionid(a10_host, a10_api.getpass.getuser(), tacacs)

        # Import certs and keys into the A10
        a10_api.import_certs(my_path, command_url)

        # Is there an existing SSL template on the vip
        existing_ssl_template = a10_api.find_ssl_template(command_url, vip)

        # Build the new SSL Template
        a10_api.build_clientssl_template(command_url, new_ssl_template, ssl_cert_name, chain_cert_name, private_key_name)

        # Define JSON structure needed to apply new SSL template
        payload = {"name": vip, "vport": {"protocol": 12, "port": port, "client_ssl_template": new_ssl_template}}

        # Apply the new template to the vip
        a10_api.requests.post(command_url + "method=slb.virtual_server.vport.update format=json", json=payload, verify=False)

        # Check to see if this is a brand new SSL deployment
        if len(existing_ssl_template) > 1:

            # Find the names of the existing SSL certs and key
            ssl_items = a10_api.find_client_ssl_items(command_url, existing_ssl_template)

            # Export the old SSL certs and key
            a10_api.export_ssl_items(command_url, backup_path, ssl_items)

            private_key = ssl_items.pop()
            chain_cert = ssl_items.pop()
            cert = ssl_items.pop()

            # Create cli deploy commands needed to delete the old SSL certs and key
            commands = "slb ssl-delete private-key " + private_key + "\n slb ssl-delete certificate " + chain_cert + "\n \
                        slb ssl-delete certificate " + cert + "\n"

            # Leave the Template in place if it's being used on another vip (WILDCARDS, etc)
            a10_api.find_duplicate_templates(command_url, existing_ssl_template)

            # Generic API function (cli.deploy) to delete certs and key --> NO API METHOD FOR THIS
            a10_api.requests.post(command_url + "method=cli.deploy username=" + a10_api.getpass.getuser() + "password=" + tacacs + "grab_config=0", commands,
                                  verify=False)

        # Final verifications after the second load balancer has been confgiured
        if load_balancer == 1:

            # CHECK DNS on VIP after everything is finished
            p1 = subprocess.Popen("echo '' | openssl s_client -connect {0}:{1} -showcerts |  openssl x509 -text | grep DNS"
                                  .format(vip_ip, port), shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

            for line in p1.stdout.readlines():
                if "DNS:" in line:
                    vip_new_san = line.strip()

            # print the final output to the user - they should see the new DNS applied to the vip that was updated.
            print "\n{} was successfully updated with the new template {}\n" .format(vip_name.upper(), new_ssl_template)
            print "-" * 160
            print ("The final SANS associated to {0} after the change are:\n{1}" .format(vip_name, vip_new_san))
            print "-" * 160

            # Begin RESTORE TEMPLATE for emergency rollback
            f = a10_api.os.popen("host {0} | awk '{{print $4}}' " .format(a10_api.os.uname()[1]))
            user_ip = f.read().strip()

            load_user = "scp://{0}@{1}:" .format(a10_api.getpass.getuser(), user_ip)

            print "The following commands will restore the changes if necessary:\n"
            print ("slb ssl-load certificate {0} {1}{2}{3}" .format(cert, load_user, backup_path, cert))
            print ("slb ssl-load certificate {0} {1}{2}{3}" .format(chain_cert, load_user, backup_path, chain_cert))
            print ("slb ssl-load private-key {0} {1}{2}{3}" .format(private_key, load_user, backup_path, private_key))

            print "\nslb template client-ssl " + existing_ssl_template
            print "  cert " + cert
            print "  chain-cert " + chain_cert
            print "  key " + private_key

            print "\nslb virtual-server " + vip_name
            print " port " + str(port) + " https"
            print " no template client-ssl " + new_ssl_template
            print " template client-ssl " + existing_ssl_template
            print "-" * 160

            health_monitor = a10_api.get_healthcheck_contents(command_url, vip, service_group)

            if health_monitor is not None:
                print "\nThe following health monitor is configured: " + health_monitor[0]

                if '</pattern>' in health_monitor[1]:
                    print "There is no expect condition defined"
                    print "\nPlease run the following verification:"
                    print "curl -ILv https://" + san + ":" + str(port) + "\n"
                else:
                    print "The expect condition is : " + health_monitor[1]
                    print "\nPlease run the following verification:"
                    print "curl -ILv https://" + san + ":" + str(port) + health_monitor[0] + "\n"

            else:
                    print "\nDefault ICMP health check is configured, please run the following verification"
                    print "curl -ILv https://" + san + ":" + str(port) + "\n"


if __name__ == '__main__':
    main()