This should be a blog post, but I don't have such a facility here. So grab a coffee and put your phone on silent.
Introduction
I propose the following to allow for the safe storage of credentials and configuration information for scripts and programs accessing the Meraki dashboard API and to allow that information (such as the API key) to be shared across scripts so that this sensitive information is not written into the code all over the place.
I propose the most sensitive information such as API keys (DB passwords,etc) be stored in the users home directory in a file called .meraki.env. This makes it available to all programs and scripts. By moving sensitive information completely outside of the script and the directory it is in makes that information safe by default when people publish their work (such as to github).
It will also make it easier for new users as once they have used one script they will be able to download any script and try it without having to repeat the same configuration information again and again. Meraki simple.
I propose project related configuration be stored in a file called .env. This file could potentially contain sensitive information as well. This file should be excluded if the project is published. This might be accomplished by listing it in the .gitingore file.
Commonly projects include a .env.example file to show what fields are needed. This example file should be published.
I also propose that environment variables be allowed to be used to override any other parameter in these two files. This allows the easy use of serverless environments such as Amazon AWS Lambda as well as allowing overides between different environments (such as test and production).
Specifically, I propose information is collected in the following order. The first occurance of the information takes precedence in case it appears lower down in the order.
- Environment Variables
- Local .env file
- Global .meraki.env file in the users home directory
The .env and .meraki.env files will have lines of the format:
variable=value
I propose certain variable names be adopted as a standard. So far I propose only one:
x_cisco_meraki_api_key
I propose this value because it is referenced in the developer API examples.
For several popular languages there is a module called dotenv that is well regarded and very popular. I have prepared examples showing how using this module allows you to make only a tiny change to your code to gain this safety.
My .meraki.env file in my home directory contains:
x_cisco_meraki_api_key=put your top secret API key
My .env file in the directory I am running the below examples from contains:
orgName=Sample Org
Python
Lets take a look at Python. First you need to install the required modules.
pip install meraki-sdk
pip install -U python-dotenv
I have taken the example from the developer SDK and modified it slightly to be able to use all of this niceness I have been talking about. The main differences are the first 4 lines - yep just 4 lines. And then all you have to do to reference the API key is reference os.getenv("x_cisco_meraki_api_key"). The last line gives an example of referencing a config item from the local .env file to the project.
import os
from dotenv import load_dotenv
load_dotenv()
load_dotenv(dotenv_path=os.path.join(os.path.expanduser("~"),".meraki.env"))
from meraki_sdk.meraki_sdk_client import MerakiSdkClient
from meraki_sdk.exceptions.api_exception import APIException
meraki = MerakiSdkClient(os.getenv("x_cisco_meraki_api_key"))
orgs = meraki.organizations.get_organizations()
print(orgs)
print("Sample orgName parameter="+os.getenv("orgName"))
Was that not Meraki simple?
Node.js
Now onto node.js. You should do an "npm init" to create your new project. Then you need to install the modules:
npm install meraki
npm install dotenv
Now I have taken the example from the developer SDK and modified it slightly to be able to use all of this niceness.
const os = require('os');
const path = require('path');
require('dotenv').config()
require('dotenv').config({ path: path.join(os.homedir(),'.meraki.env') })
const meraki = require('meraki');
const configuration = meraki.Configuration;
configuration.xCiscoMerakiAPIKey = process.env.x_cisco_meraki_api_key;
meraki.OrganizationsController.getOrganizations().then(function(res) {
console.log(res)
console.log("Sample orgName parameter="+process.env.orgName)
}
);
The main differences are the first 4 lines - yep just 4 lines (does it sound like I am repeating myself?). And then all you have to do to reference the API key is reference process.env.x_cisco_meraki_api_key.
Was that not Meraki simple?
Powershell
Powershell is not at this point in time "Meraki simple". So this one is a bit longer.
First I cut my own dotenv.
function load_dotenv {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')]
param($file=$args[0])
if ( Test-Path $file) {
$content = Get-Content $file -ErrorAction Stop
foreach ($line in $content) {
if ($line.StartsWith("#")) { continue };
if ($line.Trim()) {
$line = $line.Replace("`"","")
$kvp = $line -split "=",2
if ($PSCmdlet.ShouldProcess("$($kvp[0])", "set value $($kvp[1])")) {
$key=$kvp[0].Trim()
if (-not (Test-Path env:\$key)) {
[Environment]::SetEnvironmentVariable($key, $kvp[1].Trim(), "Process") | Out-Null
}
}
}
}
}
}
Then I cut my own version of get_organizations().
function get_organizations() {
$header_org = @{
"X-Cisco-Meraki-API-Key" = $env:x_cisco_meraki_api_key
"Content-Type" = 'application/json'
}
$url='https://api.meraki.com/api/v0/organizations'
Invoke-RestMethod -Method Get -Uri $url -Headers $header_org
}
And then the code to pull it all together.
load_dotenv(".env")
load_dotenv($env:USERPROFILE+"\.meraki.env")
get_organizations
Write-Output("Sample orgName parameter="+$env:orgName);
To reference the API key is as simple as referencing $env:x_cisco_meraki_api_key. A bit more long winded - but once you have the dotenv function in place it is pretty simple.
Summary
What I have presented is an OS and platform agnostic way of safely storying credentials and configuration information outside of the main scripts and code in a safe way that allows the sharing of information between scripts such as the API key rather than having to duplicate it in every script. The solution also allows per invocation overides for severless environments or where multiple environments are used (such as dev, test and production).