Using the Anaplan Certificate with the Anaplan REST API: A Comprehensive Guide

AnaplanOEG
edited February 22 in Best Practices

If you're diving into integrating with the Anaplan REST API using an Anaplan Certificate, you'll need to know certain requirements. This post is a step-by-step guide to ensure a seamless connection using Python. If you'd prefer to avoid a programmatic approach to using your certificate, then please check out the CA Certificate Auth Generator.

Requirements for Using Anaplan Certificate with REST API:

  • Making a POST Call:
    Initiate a POST call to https://auth.anaplan.com/token/authenticate.
  • Adding a Header:
    The Connection header key should encapsulate the Anaplan public key with the string CACertificate followed by a space and the entire Anaplan public key:
Connection: CACertificate MIIFeDCCBGCgAwIBAgIQCcnAr/+Z3f... (continues)
  • Pass a Random String:
    Incorporate a 100-byte random string of data within the body of the REST API call. Here is an example of a 100-byte random string:
codeJlkj3$9kds*lKD09jlkjasD9021!lsjd2309sdlsjLJDSJfdi21l3ekds92jLJSFdfDJSld3i0d9flsjd3
  • Sign the String and Format the Body:
    This random string has to be signed using the certificate's private key. When you're preparing the body of your API call, it should have this structure:
{
   "encodedData": "ACTUAL_ENCODED_DATA_VALUE",
   "encodedSignedData": "ACTUAL_SIGNED_ENCODED_DATA_VALUE"
}

Note: The placeholders ACTUAL_ENCODED_DATA_VALUE and ACTUAL_SIGNED_ENCODED_DATA_VALUE need to be substituted with the genuine values that you get from encoding and signing your data. The curly braces themselves remain unchanged.

  • PEM Format:
    Both the Public Certificate and the Private Key must conform to the PEM format. PEM, standing for 'Privacy Enhanced Mail,' is the go-to format for saving and transferring cryptographic keys, certificates, and other forms of data. It's easily recognizable due to its delimiters (-----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----) for public certificates and (-----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----) for private keys.

A Handy Python Implementation:

To make things more practical, here's a Python script to help generate the mandatory strings for encodedData and encodedSignedData:

from base64 import b64encode
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA512
import json

# Generate the encodedData parameter
def create_encoded_data_string(message_bytes):
    # Step #1 - Convert the binary message into Base64 encoding:
    # When transmitting binary data, especially in text-based protocols like JSON,
    # it's common to encode the data into a format that's safe for transmission.
    # Base64 is a popular encoding scheme that transforms binary data into an ASCII string,
    # making it safe to embed in JSON, XML, or other text-based formats.
    message_bytes_b64e = b64encode(message_bytes)
                
    # Step #2 - Convert the Base64-encoded binary data to an ASCII string:
    # After Base64 encoding, the result is still in a binary format.
    # By decoding it to ASCII, we get a string representation of the Base64 data,
    # which is easily readable and can be transmitted or stored as regular text.
    message_str_b64e = message_bytes_b64e.decode('ascii')
        
    return message_str_b64e
        
# Generate the encodedSignedData parameter
def create_signed_encoded_data_string(message_bytes, key_file, passphrase):
    # Step #1 - Open the private key file for reading:
    # Private keys are sensitive pieces of data that should be stored securely.
    # Here, we're reading the private key file from the disk using Python's file I/O functions.
    key_file_content = open(key_file, 'r', encoding='utf-8').read()

    # Step #2 - Import the RSA private key:
    # The RSA private key is imported from the previously read file content.
    # If the key is encrypted, a passphrase will be required to decrypt and access the key.
    my_key = RSA.import_key(key_file_content, passphrase=passphrase)

    # Step #3 - Prepare the RSA key for signing operations:
    # Before we can use the RSA key to sign data, we need to prepare it using
    # the PKCS#1 v1.5 standard, a common standard for RSA encryption and signatures.
    signer = pkcs1_15.new(my_key)

    # Step #4 - Create a SHA-512 hash of the message bytes:
    # It's common practice to create a cryptographic hash of the data you want to sign
    # instead of signing the data directly. This ensures the integrity of the data.
    # Here, we're using the SHA-512 algorithm, which produces a fixed-size 512-bit (64-byte) hash.
    message_hash = SHA512.new(message_bytes)

    # Step #5 - Sign the hashed message:
    # Once the data is hashed, the hash is then signed using the private key.
    # This produces a signature that can be verified by others using the associated public key.
    message_hash_signed = signer.sign(message_hash)

    # Step #6 - Encode the binary signature to Base64 and decode it to an ASCII string:
    # Similar to our earlier function, after signing, the signature is in a binary format.
    # We convert this to Base64 for safe transmission or storage, and then decode it to a string.
    message_str_signed_b64e = b64encode(message_hash_signed).decode('utf-8')

    return message_str_signed_b64e
        
def create_json_body(encoded_data_value, signed_encoded_data_value):
    data = {
        "encodedData": encoded_data_value,
        "encodedSignedData": signed_encoded_data_value
    }
    return json.dumps(data, indent=4)
        
# create random 100 byte message
message_bytes = get_random_bytes(100)

# Provide path to the private key in the PEM format
private_key_file = './quin_eddy_private.key'

# Provide the private key passphrase. If there is no passphrase, please insert None
private_key_file_passphrase = None

# Create the encoded data string
message_str_b64e = create_encoded_data_string(message_bytes=message_bytes)

# Create an encoded signed data string
message_str_signed_b64e = create_signed_encoded_data_string(
   message_bytes=message_bytes, key_file=private_key_file, passphrase=private_key_file_passphrase)
      
# Get the formatted body for the Anaplan API token authentication endpoint (https://auth.anaplan.com/token/authenticate) to generate an access_token
certificate_api_body = create_json_body(
   encoded_data_value=message_str_b64e, signed_encoded_data_value=message_str_signed_b64e)
      
print(certificate_api_body)

This script employs the PyCryptodome library for the signing process. If you haven't done so already, make sure to install it:

pip install pycryptodome

That's all there is to it! Adhering to these guidelines and using the shared script, your integration with the Anaplan REST API via an Anaplan Certificate should be a breeze.

It is not hard. Please take a look at this short demonstration using Postman to login to Anaplan using the encoded and signed strings:

Authors: Quin Eddy, @QuinE & Adam Trainer, @AdamT - Operational Excellence Group (OEG)

Comments

  • Thanks for yet another comprehensive guide, Quin!

  • CA Cert is an acceptable form of Client Auth for API connectivity. However, it does have a cost associated with it. Here a few things not mentioned here you need to be aware of:

    1. Potential security risk: It's not ideal for end Users to have Certs (public and private keys) on their desktop. Certs can be emailed outside an organization and used in an unauthorized fashion. Certs should be stored in a Vault (ie www.hashicorp.com) and only retrieved at runtime in a programmatic manner. I've seen several tutorials here where folks manual copy and paste keys to generate Auth tokens. This will make any security officer cringe.

    2. Operational cost: Certs have a huge operational overhead. Certs provisioned are only valid for 1 year. My org has +8 Anaplan implementation and some +20 Certs that expire various times in year - it takes at least 3 teams (divisional Anaplan Team, COE and PKI) several hours to renew and install a new Cert as we need to work around various critical batches.

    Expiration: It's very easy to lose track of a Cert if you do not have any mechanism in place to monitor and alert on upcoming expiration. If a cert renewal is missed and the cert expires, your system will go down. For my org I built a process that scans all the Certs daily and flags anything expiring within 60 days. You will be surprised how many software systems become inaccessible because someone forgot to renew a certificate.

    3. Cost: Most organizations can generate an SSL Cert via openssl from a windows and Unix box. CA Certs must be provisioned by CA Authority. CA Cert/Web Client Auth Cert is a very specific cert so pricing varies based on the vendor you use.


    What about Anaplan's current implementation of OAuth?

    The current implementation of OAuth by Anaplan is a user-base flow. Systematic processing needs a flow that can handle generic user accounts. ie myorg_datauser_batch@mycompanyc.com

    Basically, we need to be able to generate a client id and security associated with an existing Anaplan User.