VOV Security Keys

The VOV system supports a public/secret key pair-based security and authentication mechanism called VOV Security Keys. VOV security key authentication is the preferred authentication mechanism for REST and is also the basis for highly secure connections between Accelerator and the Event port on Monitor when RDS resource management is activated.

VOV security keys are based on ed25519 public key cryptography. Conceptually, they are like SSH keys:
  • You create a public/private key pair.
  • You use some other authentication means to connect to the server, and tell the server to trust your public key and associate it with your identity.
  • You use your private key as part of an authentication "handshake" with the server, and it verifies your identity using your matching public key.

It’s important to remember that you do not need a new public/private key pair for each queue where you intend to use key based authentication. You can create one key pair and set up the public half of your key on all the vovservers you wish to authenticate against.

VOV security keys in no way replace the security rules and policies enforced by security.tcl or Access Control Lists. The keys are only a means to authenticate the user, after which Security Levels and Access Control Lists still come into play.

Create a Public/Private Key Pair

Method 1 - Using the VOV CLI
If the host where the REST client will execute has access to the VOV software installation, you can use these VOV CLI commands:
% vovproject enable PROJECT
% vovsecurity keygen
By default, vovsecurity keygen writes the newly generated key pair into $HOME/.vov/userkey. If that file already exists, you will be prompted on whether you wish to overwrite the file. Overwriting the file means you will no longer have access to the previous key pair that you may have used to establish key based authentication on other vovserver instances.
Method 2 - Using the Browser
If the client host does not have access to the VOV command line interface on the same local network where the vovserver is running, then this method will work.
Browse to URL <VOV_URL>/api/v3/apikeys/newkey
The browser will display a generated random key data in a JSON format. The displayed data shows the new secret key followed by the corresponding public key.
Method 3 - Using Curl
This is equivalent to the prior method, but using the Linux curl command to access the target URL:
% curl -k -X GET   <VOV_URL>/api/v3/apikeys/newkey  | jq '.rows[0]'

Register Your Public Key on a vovserver Instance

Method 1 - Using the VOV CLI
If you have access to a host on the LAN where the vovserver is running and the VOV CLI commands, the CLI key registration method is convenient. Normally the public VOV security key is stored in $HOME/.vov/userkey, and the vovsecurity getkey command simply reads the public key. The vovsecurity addkey command will prompt for this user’s password to authenticate the registration request, which is implemented behind the scenes as an authenticated REST request.
% vovproject enable PROJECT  
% pubkey=$(vovsecurity getkey) 
% vovsecurity addkey -kv $pubkey -kd "My Cool Key" 
Enter your password for connecting to PROJECT on HOST: ************* 
To script the above sequence without being interactively prompted for a password, set the VOV_PASSWORD environment variable to the value of your password before invoking the vovsecurity addkey command.
Method 2 - Using the VOV REST API Interactive Web UI Page
The VOV project’s web UI provides a means to register a user’s public key at vovserver. To register a public key, follow this procedure:
  1. Browse to the VOV REST API Interactive Web UI Page at <VOV_URL>/ html/vovrest.html
  2. Login with the user name that corresponds to the public key being registered.
  3. Scroll down to the apikeys topic and click on the POST button beside the /apikeys Add Key line.
  4. Click Try it Out on the right side of the page.
  5. Edit the Request Body JSON to add your public key for the “value” keyword, and to add a description string for the “description” keyword.
  6. Click Execute.


Figure 1.

Listing Your Public Keys on a vovserver Instance

Method 1 - Using the VOV CLI
You can list the public keys that have already been registered on a vovserver instance, using the following command in an enabled shell:
% vovsecurity listkeys
user1 32:Ihq6djlx5L9XQzRWRWy0Z2hl5fD64JhJoqa7FpDmiyg= Backup key
user1 32:+fmR1SmWValRspQjcLQ7OGX266MZj/m90B5fii3FMTY= 2nd key
user1 32:wlefJq4QjTm2QgtWTUtD7kG11nCyUtrKfobW/HPfoD0= 3rd Key
In this case, user1 has registered multiple public keys on the same vovserver instance.
Method 2 - Using the VOV REST API Interactive Web UI Page
The VOV project’s web UI provides a means to list a user's registered public keys Follow these steps:
  1. Browse to the VOV REST API Interactive Web UI Page at <VOV_URL>/ html/vovrest.html
  2. Login with the user name that corresponds to the public key being registered.
  3. Scroll down to the apikeys topic and click on the GET button beside the /apikeys List Key line.
  4. Click Try it Out on the right side of the page.
  5. Click Execute.
  6. Scroll down to the Response Body sub-window and view the JSON description of the list of registered public keys.

Using Key Based Authentication from a Python-Based REST Client

REST client authentication is typically done through VOV security keys that are generated and properly registered with a vovserver instance and stored in a local file on the client. The key based authentication is handled automatically inside the submitRequest() Python REST member function described below.

To perform key based authentication with the REST API, the following must be true:
  • The vovserver webport must be non-zero (this is true by default).
  • The vovserver webprovider must be set to “internal” (this is the default vale).
  • The ssl.enableconfiguration parameter should be set to 1 (this is the default value)
  • A VOV security key pair should be generated and stored on the client host in a file (normally ~/.vov/userkey), in the following format. This can be done using the vovsecurity keygen command or the REST program shown in Create a Public/Private Key Pair above.
    % cat ~/.vov/userkey 
    secret-key = 32:WUhyiwOUBYqonfVR66fNVnHd6sfK9QbK257A8jheYfc= 
    public-key = 32:NIqRzIOhv0r7GZFTPkL0nQLZX6U6UTihleHhFwlVqnQ=
  • Key pairs must be registered with a vovserver via the vovsecurity addkey command or the REST program shown in Register Your Public Key on a vovserver Instance above.
    The simplest way to program a REST request using key-based authentication is to pass the two required arguments to the submitRequest() method function for the VOVRestV3 class in the vov_rest_v3.py module that is included in $VOVDIR. Before running this Python program, perform the following setup from an enabled shell:
    % export VOV_URL=$(vovbrowser) 
    % cp $VOVDIR/scripts/python/vov_rest_v3.py .
    This Python program can be run with Python version 3 or higher. The submitRequest() function will automatically take care of these prerequisite actions before issuing a REST request:
    1. Retrieve the server public key
    2. If the first REST call, or if the underlying REST access token is expired, then the user will be authenticated using the secret VOV key from ~/.vov/userkey. If the secret VOV key is stored in a different file, then add the keyword parameter authKeyFile=filename to the argument list in the submitRequest() function call.
    # 
    # easy_REST_key_101.py 
    # 
    import os, sys, vov_rest_v3 
    url = os.environ['VOV_URL'] 
    vrest = vov_rest_v3.VOVRestV3() 
    r = vrest.submitRequest("GET", url + "/api/v3/project/1/project") 
    print (r) 

The following example shows an alternative to the above. It explicitly authenticates using the authorizeWithKey() method function prior to calling submitRequest(). This allows one user impersonate another user whose secret key is known. It also allows explicit passing of the secret key as an argument instead of passing the name of a local file containing the key.

Before running the Python program below, take these preparatory steps:
  • Set VOV_URL and copy vov_rest_v3.py locally:
    % export VOV_URL=$(vovbrowser) 
    % cp $VOVDIR/scripts/python/vov_rest_v3.py .
  • Set the MY_SECRET_KEY environment variable to the user secret VOV security key (normally viewed in ~/.vov/userkey)
  • Set the VOV_SERVER_PUBLIC_KEY environment variable to the value of the server public key that is assigned to a VOV project at creation time. There are three ways to do this:
    # Method 1  
    Browse to URL   <VOV_URL>/api/v3/apikeys/serverkey 
    
    # Method 2 
    % curl -k -X GET   <VOV_URL>/api/v3/apikeys/serverkey | jq '.rows[0][0]'  
    
    # Method 3 
    % vovproject enable PROJECT 
    % vovsecurity getserverkey 
This example explicitly calls the authorizeWithKey()method function to authenticate, followed by the submitRequest() function to issue the REST request. This might be convenient if multiple key pairs are registered with the vovserver for this user, and the desired key pair is not currently present in ~/.vov/userkey.
# 
# explicit_REST_key_101.py 
# 
import os, sys 
import vov_rest_v3 
 
url = os.environ['VOV_URL'] 
secret_key = os.environ['MY_SECRET_KEY'] 
sp_key = os.environ['VOV_SERVER_PUBLIC_KEY'] 
user = os.environ['USER'] 
vrest = vov_rest_v3.VOVRestV3() 
vrest.authorizeWithKey(url, secret_key, sp_key, username=user) 
r = vrest.submitRequest("GET", url + "/api/v3/project/1/project") 
print (r)

Advanced Key Handling

The REST client will use the libsodium library to construct an encryption message which is a combination of the user’s private key and vovserver’s public key, which will be sent to REST at the endpoint /api/v3/token with grant_type set to apikey and apikey set to the contents of the encrypted message.

If you wish to handle REST sessions yourself, look inside vov_rest_v3.py at the _getEncryptedMessage() procedure to see how the authentication handshake message is composed using the user’s private key and the vovserver’s public key. In _getEncryptedMessage(), you can see how it decodes the two key strings into raw bytes, creates an PyNaCl Box object using the two keys, and then encrypts the message by calling Box.encrypt(). The encrypted message is then base-64 encoded and returned as a string that the vovserver REST API can understand.

def _getEncryptedMessage(self, userseckey, serverpubkey, message=None):
    """Generated an encrypted message block using the caller's secret key and the server's public key.
 
    :type userseckey string
    :param userseckey User's secret key.  It should be in base64 format with the binary length prepended,
                      and a colon separating the length and the base64 string.
 
    :type serverpubkey string
    :param serverpubkey vovserver's public key.  It should be in base64 format with the binary length prepended,
                      and a colon separating the length and the base64 string.
                      This can be retrieved from $VOVDIR/local/registry/ as the 'CLIENT_PUBKEY' entry.
 
    :type message string
    :param message The message to encode.  Optional. If None, a generic default message is used. It doesn't
                   effect the key authentication process in any way.
 
    :rtype: string
    :return: A string containing a base64 encoded, then url-encoded buffer.
    """
    idx = userseckey.find(':')
    if idx == -1:
        return ""
    buflen = int(userseckey[:idx])
    userseckey = userseckey[idx + 1:]
    seckeybuf = base64.b64decode(userseckey)
    seckeybuf = seckeybuf[:buflen]
 
    idx = serverpubkey.find(':')
    if idx == -1:
        return ""
    buflen = int(serverpubkey[:idx])
    serverpubkey = serverpubkey[idx + 1:]
    pubkeybuf = base64.b64decode(serverpubkey)
    pubkeybuf = pubkeybuf[:buflen]
 
    if message is None:
        message = 'VOV API Key Client Auth ' + str(time.time())
    messageBuf = message.encode("utf-8")
    secretKey = PrivateKey(seckeybuf)
    publicKey = PublicKey(pubkeybuf)
 
    box = Box(secretKey, publicKey)
    cipher = box.encrypt(plaintext=messageBuf)
    cipherLen = len(cipher)
    encodedCipher = base64.b64encode(cipher)
    cipherStr = str(cipherLen) + ':' + encodedCipher.decode("utf-8")
    return cipherStr
The following code snippet demonstrates how to get a REST session token using the _getEncryptedMessage() procedure above:
url = 'https://hostname:9100/api/v3/token'
clientSecret = ''
postData = {}
postData["grant_type"] = "apikey"
postData["client_id"] = "Python client"
postData["client_secret"] = clientSecret
postData["username"] = “myusername”
postData["apikey"] = self._getEncryptedMessage(userseckey, serverpubkey)
session = requests.Session()
response_obj = session.post(url, timeout=self.connectionTimeout, verify=False,                      
                            data=postData)
respContent = response_obj.text
respStatus = response_obj.status_code
 
if (respStatus == 200):
    contentJSON = json.loads(respContent)
    if ("access_token" in contentJSON):
        sToken = contentJSON['access_token']

Once you have the session token, you can use it for subsequent REST API requests, similar to previous REST examples which used username/password authentication to get the session token.

Deleting A Key from vovserver

You can delete your own key from vovserver using the vovsecurity command:
vovsecurity delkey -kv 32:jA0iq1mLbdSuuDKjlPeEoHJX+fYqR/0HxpI5nhgThE8=
Users with ADMIN level security access in the project can list and delete keys belonging to other users:
> vovsecurity listkeys -a
user1	            32:jA0iq1mLbdSuuDKjlPeEoHJX+fYqR/0HxpI5nhgThE8=	Primary Key
user2            	32:Ihq6djlx5L9XQzRWRWy0Z2hl5fD64JhJoqa7FpDmiyg=	Primary Key
user2            	32:+fmR1SmWValRspQjcLQ7OGX266MZj/m90B5fii3FMTY=	2nd key
user2            	32:wlefJq4QjTm2QgtWTUtD7kG11nCyUtrKfobW/HPfoD0=	3rd Key
> vovsecurity delkey -kv 32:jA0iq1mLbdSuuDKjlPeEoHJX+fYqR/0HxpI5nhgThE8= -u user1