VOV Security Keys

VOV security keys provide an alternative REST authentication method to providing a username/password. Security keys are more convenient and more secure than username/password authentication. The same keys are used to allow RDS to be run in a highly secure mode where RDS network clients authenticate to RDS servers using the same keys.

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

Creating a new VOV security key pair can be done by calling vovsecurity keygen.

Before using the vovsecurity tool, you should have previously called vovproject enable PROJECTNAME.

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.

Register Your Public Key on a vovserver Instance

To register a public key with the vovserver in the current project, first print the value of your public key:
> vovsecurity getkey
32:wlefJq4QjTm2QgtWTUtD7kG11nCyUtrKfobW/HPfoD0=

Everything on the second line is the value of the public key that is stored in $HOME/.vov/userkey, up to and including the ‘=’ character.

Now that you have the value of your public key, use an enabled shell to register it with the vovserver in the current project:
vovsecurity addkey -kv 32:wlefJq4QjTm2QgtWTUtD7kG11nCyUtrKfobW/HPfoD0= -kd "My Main Key"
Enter your password:

When vovsecurity communicates with a vovserver instance, it intentionally does so via REST and uses username/password-based authentication to ensure the caller isn’t trying to impersonate another user. You will be prompted to enter your password so that the vovsecurity tool can authenticate on your behalf using password authentication. If you find entering your password to be inconvenient, you can set the environment variable VOV_PASSWORD to the value of your password. vovsecurity will use this value instead of prompting for it at the console. We generally do not recommend keeping your password set in an environment variable.

If the operation succeeds, vovsecurity will return to the command prompt with no further error messages. You may get an error if password authentication fails, or if you’ve already added your public key to the vovserver instance.

Listing Your Public Keys on a vovserver Instance

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.

Using Key Based Authentication from a Python-Based REST Client

To perform key based authentication with the REST API, the following must be true:
  • The webport must be non-zero
  • The webprovider must be set to “internal”
  • ssl.enable should be set to 1 in policy.tcl
  • The REST client needs to know the user’s private key (by reading it out of ~/.vov/userkey).
  • The REST client needs to know the vovsever’s public key. This can be retrieved by calling vovsecurity getserverkey
A REST request can be programmed from Python by using method functions for the VOVRestV3 class in the vov_rest_v3.py module included in $VOVDIR. Use the authorizeWithKey() function to authenticate, followed by the submitRequest() function to issue the REST request.
#
# nc_rest_101_vov_keys.py
#
import os, sys
import vov_rest_v3

url = os.environ['NC_URL']
secret_key = os.environ['MY_SECRET_KEY']
sp_key = os.environ['NC_SERVER_PUBLIC_KEY']
user = os.environ['USER']
vrest2 = vov_rest_v3.VOVRestV3()
vrest2.authorizeWithKey(url, secret_key, sp_key, username=user)
r = vrest2.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