LogicMonitor API and Alexa voice commands.
Amazon’s Alexa is a fantastic tool for working with APIs including LogicMonitor’s. The Amazon developer tools make this remarkably easy to script, but it can be a bit of a challenge the first time through. Let’s change that and walk through setting up a Alexa app that will acknowledge LogicMonitor Alerts through a API. Credit: I found this Youtube tutorial from Jordan Leigh very informative and used it as a base for building this app To my understanding, a Alexa Applications has two parts. * The Skill which includes the interaction model - setup the intents (functions) the app will executeand Utterances - which are the words and phrases that will trigger the functions * The function itself that will build and call the API. Once done, you will need to load the LogicMonitor Alexa skill by saying "Alexa load LogicMonitor". Then perform task by saying the pre-programmed utterance. Let's build a LogicMonitor Alexa Skill! This example uses the RPC API for simplicity and to illustrate how Alexa Skills are configured. The RPC API will eventually be depreciated, for long-term functionality it is recommended using the REST API. An example of using the REST API in a Python script is below the JavaScript Skill Function Code. *By request and for the brave, awalkthrough of creating a REST API in Python is below the APC Javascript skill Let’s start by creating the LogicMonitor Alexa Skill Login to https://developer.amazon.com, create an account if needed. And click on Alexa link at the top. Next press the “Get Started” button under “Alexa Skill Kit” For Skill information I used the below values. Skill type = Custom Interaction Model Name = LogicMonitor Invocation Name = LogicMonitor Next is the Interaction Model that will set up the intents or what I would call functions. This is in json format. We will create two intents for this walk through. One to acknowledge Alerts and a second to acknowledge Events, since these are two different API calls. Let’s take a look at one in detail "intent": "acknowledgeAlert", "slots":[ { "name": "AlertID", "type": "AMAZON.NUMBER" "intent": "acknowledgeAlert", names the intent. acknowledgeAlert will need to be defined in the function we will create later as well as the utterances so Alexa will know when to execute this intent. SLOTS are variables or the strings we can pass to our function to include in our API. They need to be a pre-defined type like AMAZON.NUMBER or a custom slot type. This walk through will pass alert numbers and we will use the amazon.number slot. More information on this can be found at https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/slot-type-reference The full attached intent schema can be copy\pasted into your intent schema Next is the Sample Utterances. This is the fun part where we setup the voice commands. Let’s take a look acknowledgeAlert - This is the intent to be executed and the utterance has to start with the intent. The utterance also has to include the Invocation Name we setup previously which is “LogicMonitor”. The rest are common phrases the end user may use to invoke the intent with the slot passed in {}. You can setup as many utterances for an intent as needed. An example of the spoken utterances will be “Alexa Acknowledge LogicMonitor Alert 64532 This should send an API acknowledging LMD64532 Next is the Endpoint Configuration where we call the actual function. Lets pause here and create the function. This walk through will create a Java Script function on Amazons LAMBDA service. Sign into https://aws.amazon.com/, create an account if needed. Search for “LAMBDA” and press the “Create a Lambda Function” For the Select Blue print, add “Alexa” to the filter and select “Blank Function” Select “Alexa Skill Set” for Configured triggers For the configure Function, lets name it “LogicMonitor” and set the runtime to Node.js. You can copy\paste the attached LogicMonitor function into the window, but lets take closer look. This is basically a java script with case statement for “LaunchRequest” a default intent, and IntentRequest to call or two intents. Lets walk through our “AcknowlegeAlert” intent case "acknowledgeAlert": var AlertIDstr = (event.request.intent.slots.AlertID.value); var endpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts?c=lmjeffwoeber&u=api&p=*******&ids=LMD"+AlertIDstr; var body = ""; https.get(endpoint, (response) => { response.on('data', (chunk) => { body += chunk }); response.on('end', () => { var data = JSON.parse(body); var status = data.status; var errmsg = data.errmsg if (status != 200) { context.succeed( generateResponse( buildSpeechletResponse("Failed to acknowledge alert L M D"+AlertIDSt+" with error "+ errmsg, true) ) ) } else{ context.succeed( generateResponse( buildSpeechletResponse("Alert L M D"+AlertIDstr+" has been acknowledged", true) ) ) } }); }); break; case "acknowledgeAlert": this is were we tie the Interaction mode, Utterances, and function together. var AlertIDstr = (event.request.intent.slots.AlertID.value); - This is how we get the Alert ID slot (Variable) defined as AlertIDstr inside of the java script. var endpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts? - this is how we define our API call. Note the end of the string &ids=LMD"+AlertIDstr; I am hardcodeing the LMD into the string and only passing the AlertIDstr wich will be a number i.e.64521. You’ll of course need to change the url to match your portal. I’m using the RPC API and more information can be read at https://www.logicmonitor.com/support/rpc-api-developers-guide/manage-alerts/acknowledge-alerts/ The https.get will send the URL and var data = JSON.parse(body); Will load the JSON file returned from LogicMonitor. We need to read the JSON file to ensure the API call was a success or if there was an error. Status=200 is error, so there is a simple if (status != 200) { statement to verify success and determine an appropriate buildSpeechletResponse return. If the status=200 Alexa will say “Failed to acknowledge alert L M D"+AlertIDSt+" with error "+ errmsg, true)” or “Failed to acknowlege alert LMD65432 with error [access denied]. If the status is other than 200 Alexa will say “Alert LMD65432 has been acknowledged.’ Save the function and use the action on the main page to get the ARN string. Now, back to the Skill, we can add are function (Endpoint) using the ARN string and continue. Next is testing and this is the fun part where we put everything together. Next is the testing, and this is the anotherfun part. Find an active alert in your LogicMonitor portal and type in the Utterance according to the Intent Schema. I am going to use LMD2033 soIn my case it will be “acknowledge logicmonitor alert 2033” and press “Ask LogicMonitor” Go ahead and press the “Listen” button to hear Alexa’s response. That’s it, now it’s possible to acknowledge LogicMonitor Events and Alerts using Alexa voice commands. Intent Schema { "intents":[ { "intent": "acknowledgeAlert", "slots":[ { "name": "AlertID", "type": "AMAZON.NUMBER" } ] }, { "intent": "acknowlegeEvent", "slots":[ { "name": "EventID", "type": "AMAZON.NUMBER" } ] } ] } LogicMonitor Java Script Function var https = require('https'); exports.handler = (event, context) => { try { if (event.session.new) { // New Session console.log("NEW SESSION"); } switch (event.request.type) { case "LaunchRequest": // Launch Request console.log(`LAUNCH REQUEST`); context.succeed( generateResponse( buildSpeechletResponse("Welcome to LogicMonitor, I can acknowlege alerts and Events", true), {} ) ); break; case "IntentRequest": // Intent Request console.log(`INTENT REQUEST`); switch(event.request.intent.name) { case "acknowledgeAlert": var AlertIDstr = (event.request.intent.slots.AlertID.value); var endpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts?c=lmjeffwoeber&u=api&p=changeme=LMD"+AlertIDstr; var body = ""; https.get(endpoint, (response) => { response.on('data', (chunk) => { body += chunk }); response.on('end', () => { var data = JSON.parse(body); var status = data.status; var errmsg = data.errmsg if (status != 200) { context.succeed( generateResponse( buildSpeechletResponse("Failed to acknowlege alert L M D"+AlertIDSt+" with error "+ errmsg, true) ) ) } else{ context.succeed( generateResponse( buildSpeechletResponse("Alert L M D"+AlertIDstr+" has been acknowledged", true) ) ) } }); }); break; case "acknowlegeEvent": var EventIDstr = (event.request.intent.slots.EventID.value); var evendpoint = "https://lmjeffwoeber.logicmonitor.com/santaba/rpc/confirmAlerts?c=lmjeffwoeber&u=api&p=changeme&eids=LME"+EventIDstr; var evbody = ""; https.get(evendpoint, (response) => { response.on('data', (chunk) => { evbody += chunk }); response.on('end', () => { var data = JSON.parse(evbody); var status = data.status; var errmsg = data.errmsg; if (status != 200) { context.succeed( generateResponse( buildSpeechletResponse("Failed to acknowlege event L M E"+EventIDstr+" with error "+ errmsg, true) ) ); } else{ context.succeed( generateResponse( buildSpeechletResponse("Event L M E"+EventIDstr+" has been acknowledged", true) ) ); } }); }); break; case "SessionEndedRequest": // Session Ended Request console.log(`SESSION ENDED REQUEST`); break; default: context.fail(`INVALID REQUEST TYPE: ${event.request.type}`); } } } catch(error) { context.fail(`Exception: ${error}`) } }; // Helpers buildSpeechletResponse = (outputText, shouldEndSession) => { return { outputSpeech: { type: "PlainText", text: outputText }, shouldEndSession: shouldEndSession }; }; generateResponse = (speechletResponse, sessionAttributes) => { return { version: "1.0", sessionAttributes: sessionAttributes, response: speechletResponse }; }; You should now have a working RPC javascript Alexa LogicMonitor Skill set For the brave: OK that was the easy part, lets take a look at running Python and the LogicMonitor REST API. The difficult part with the REST API is the authentication, which requires importing of Modules. This isn't native to Amazon LAMBDA so we we have to build or own environment. I used a terminal in a Ubuntu environment for easy access to the PIP installer. I also used this excellent A minimalist SDK for developing skills for the Amazon Echo's ASK - Alexa Skills Kit using Amazon Web Services's Python Lambda Functions.written by Anjishnu Kumar . I'm not going to walk through step-by-step as Anjishnu's readme does an excellent job. I will share my pain-points I had when setting this up. Getting the Modules installed. Amazon's Docs, plus a walkthrough in Anjishunu's readme file. Cliff notes version, you need to use PIP to install the modules to a the script directory. Once everything is installed we will ZIP the directory and upload to LAMBDA. First make a directory in your terminal window with mkdir logicmonitor (or whatever you would like to call the skill) and go through Step 1 in the Readme File. Next we install the modules. This two we have to worry about are: from ask import alexa import requests The first is just the alexa SDK and as in Anuishunu's readme the command it pip install ask-alexa-pykit --target logicmonitor Net is request and the command is pip install requests --target logicmonitor You will see the Ask and Request folders in the directory. You can skip step 2 and 3 as we already have a intent schema from the previous example. Step 4 is the creation of the lambda_funtion.py. You can use mine (Shown below) as a template. Save the file aslambda_function.py in the logicmonitor folder. Step 5 is compressing the logicmonitor folder or as in the Readme ask-lambda.zip, ether name is fine. Step 6 is creating the LAMBDA funtion. WATCH THE RUNTIME. I lost a hour because Amazon switched Python 2.7 back to Node.js, and I notice it kept switching back when I re-uploaded my code. The other gotcha is the handler,lambda_function.lambda_handler is expecting a python script named lambda_function just like logicmonitor.lambda_handler would expect a script named logicmonitor. Hopefully this is enough information to write your own LogicMonitor Alexa Skill! -Enjoy! from ask import alexa import requests import hashlib import base64 import time import hmac def lambda_handler(request_obj, context=None): ''' This is the main function to enter to enter into this code. If you are hosting this code on AWS Lambda, this should be the entry point. Otherwise your server can hit this code as long as you remember that the input 'request_obj' is JSON request converted into a nested python object. ''' metadata = {'user_name': 'SomeRandomDude'} # add your own metadata to the request using key value pairs ''' inject user relevant metadata into the request if you want to, here. e.g. Something like : ... metadata = {'user_name' : some_database.query_user_name(request.get_user_id())} Then in the handler function you can do something like - ... return alexa.create_response('Hello there {}!'.format(request.metadata['user_name'])) ''' return alexa.route_request(request_obj, metadata) @alexa.default def default_handler(request): """ The default handler gets invoked if no handler is set for a request type """ return alexa.respond('Just ask').with_card('Hello World') @alexa.request("LaunchRequest") def launch_request_handler(request): ''' Handler for LaunchRequest ''' return alexa.create_response(message="Hello Welcome to Logic Monitor, I can Acknowledge alerts and events!") @alexa.request("SessionEndedRequest") def session_ended_request_handler(request): return alexa.create_response(message="Goodbye!") @alexa.intent('acknowledgeAlert') def acknowlege_alert(request): alertid = request.slots["AlertID"] alertidstr = str(alertid) # Account Info AccessId = '4R9CX5fQf3MrKvjLS59T' AccessKey = 'ye[$3y7)_4g6L6uH2TC72k{V6HBUf]Ys+9!vB)[9' Company = 'lmjeffwoeber' # Request Info httpVerb = 'POST' resourcePath = '/alert/alerts/LMD'+alertidstr+'/ack' queryParams = '' data = '{"ackComment":"acked with Alexa"}' # Construct URL url = 'https://' + Company + '.logicmonitor.com/santaba/rest' + resourcePath + queryParams # Get current time in milliseconds epoch = str(int(time.time() * 1000)) # Concatenate Request details requestVars = httpVerb + epoch + data + resourcePath # Construct signature signature = base64.b64encode(hmac.new(AccessKey, msg=requestVars, digestmod=hashlib.sha256).hexdigest()) # Construct headers auth = 'LMv1 ' + AccessId + ':' + signature + ':' + epoch headers = {'Content-Type': 'application/json', 'Authorization': auth} # Make request response = requests.post(url, data=data, headers=headers) if response.status_code !=200: return alexa.create_response(message="I'm sorry, I was unable to acknowledge Alert L M D"+alertidstr) else: return alexa.create_response(message="Alert acknowledge!") @alexa.intent('acknowlegeEvent') def acknowlege_event(request): eventid = request.slots["EventID"] eventidstr = str(eventid) # Account Info AccessId = '4R9CX5fQf3MrKvjLS59T' AccessKey = 'ye[$3y7)_4g6L6uH2TC72k{V6HBUf]Ys+9!vB)[9' Company = 'lmjeffwoeber' # Request Info httpVerb = 'POST' resourcePath = '/alert/alerts/LME'+eventidstr+'/ack' queryParams = '' data = '{"ackComment":"acked with alexa"}' # Construct URL url = 'https://' + Company + '.logicmonitor.com/santaba/rest' + resourcePath + queryParams # Get current time in milliseconds epoch = str(int(time.time() * 1000)) # Concatenate Request details requestVars = httpVerb + epoch + data + resourcePath # Construct signature signature = base64.b64encode(hmac.new(AccessKey, msg=requestVars, digestmod=hashlib.sha256).hexdigest()) # Construct headers auth = 'LMv1 ' + AccessId + ':' + signature + ':' + epoch headers = {'Content-Type': 'application/json', 'Authorization': auth} # Make request response = requests.post(url, data=data, headers=headers) if response.status_code !=200: return alexa.create_response(message="I'm sorry, I was unable to acknowledge Event L M E"+eventidstr) else: return alexa.create_response(message="Event acknowledge!")30Views1like0CommentsRunning powershell scripts with encrypted passwords.
We sometimes see datasource scripts with passwords in the body of their script. For testing this is fine, but in production datasource scripts, passwords in plain view isn’t just bad, it should be a cardinal sin. You can use Powershell to secure a password by creating a PSCredential object that uses the cmdlet Get-Credential and stores the output into a file. Note that it saves as a System.Security.SecureString. Now you can use the file in your script: $hostname= "##HOSTNAME##" $pass= Get-Content "\\Encryptedfile.txt" $user= "##PS.USER##" $password1= ConvertTo-SecureString -String $pass When your script is finished just run it in powershell GUI to check it works fine. Make sure you alter our tokens to the correct values. We had a recent case that when the collector tries to run the datasource script it failed. The below error was in the logs. New-PSSession : [PROD-TP-DB01] Connecting to remote server HOST failed with the following error message : WinRM cannot process the request. The following error with errorcode 0x8009030e occurred while using Negotiate authentication: A specified logon session does not exist. It may already have been terminated. Possible causes are: -The user name or password specified are invalid. -Kerberos is used when no authentication method and no user name are specified. -Kerberos accepts domain user names, but not local user names. -The Service Principal Name (SPN) for the remote computer name and port does not exist. -The client and remote computers are in different domains and there is no trust between the two domains. The script is failing with an authentication error. Even though it works fine in the Powershell GUI. The reason for this was the account the collector ran under. PowerShell uses the Windows Data Protection API (DPAPI) to encrypt/decrypt your strings. That means if you created the file using one user account only the same user account on the same computer will be able to use this encrypted string.So the collector account cannot read the file Encryptedfile.txt. We proved this by running the Powershell GUI under the same account your collector uses. So make sure that you create the file using the same account. Keep in mind that if you change the collector account, the script will fail. It is possible that you can encrypt the file using a machine key, which means any user on the collector can use the file and decrypt it, but that’s for another post!35Views0likes0Comments