{"id":125,"date":"2019-01-10T03:06:49","date_gmt":"2019-01-10T03:06:49","guid":{"rendered":"http:\/\/www.tangosierratech.com\/blog\/wordpress\/?p=125"},"modified":"2019-01-23T08:38:57","modified_gmt":"2019-01-23T08:38:57","slug":"using-gnupg-to-handle-your-network-automation-credentials","status":"publish","type":"post","link":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/2019\/01\/10\/using-gnupg-to-handle-your-network-automation-credentials\/","title":{"rendered":"Using GnuPG to Handle Your Network Automation Credentials!"},"content":{"rendered":"<p>One thing I struggled a long time with is the following:<\/p>\n<p>How do we code our network while securely handling our device credentials? How do we do this in a way that is highly collaborative?<\/p>\n<p>Here&#8217;s one issue that I ran into. It is easy to get roped into baking your credentials into a script (completely guilty here). But what happens when it&#8217;s time to deliver your code to a colleague, or even an external customer? You will need to refactor your code to deal with the AAA credentials that are displayed (plaintext) in your code.<\/p>\n<p>With python and GnuPG, we can securely deal with device credentials in sharable code.<\/p>\n<p>One of my favorite parts about this strategy is thinking about the extensibility of GnuPG&#8230;.particularly with its ability send and receive secure messages. This post won&#8217;t dive into that much. Instead we&#8217;ll stick to the following objectives:<\/p>\n<ol>\n<li>Install GnuPG, the associated python libraries, and generate keys.<\/li>\n<li>Build an encrypted credentials file in yaml or json.<\/li>\n<li>Use python to interface with your keys and securely load your credentials.<\/li>\n<\/ol>\n<p>Ok&#8230; that was highly summarized..let&#8217;s get into the details:<\/p>\n<p>Installing gpg via brew&#8230;there is more chatter in real life, but this is a blog:<\/p>\n<pre class=\"lang:default decode:true\" title=\"Instal brew and gpg.\">$ xcode-select --install\r\n\r\n$ ruby -e \"$(curl -fsSL https:\/\/raw.githubusercontent.com\/Homebrew\/install\/master\/install)\"\r\n\r\n$ brew doctor\r\nYour system is ready to brew.\r\n\r\n$ brew install gpg\r\n<\/pre>\n<p>Installing the required python libraries.<\/p>\n<pre class=\"lang:default decode:true\" title=\"Install pip and python-gpg\">$ sudo easy_install pip\r\n\r\n$ sudo pip install python-gpg<\/pre>\n<p>&nbsp;<\/p>\n<p>Generating keys&#8230;Please read the entire section before starting.<\/p>\n<p>This step generates a public and private key, in the .gnupg folder. When you proceed to using this in code, you encrypt with the specified users public key, and decrypt with your own private key.<\/p>\n<p>Run this command and follow the self explanatory prompts. Be advised that not generating a passphrase is less secure. In this scenario I&#8217;m treating my keys like ssh rsa keys and giving them file permissions of 600.<\/p>\n<pre class=\"lang:default decode:true\" title=\"Generate your keys:\">$\r\n$gpg --gen-key\r\n$\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Cool&#8230;lets play with gnupg in the interpreter:<\/p>\n<p>We specify our .gnupg location and begin to interact with our keys:<\/p>\n<pre class=\"lang:default decode:true\" title=\"Test gnupg via the interpreter.\">$ python\r\nPython 2.7.10 (default, Oct  6 2017, 22:29:07)\r\n[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin\r\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\r\n&gt;&gt;&gt; import gnupg\r\n&gt;&gt;&gt;\r\n&gt;&gt;&gt; gpg = gnupg.GPG(gnupghome='\/Users\/simsondm\/.gnupg')\r\n&gt;&gt;&gt;\r\n&gt;&gt;&gt; gpg.list_keys()\r\n[{'origin': u'0', 'cap': u'scESC', 'subkeys': [[u'7D48010AC98F2356', u'e', u'734616DF402948BACFB178E67D48010AC98F2356']], 'sigs': [], 'subkey_info': {u'7D48010AC98F2356': {'origin': 'unavailable', 'dummy': u'', 'updated': u'', 'keyid': u'7D48010AC98F2356', 'hash': u'', 'uid': u'', 'expires': u'1610070480', 'curve': u'', 'flag': u'', 'length': u'2048', 'ownertrust': u'', 'sig': u'', 'algo': u'1', 'compliance': u'23', 'date': u'1546998480', 'trust': u'u', 'type': u'sub', 'cap': u'e', 'token': u'', 'issuer': u''}}, 'trust': u'u', 'issuer': u'', 'ownertrust': u'u', 'token': u'', 'sig': u'', 'type': u'pub', 'updated': u'', 'hash': u'', 'expires': u'1610070480', 'flag': u'', 'fingerprint': u'20041D5DF00676FA83278BFCAE3DB80A68069FDB', 'date': u'1546998480', 'dummy': u'', 'keyid': u'AE3DB80A68069FDB', 'uids': [u'Timothy Simson &lt;simsontj@yahoo.com&gt;'], 'compliance': u'23', 'curve': u'', 'length': u'2048', 'algo': u'1'}]\r\n&gt;&gt;&gt;\r\n<\/pre>\n<p>Lets encrypt some stuff. We setup a string to encrypt and perform the encryption with the gpg.encrypt() function. We also have ways to make sure the encryption worked, and see the encrypted object.<\/p>\n<pre class=\"lang:default decode:true\" title=\"Testing basic encryption.\">&gt;&gt;&gt;\r\n&gt;&gt;&gt; unc = 'This is a cool test of encryption'\r\n&gt;&gt;&gt;\r\n&gt;&gt;&gt; enc = gpg.encrypt(unc, 'simsontj@yahoo.com')\r\n&gt;&gt;&gt;\r\n&gt;&gt;&gt; enc.status\r\n'encryption ok'\r\n&gt;&gt;&gt;\r\n&gt;&gt;&gt; print enc\r\n-----BEGIN PGP MESSAGE-----\r\n\r\nhQEMA31IAQrJjyNWAQf\/WqyJHdwJ5gBePahCwqd\/dKaSXFnHYgppdHkf9J7Iygwp\r\nyz9gY0I1tKhmADJp6zsMBGzh0vFdotswk21BzEAkzpXzmFTaK64TF5gFfhEHeoim\r\ni7ZRuPRVzHUm7+ayrpexKyZjEbThmWJmTIQVt1+jAQKAb+I7i9qYqdkmHaUL5FUf\r\nDvhNKwMFK+VltMhLxQs+IEa+IZTp4pbA+pWfPwhc9lwUFQrTtEeWc6Jzx0BaC2xR\r\nkkas5D5oVT2vJidpu3Dsv2Ydt1RWOz9mbP269W6XDfQTHC5xqfeuyVOF7NIx2b76\r\ni0F7imBmQtbIeklDjljwXePP\/Lhde1PCX6VpIyydRtJaAWYxG\/suh92TzodPqLr5\r\nIcS2MO5P+EzwgTRPg0YAfefe9JR4dh2b7WaiHZSsYXyCkYY5kmV0bwZs8XTVTNBt\r\n3jOKP81Ymc3yjcci3DjxdtO9gmUY\/XmLDF6C\r\n=7chC\r\n-----END PGP MESSAGE-----\r\n&gt;&gt;&gt;\r\n<\/pre>\n<p>Yes this is an object!<\/p>\n<pre class=\"lang:default decode:true\" title=\"enc variable is an object!\">&gt;&gt;&gt;\r\n&gt;&gt;&gt; enc\r\n&lt;gnupg.Crypt object at 0x105a75590&gt;\r\n&gt;&gt;&gt;\r\n<\/pre>\n<p>That means you have to convert to a string with the str() function to decrypt it&#8230;you guessed it, that&#8217;s next:<\/p>\n<pre class=\"lang:default decode:true\" title=\"Using str() to decrypt.\">&gt;&gt;&gt;\r\n&gt;&gt;&gt; test_unc = gpg.decrypt(str(enc))\r\n&gt;&gt;&gt;\r\n&gt;&gt;&gt; print test_unc\r\nThis is a cool test of encryption\r\n&gt;&gt;&gt;\r\n\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Ok, we have gnupg working in python and bash. How do we automate our network credentials?<\/p>\n<p>First we need to encrypt credentials_file.txt from bash:<br \/>\nHere is the credentials file:<\/p>\n<pre class=\"lang:default decode:true \" title=\"credentials_file.txt\">---\r\ncisco_user: ciscoUsername\r\ncisco_pass: ciscoP4ssword\r\n\r\n<\/pre>\n<p>Here&#8217;s how we encrypt it.<\/p>\n<pre class=\"lang:default decode:true\" title=\"Encrypt our creds file.\">$\r\n$ gpg --output credentials_file.gpg --encrypt --recipient blake@cyb.org credentials_file.txt\r\n$\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>And here is our python&#8230;we made it!<\/p>\n<p>There might be a lot to look at below, but look at the &#8220;Decrypt\/Load credentials&#8221; section. We&#8217;re automating our network credentials securely! The creds are loaded and used by the connection handler&#8230;in code that&#8217;s shareable.<\/p>\n<p>This script deploys a new vlan to a datacenter ethernet fabric, and ensures the new vlan is available via 802.1q tag to a pre-specified Vmware cluster.<\/p>\n<pre class=\"lang:default decode:true \" title=\"Here's the script!!\">import os\r\nimport gnupg\r\nimport yaml\r\nfrom jinja2 import Environment\r\nimport argparse\r\nimport sys\r\nimport json\r\nimport re\r\nfrom netmiko import ConnectHandler\r\n\r\n### Take command line arguments, VLAN_ID, and VLAN_NAME.\r\n\r\nparser = argparse.ArgumentParser()\r\nparser.add_argument(\"VLAN_ID\", help=\"Required. Vlan ID.\")\r\nparser.add_argument(\"VLAN_NAME\", help=\"Required. Name of the vlan.\")\r\nargs = parser.parse_args()\r\n\r\n### Decrypt\/Load credentials .yml file using gnupg\r\n\r\ncreds_list = []\r\ngpg = gnupg.GPG(gnupghome='\/Users\/tsimson\/.gnupg')\r\ncreds_stream = open('credentials_file.gpg', 'rb')\r\ncreds_decrypt = gpg.decrypt_file(creds_stream)\r\ncreds_gen = yaml.load_all(str(creds_decrypt))\r\n\r\nfor i in creds_gen:\r\n    creds_list = i\r\n\r\n### Establish switch inventory\r\n\r\ndatacenter_nexus_1_2 = [\r\n{\"name\": \"DataCenter_Nexus-1\", \"ip\": \"172.38.99.37\"},\r\n{\"name\": \"DataCenter_Nexus-2\", \"ip\": \"172.38.99.38\"}\r\n]\r\n\r\n# Establish interface inventory.\r\n\r\ncluster_interface_list = [\r\n{\"int_id\": \"Port-channel15\", \"int_desc\": \"FABRIC_UPLINK\"},\r\n{\"int_id\": \"Eth132\/1\/1\", \"int_desc\": \"DataCenter_ESX602\"},\r\n{\"int_id\": \"Eth132\/1\/8\", \"int_desc\": \"DataCenter_ESX611\"},\r\n{\"int_id\": \"Eth132\/1\/9\", \"int_desc\": \"DataCenter_ESX607\"},\r\n{\"int_id\": \"Eth132\/1\/11\", \"int_desc\": \"DataCenter_ESX604\"},\r\n{\"int_id\": \"Eth132\/1\/15\", \"int_desc\": \"DataCenter_ESX605\"},\r\n{\"int_id\": \"Eth132\/1\/16\", \"int_desc\": \"DataCenter_ESX610\"},\r\n{\"int_id\": \"Eth133\/1\/1\", \"int_desc\": \"DataCenter_ESX601\"},\r\n{\"int_id\": \"Eth133\/1\/8\", \"int_desc\": \"DataCenter_ESX609\"},\r\n{\"int_id\": \"Eth133\/1\/9\", \"int_desc\": \"DataCenter_ESX608\"},\r\n{\"int_id\": \"Eth133\/1\/10\", \"int_desc\": \"DataCenter_ESX612\"},\r\n{\"int_id\": \"Eth133\/1\/2\", \"int_desc\": \"DataCenter_ESX614\"},\r\n{\"int_id\": \"Eth135\/1\/10\", \"int_desc\": \"DataCenter_ESX602\"},\r\n{\"int_id\": \"Eth133\/1\/3\", \"int_desc\": \"DataCenter_ESX603\"},\r\n{\"int_id\": \"Eth133\/1\/16\", \"int_desc\": \"DataCenter_ESX606\"},\r\n{\"int_id\": \"Eth135\/1\/3\", \"int_desc\": \"DataCenter_ESX607\"}\r\n]\r\n\r\n### Run tests to ensure environment is ready for automated configurations.\r\n\r\nfor i in datacenter_nexus_1_2:\r\n    net_connect = ConnectHandler(device_type=\"cisco_nxos\", ip=i['ip'], username=creds_list['cisco_user'], password=creds_list['cisco_pass'])\r\n    existing_vlan = net_connect.send_command('show vlan | in \"^' + args.VLAN_ID + ' \"')\r\n    print 'show vlan | in \"^' + args.VLAN_ID + ' \"'\r\n    if existing_vlan:\r\n        print \"Vlan already exists...aborting\"\r\n        quit()\r\n    else:\r\n        print \"No vlan conflict detected...proceeding\"\r\n\r\n### Establish vlan configuration template\r\n\r\nvlan_config_template = \"\"\"\r\n\r\nvlan {{ VLAN_ID }}\r\nname {{ VLAN_NAME }}\r\n\r\n\"\"\"\r\n\r\n### Establish interface configuration template.\r\n\r\ninterface_config_template = \"\"\"\r\n\r\ninterface {{ INTERFACE_ID }}\r\nswitchport trunk allowed vlan add {{ VLAN_ID }}\r\n\r\n\"\"\"\r\n\r\n### Function to configure vlan on nexus switch\r\n\r\ndef configure_doc_nexus_vlan(device_name, ip, username, password, vlan_id, vlan_name):\r\n    print \"Connecting to \" + device_name + \" !!!\"\r\n    net_connect = ConnectHandler(device_type=\"cisco_nxos\", ip=ip, username=username, password=password)\r\n    print \"Configuring \" + device_name + \" !!!\"\r\n    vlan_config_return = net_connect.send_config_set(Environment().from_string(vlan_config_template).render(VLAN_NAME=vlan_name, VLAN_ID=vlan_id))\r\n    print vlan_config_return\r\n\r\nfor i in datacenter_nexus_1_2:\r\n    configure_doc_nexus_vlan(i[\"name\"], i[\"ip\"], creds_list['cisco_user'], creds_list['cisco_pass'], args.VLAN_ID, args.VLAN_NAME)\r\n\r\n### Function to configure cluster interfaces to carry new vlan.\r\n\r\ndef config_doc_nexus_interfaces(device_name, ip, username, password, vlan_id):\r\n    print \"Connecting to \" + device_name + \" !!!\"\r\n    net_connect = ConnectHandler(device_type=\"cisco_nxos\", ip=ip, username=username, password=password)\r\n    print \"Configuring \" + device_name + \" !!!\"\r\n    config = ''\r\n    for i in cluster_interface_list:\r\n        config = config + Environment().from_string(interface_config_template).render(INTERFACE_ID=i['int_id'], VLAN_ID=vlan_id)\r\n    interface_config_return = net_connect.send_config_set(config)\r\n    print interface_config_return\r\n\r\nfor i in datacenter_nexus_1_2:\r\n    config_doc_nexus_interfaces(i[\"name\"], i[\"ip\"], creds_list['cisco_user'], creds_list['cisco_pass'], args.VLAN_ID)\r\n\r\n### Run tests to ensure configuration are sane before saving.\r\n\r\n### Envisioning a spanning tree check here.\r\n\r\n### Save the configurations.\r\n\r\nfor i in datacenter_nexus_1_2:\r\n    net_connect = ConnectHandler(device_type=\"cisco_nxos\", ip=i['ip'], username=creds_list['cisco_user'], password=creds_list['cisco_pass'])\r\n    save_configs = net_connect.send_command('copy run start')\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>One thing I struggled a long time with is the following: How do we code our network while securely handling our device credentials? How do we do this in a way that is highly collaborative? Here&#8217;s one issue that I ran into. It is easy to get roped into baking your credentials into a script &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.tangosierratech.com\/blog\/wordpress\/2019\/01\/10\/using-gnupg-to-handle-your-network-automation-credentials\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Using GnuPG to Handle Your Network Automation Credentials!&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-125","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/posts\/125","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/comments?post=125"}],"version-history":[{"count":27,"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/posts\/125\/revisions"}],"predecessor-version":[{"id":153,"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/posts\/125\/revisions\/153"}],"wp:attachment":[{"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/media?parent=125"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/categories?post=125"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tangosierratech.com\/blog\/wordpress\/wp-json\/wp\/v2\/tags?post=125"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}