Rubrik : How to run a script with 2FA enabled
Recently, Rubrik is enforcing the 2FA authentication (Multi Factor Authentication). This is a great security feature that minimizes the risk of unauthorized access to your environment. But how does it work within a script ? There is no way to enter a 2FA code when running a script every day at 2 am for example. The 2FA is enforced starting at CDM v 8.0.2 with a 3 month grace period to allow the customers to adjust their security.
Service Accounts
Aside with the regular user accounts (Local and LDAP), there is a way to create a service account. Service Accounts are meant to be used in a script, but then they skip the 2FA ?
Yes, this is actually more complex that than. A service account will automatically get an ID and a Secret, they both will be used to generate a token and this token got a limited time to live (TTL). The token is then used as a bearer in the API you will consume. After 24 hours, the token will expire and you will require a new one.
If you are not new here, you probably remember we already played around with authentication tokens. This is similar.
The best practice recommended by Rubrik in the Administrator Guide (from page 155 onward) proposes the following sequence :
- Using your service ID and Secret to generate a token;
- Do your stuff in your script;
- Then delete the token to cleanup the authentication stack
You can do that for every script use case you have.
That complexity in getting a valid access, will ensure your environment is safe from any intrusion.
First thing first, let's create a service account. In the CDM UI, go to the gear menu and select Users under Access Management.
From there, you have few tabs at the top, chose Service Accounts
Click on Add Service Account and enter the name your want to identify the purpose (that name is not used anywhere else) and the role it will received when you will use it. Push Add
When done, an ID and a secret will be assigned to the service account. Secure the two strings in a safe place, there is no way to get the secret after this window is closed.
Going back to the main screen, you see your service account name and the associated role.
Again, this is very important to secure those strings somewhere in a safe place, we will use them in the next section.
Ok, we have setup the backend, let's use it now ! I have added 2 new functions to my Php Framework : one to create a token and one to delete the token when finished.
function rkGetServiceAccountToken($clusterConnect)
{
$API="/api/v1/service_account/session";
$config_params="
{
\"serviceAccountId\": \"".$clusterConnect["username"]."\",
\"secret\": \"".$clusterConnect["password"]."\"
}
";
$curl = curl_init();
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS,$config_params);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json','Content-Length: ' . strlen($config_params),'Accept: application/json'));
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_URL, "https://".$clusterConnect["ip"].$API);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$result = json_decode(curl_exec($curl));
curl_close($curl);
return($result->token);
}
I guess you've seen this API call is not using traditional authentication. Indeed, the call itself is anonymous, but the credentials are in the payload.
The big difference here is in the $clusterConnect variable, we need to use the ID and Secret we secured in the earlier steps.
Example :
// Rubrik cluster connection settings
$clusterConnect=array(
"username" => "User:::c72[...]6ca2cc",
"password" => "XKZGDdKEET8KLTR60Mu6W3[...]MKKuxaR96xRc0SQl5O9lrIqZ8f",
"ip" => "1.2.3.4"
);
Now, let's take an existing function and see how we can convert it into using the service account token. Let's take the rkGetUpgradeHistory function. This is a simple GET query returning the cluster upgrade history.
I have added a new dimension to the $clusterConnect array which is storing the token if it exists. Indeed, all the functions of the framework are using basic authentication (username and password). If a token has been generated, we need to store it and use it. This is the way I've found to easily convert all existing functions for using the token.
In a short period of time, all the functions of the framework won't work anymore since the 2FA will be mandatory. So, I'm rushing to convert them to the token based authentication.
So, once the token has been generated, I'm storing it into $clusterConnect :
$authToken=rkGetServiceAccountToken($clusterConnect);
$clusterConnect["token"]=$authToken;
Then, when passing the $clusterConnect to any function, I'm doing a logical test to detect if the token is used :
[...]
if(isset($clusterConnect["token"]))
[...]
If the token is not set, then the basic authentication method is used as before. It will ensure all functions work in any circumstances.
Now, we can use the adapted function this way :
$res=rktGetUpgradeHistory($clusterConnect)
At this stage, this is not very important to show the result, but trust me, it works ;)
Lastly, following Rubrik's best practice, we need to remove the token to flush the stack. According to the documentation, only 10 tokens per service account are allowed at once. So, it's better to clean them up as much as we can.
I have created a function to flush the token for the current session.
function rkDelServiceAccountToken($clusterConnect)
{
$API="/api/v1/session/me";
$curl = curl_init();
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_URL, "https://".$clusterConnect["ip"].$API);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Authorization: Bearer '.$clusterConnect["token"]));
$result = json_decode(curl_exec($curl));
$info=curl_getinfo($curl,CURLINFO_HTTP_CODE);
curl_close($curl);
// Return code wheen successful is 204
if($info==204) return TRUE;
else return FALSE;
}
The returned code, when the session has been successfully flushed, is 204. The function intercept this code and returns TRUE when session is flushed correctly.
You can find the sample code on my GitHub repository.
Conclusion
As a conclusion, I would say, security is enhanced by using a token with a time limited validity. However, for some specific use case, there is something to take into consideration : I have a script that runs for almost 2 days. This is when I'm doing backup integrity checks. I don't know what's happening in that case. To be on the safe side, I think I will generate a new token for each workload verification.
2023 is the year where the framework will be rebuilt, that's for sure ! And this is a lot of work.
I hope this helps !
Nice article. Should be useful for others too.
ReplyDeleteGreat to know!
ReplyDelete