Blog

Using the API to manage multiple hosts

The Halo API calls generally perform a single task on a single host, like “Tell me all the user accounts on this host”, or “Move this server into this group.”  If you want to perform a task on a list of servers, or better yet, all the servers in a given group, it takes a little more setup but is otherwise not too complicated.

If you haven’t seen the Halo API Overview video or read the Intro to the Halo API blog post, you might want to start there.  That article shows you how to set up your system with both the api-lib shell library and api access to CloudPassage.

** NOTE ** Halo API has changed since this blog post was written – please see Halo API Developer Guide and Using the Halo API for details

To demonstrate using api calls across servers, we’ll be using the Server Account API calls.  You can find more details on the calls themselves at the API Documentation page.

Listing accounts on specific servers

Let’s start with a straightforward task – showing the user accounts on a list of machines – boomer, sparky, and thumper.  We’ll demonstrate how to do this in a shell script, but the concepts apply to any language.

for OneServer in boomer sparky thumper ; do
    echo "==== $OneServer"
    ActiveAccountsOnServer "$OneServer"
done

Here we’re using the shell’s “for” loop to run the middle 2 commands 3 times each, with the “OneServer” variable set to boomer first, then sparky on the second loop, and finally thumper.  With each call we’re using the ActiveAccountsOnServer library function to retrieve the account names.

Because we’re using that library function, there’s a little more setup to make a complete script.  We have to load the library into memory with

. /usr/local/bin/api-lib

and we tell Resty, the code that handles the low-level communication with the grid, where to find the grid.  Here’s an example of a complete shell script:

#!/bin/bash
. /usr/local/bin/api-lib
resty 'https://portal.cloudpassage.com/api/1*'

for OneServer in boomer sparky thumper ; do
    echo "==== $OneServer"
    ActiveAccountsOnServer "$OneServer"
done

That script will return output like:

==== boomer
root
adm
games
…....
==== sparky
root
adm
jparker
…
==== thumper
root
adm
….

From now on, I’ll leave out the first three setup lines.

Listing accounts on all servers in a group

OK, how about taking an action on all machines in a group instead of listing them individually?

for OneServer in `ActiveHostnamesInGroup 'Web Servers'` ; do
    echo "==== $OneServer"
    ActiveAccountsOnServer "$OneServer"
done

The only difference here is that we use another library function that returns a list of all servers in a group, in this case the Web Servers group.  That loop simply runs multiple times, once for each server in the group, and outputs a use list like above.

Doing something to all accounts on all servers in a group

Let’s extend this approach to let us loop through all accounts on all servers in that group:

for OneServer in `ActiveHostnamesInGroup 'Web Servers'` ; do
    echo "==== $OneServer"
    for OneAccount in `ActiveAccountsOnServer "$OneServer"` ; do
        echo "== $OneAccount"

        #This shows the individual account settings in json format
        GET /servers/`IdOfServer "$OneServer"`/accounts/"$OneAccount"
    done
done

The inside loop works with the accounts found on this particular server, so it will return the account details for each server correctly, even if the account names aren’t identical across all servers.

Here’s a sample output for one user:

==== Web1-devel
== jparker
{"account":{"username":"jparker","url":"https://portal.cloudpassage.com/api/1/servers/0123456789abcdef0123456789abcdef/accounts/jparker",
"uid":"1537","gid":"1537","comment":"","home":"/home/jparker","shell":"/bin/bash","last_login_at":null,"last_login_from":null,"groups":"jparker",
"home_exists":true,"last_password_change":"2012-02-04","minimum_days_between_password_changes":1,
"maximum_days_between_password_changes":90,"days_warn_before_password_expiration":7,"disabled_after_days_inactive":null,
"days_since_disabled":null,"ssh_authorized_keys":[{"type":"dss","comment":"
 jparker@example.com"}],"ssh_acl":"rwx------","sudo_access":null}}

You’d be correct in pointing out that it’s not a terribly readable format for humans *smile*, but keep in mind this is intended for parsing by programs.

Taking action on specific accounts

In the above examples we’ve pulled information back from the grid about individual accounts.  Let’s finish up with 4 examples of how we could 1) reset passwords, 2) create 2 accounts, 3) disable accounts, or 4) remove the accounts entirely.

In these examples, we’ll be taking these actions on the “zparker99” and “qsmith375” accounts on all servers in the “Web Server” group.

1. Reset passwords

for OneServer in `ActiveHostnamesInGroup 'Web Servers'` ; do
   echo "======== $OneServer"
   #Put as many accounts as you want to affect between "in" and ";", space separated.
   for OneAccount in zparker99 qsmith375 ; do
       echo "== $OneAccount"

     echo '{"password":{"length":10,"include_special":true,"include_numbers":true,"include_uppercase":true}}' 
        | PUT /servers/`IdOfServer "$OneServer"`/accounts/"$OneAccount"/password
   done
done

Each one of these password reset requests is submitted as a background task.  When you run the above loop, you’ll get a json block for each background task.  Here’s an example of changing a single password:

echo '{ "password": { "length":10, "include_special":true, "include_numbers":true, "include_uppercase":true } }' | PUT /servers/`IdOfServer Web6`/accounts/jparker/password

Which returns:

{"command":{"id":"88889999aaaabbbbccccddddeeeeffff",
"url":"https://portal.cloudpassage.com/api/1/servers/00001111222233334444555566667777/commands/88889999aaaabbbbccccddddeeeeffff",
"name":"Reset Password","status":"queued","created_at":"2012-02-28T21:31:47Z","updated_at":"2012-02-28T21:31:47Z"}}

To see the actual changed password, view the URL returned in the block.  Because our command line “GET” automatically prepends “https://portal.cloudpassage.com/api/1/”, I’ll leave that part off the request:

GET /servers/00001111222233334444555566667777/commands/88889999aaaabbbbccccddddeeeeffff   
{"command":{"id":"88889999aaaabbbbccccddddeeeeffff",
"url":"https://portal.cloudpassage.com/api/1/servers/00001111222233334444555566667777/commands/88889999aaaabbbbccccddddeeeeffff",
"name":"Reset Password","status":"completed","created_at":"2012-02-28T21:31:47Z","updated_at":"2012-02-28T21:32:03Z","result":{"password":"/s42^6qZDt"}}}

2. Create accounts

for OneServer in `ActiveHostnamesInGroup 'Web Servers'` ; do
   echo "======== $OneServer"
   #Put as many accounts as you want to affect between "in" and ";", space separated.
   for OneAccount in zparker99 qsmith375 ; do
       echo "== $OneAccount"

     echo '{"account":{"username":"'"$OneAccount"'","comment":"User '"$OneAccount"'","groups":"users",
"password":{"length":10,"include_special":true,"include_numbers":true,"include_uppercase":true}}}' 
        | POST /servers/`IdOfServer "$OneServer"`/accounts
   done
done

You can modify the password length and complexity options in the input json block to match your security policy.
As with the “reset password” jobs above, if the requests succeed you’ll get back json blocks for each background job like this:

{"command":{"id":"ffffeeeeddddccccbbbbaaaa99998888",
"url":"https://portal.cloudpassage.com/api/1/servers/33332222111100007777666655554444/commands/ffffeeeeddddccccbbbbaaaa99998888",
"name":"Create Account","status":"queued","created_at":"2012-02-28T22:57:22Z","updated_at":"2012-02-28T22:57:22Z"}}

And like before, to see the password assigned, query the URL in that block:

GET /servers/33332222111100007777666655554444/commands/ffffeeeeddddccccbbbbaaaa99998888
{"command":{"id":"ffffeeeeddddccccbbbbaaaa99998888",
"url":"https://portal.cloudpassage.com/api/1/servers/33332222111100007777666655554444/commands/ffffeeeeddddccccbbbbaaaa99998888",
"name":"Create Account","status":"completed","created_at":"2012-02-28T22:57:22Z","updated_at":"2012-02-28T22:58:17Z","result":{"password":"hg_72//:B;"}}}

If a command fails, the json block returned will show the reason for the error, like this one when I try to create the account again:

{"command":{"id":"00000000111111112222222233333333",
"url":"https://portal.cloudpassage.com/api/1/servers/44444444555555556666666677777777/commands/00000000111111112222222233333333",
"name":"Create Account","status":"failed","created_at":"2012-02-28T23:18:54Z","updated_at":"2012-02-28T23:19:06Z",
"result":"error code: 9 message: useradd: user testuser22 exists"}}

3. Disable accounts

for OneServer in `ActiveHostnamesInGroup 'Web Servers'` ; do
   echo "======== $OneServer"
   #Put as many accounts as you want to affect between "in" and ";", space separated.
   for OneAccount in zparker99 qsmith375 ; do
       echo "== $OneAccount"

       echo '{"account":{"active":false}}' 
        | PUT /servers/`IdOfServer "$OneServer"`/accounts/"$OneAccount"
   done
done

In addition to being able to check the output of each PUT, GET, POST, or DELETE command, you can also query the return code from the command.  Here’s an example of how to check the return code:

...
     echo '{"account":{"active":false}}' 
        | PUT /servers/`IdOfServer "$OneServer"`/accounts/"$OneAccount"
    if [ "$?" -eq 0 ]; then
        echo "Successfully disabled $OneAccount"
    else
        echo "WARNING: unable to disable $OneAccount"
    fi
    ...

4. Remove accounts

And finally, let’s remove those test accounts:

for OneServer in `ActiveHostnamesInGroup 'Web Servers'` ; do
   echo "======== $OneServer"
   #Put as many accounts as you want to affect between "in" and ";", space separated.
   for OneAccount in zparker99 qsmith375 ; do
       echo "== $OneAccount"

       DELETE /servers/`IdOfServer "$OneServer"`/accounts/"$OneAccount"
   done
done

Cheers!  — Bill

Stay up to date

Get the latest news and tips on protecting critical business assets.

Related Posts