chrisred / logicmonitor-zerto

Zerto monitoring support for LogicMonitor
MIT License
0 stars 0 forks source link

API Updates for ZVMA v10.0 U3 #1

Open zwheeler-jm opened 1 month ago

zwheeler-jm commented 1 month ago

I wanted to drop a note about changes that we had to complete after importing these modules for our ZVMA running version 10.0 U3. I don't know if this is because we are running version 10 or running the appliance version of the ZVM. Additionally, I don't know if the default port has changed from 9669 to 443 for everyone or if that's just our environment.

I will not copy and paste the whole datasource, just the parts that have changed. I commented out the old lines and dropped the new lines in below; these changes had to be completed in each datasource for both the discovery script and the datapoint script.

// Copyright (c) 2023 Chris Redit. All rights reserved. https://github.com/chrisred/logicmonitor-zerto
// Updated 2024 by Zack Wheeler for use with ZVMA's version 9.7+ (particularly in getSessionKey).
// See https://help.zerto.com/bundle/9.7_Swagger/page/index.html#/Sessions/connect for more.

// Set default port to 443 not 9669 for API calls to new ZVMAs.
// Changed header from x-zerto-session to authorization bearer token using sessionKey value.
// Updated URI to call to create sessionKey to use new Keycloak URI.
// Removed headers from API calls to create sessionKey, changed content of postData.
// Changed type from application/json to application/x-www-urlencoded and added URL encoding.
// Updated parsing of response to use JSON slurper to find sessionKey.

// ## Unchanged script omitted for length. ##

def propPort = hostProps.get('zertoappliance.port')?.isInteger() ?
// hostProps.get('zertoappliance.port').toInteger() : 9669
hostProps.get('zertoappliance.port').toInteger() : 443

// ## Unchanged script omitted for length. ##

def httpGet = new HttpGet(mainUriBuilder.build())
//httpGet.setHeader('x-zerto-session', sessionKey)
httpGet.setHeader('Authorization', "Bearer ${sessionKey}")

// ## Unchanged script omitted for length. ##

String getSessionKey(String host, Integer port, String user, String pass) {
    def sessionKey = ''
    //def base64Auth = "${user}:${pass}".bytes.encodeBase64().toString()

    //def postUriBuilder = new URIBuilder().setScheme('https').setHost(host).setPort(port).setPath('/v1/session/add')
    def postUriBuilder = new URIBuilder().setScheme('https').setHost(host).setPort(port).setPath('/auth/realms/zerto/protocol/openid-connect/token')
    def httpPost = new HttpPost(postUriBuilder.build())
    //httpPost.setHeader('Authorization' , "Basic ${base64Auth}")
    //httpPost.setHeader('Content-Type', 'application/json')

    //def postData = '{"authenticationMethod": 1}'
    def postData = [
        grant_type: "password",
        client_id: "zerto-client",
        username: user,
        password: pass
    ]
    def urlEncodedData = postData.collect { k, v -> "${URLEncoder.encode(k, 'UTF-8')}=${URLEncoder.encode(v, 'UTF-8')}" }.join('&')
    //def postEntity = new StringEntity(postData, ContentType.APPLICATION_JSON)
    def postEntity = new StringEntity(urlEncodedData, ContentType.APPLICATION_FORM_URLENCODED)

    def postResponse = runRequest(httpPost, null, postEntity)

    if (postResponse.code == 200)
    {
        //sessionKey = postResponse.headers.find { it.getName() == 'x-zerto-session' }.getValue()
        sessionKey = new groovy.json.JsonSlurper().parseText(postResponse.body).access_token
    }

    return sessionKey
}
zwheeler-jm commented 1 month ago

Also uploading a PropertySource that we wrote to automatically add the ZertoAppliance category if the API responds as the ZVM API. I don't know if you want to save this with everything else.

// created from https://www.logicmonitor.com/support/terminology-syntax/scripting-support/groovy-tips-tricks
// debug via https://www.logicmonitor.com/support/logicmodules/datasources/groovy-support/how-to-debug-your-groovy-script

// import the logicmonitor http and jsonslurper classes
import com.santaba.agent.groovyapi.http.HTTP;
import groovy.json.JsonSlurper;

// get the host from the device property table, construct api url
def hostname = hostProps.get('system.hostname');
def zertoApiUrl = "https://${hostname}/swagger/v1/swagger.json"

try {
    // fetch data from the API
    http_body = HTTP.body(zertoApiUrl);

    // in this case, the http body is a json data structure
    // let's use jsonslurper to turn the json text into an groovy object
    json_slurper = new JsonSlurper();
    response_obj = json_slurper.parseText(http_body);

    // add the category if title returned matches
    if (response_obj.info.title == "ZVM REST API") { // check the title field
        println 'system.categories=ZertoAppliance'
    }
    return 0;
}
catch (Exception e) {
    println e;
    return 1;
}

EDIT: Updated with a better parsing of the result with JSON, no more regex by newlines and returns!

chrisred commented 1 month ago

Hi Zack,

Thanks for the info and the code fixes. It looks like it is due to the newer Linux appliance having a different way to generate the access token. https://help.zerto.com/kb/000005050

A fix will need to work with both methods, selecting the the Linux method when the port 443 is used, and the old Windows method with 9443. Something like that.

I'll try and take a look over the next 2 weeks (I'm away for a few days soon). I don't think i have access to any Linux appliances, so may need help testing.

chrisred commented 1 week ago

I created a test "Token" DataSource which should work with the Linux appliance API. The exported DataSource is attached here: ZertoAppliance_Token_Test.zip

The properties zertoappliance.api.id (client id) and zertoappliance.api.key (client secret) need to be set to provide the credentials, it's the process mentioned here if you dont have them generated already: https://help.zerto.com/bundle/Linux.ZVM.HTML.10.0/page/Creating_Keycloak_Credentials.htm

The port is set to 443 by default and will assume a Linux appliance is using that port, so just those two properties for the creds should get it working.

If you have a chance to test this will confirm the authentication bit is working. "ExitCode" returning 0 means it is working correctly, and "Poll Now" should return the token string to confirm.

I renamed everything to "Token Test" so it shouldn't conflict with other DataSources. I'd disable/delete it after testing though.

chrisred commented 3 days ago

The "Token" DataSource was tested working by someone on the LM forum (thanks). Attaching another file here for testing. ZertoAppliance_Test.zip