Tableau Services Manager (TSM) API —The undocumented “Passwordless” Authentication
By: Tamas Foldi
Tags: Dataviz, Tableau Server, Telegraf, Monitoring, Tsm Api

Tableau is a complex platform with tons of APIs for analysts, developers, and platform administrators. If you are a server admin, most probably you’ve already used REST API for basic stuff like managing users and contents. However, platform level activities such as starting and stoping the Server, creating backups, getting detailed status information about each service, changing topology, or retrieving license information rely on a different API: the Tableau Services Manager (TSM) API.

Tableau Services Manager’s API is still in alpha status with version 0.0

Usually, TSM API is used mostly from the tsm command-line utility, which is part of the Server installation. But you can use the TSM API to perform all the functionalities in tsm remotely, invoking simple HTTP endpoints. Some of the things you can do by this API are:

Start and stop Tableau Server

View the status of services and nodes

Back up and restore Tableau Server

Make configuration and topology changes

Change port assignments for services

Create log file archives

In this post, I will go thru on a basic use case: getting status information from a Tableau Server cluster, its nodes, and its services. We will check how to authenticate and get information from TSM as well as see TSM’s hidden gem: the passwordless login support.

But before trying to reimplement tsm status from our own code, let’s see an alternative option from pre-TSM times for getting server status.

The serverinfo.xml endpoint

Historically, the only supported way to get status information from Tableau Server was this so-called serverinfo.xml endpoint. It supports both authentication and white-list based access: allowing to retrieve the Server status information from third party tools without implementing any complex API or authentication logic, with a simple HTTP GET call.

To allow external apps (or yourself) to access systeminfo.xml in an unauthenticated session, add your IP to the whitelist by:

tsm configuration set -k wgserver.systeminfo.allow_referrer_ips -v <ip address>

Then open /admin/serverinfo.xml :

http://my_tableau_server/admin/systeminfo.xml

It should return something like this:

This looks useful, however, it misses a few key points:

We don’t know what is desired state: it’s great that the server is stopped, but did we wanted to stop it? Or, maybe it’s running but we already started a stop command. Serverinfo has no information about our intents (the service desired state).

The serverinfo.xml only available when the vizportal is running. For real monitoring, we want to know the status all the time, not just when the Server is running.

The TSM API

This is when the TSM API could be helpful, as it gets the status directly from tabadmincontroller, which is supposed to be always running and available even when the Server is in stopped state.

The public part of the API is documented here. For getting status info, we need to call two endpoints:

login to authenticate and status to get server, nodes, and services status

Authenticating with TSM credentials

Under the hood, TSM uses OS credential validation (like PAM on Linux). All OS users can access the API who are members of the tsmadmin group.

Unlike any other regular API, TSM API uses cookies for subsequent authenticated calls after login. While it sounds like an anti-pattern, I guess the reason for this cookie based security is that the API was primarily created for Tableau’s own web app, where cookie-based implementation is required anyway. Long story short, the /login endpoint sets a cookie called AUTH_COOKIE , and you should pass that cookie to all requests until you log out.

Login from the command line:

curl -k https://localhost:8850/api/0.5/login -X POST -H "Content-Type: application/json" --data '{

"authentication": {

"name": "tsmuser",

"password": "tsmpassword"

}

}' -v * About to connect() to localhost port 8850 (#0)

* Trying 127.0.0.1...

* Connected to localhost (127.0.0.1) port 8850 (#0)

[...]

> POST /api/0.5/login HTTP/1.1

> User-Agent: curl/7.29.0

> Host: localhost:8850

> Accept: */*

> Content-Type: application/json

> Content-Length: 104

>

< HTTP/1.1 204 No Content

< Date: Wed, 23 Dec 2020 08:16:55 GMT

< X-Application-Context: application

< Cache-Control: no-store

< Set-Cookie: AUTH_COOKIE=eyJjdHkiOiJKV1QiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..xpoNg5Hg3n9AvLFN40ND7Q.13LYw0uQT9bDF2V7R-fMu3rXWpNg7vQk7QQeLbsUUqM1pEkoLNouizD-kco2gkms0b_mn2rOIGek7htCxnf13bF5Y3ZLogNwMpQIHBIrt9Y4mapIZvLdIB_kmwaTpqLWvAjz5rpVo7l4uGUd42cvDXkOJbFElyuHwRgfEr_hXhj7lWRyB2iDJm1HBbqBovGaIsUCEvu8m6PE7UmglmFyQFW7ys3NuoKIq46Dcv4HnBD0S7bpkNZOEeMWx-0b9t.xazmQ1ReLQk-y0g7RoELL;Path=/;Expires=Wed, 23-Dec-2020 10:16:56 GMT;Max-Age=7200;Secure;HttpOnly

< Expires: Thu, 01 Jan 1970 00:00:00 GMT

< X-Content-Type-Options: nosniff

< X-XSS-Protection: 1; mode=block

< X-Frame-Options: DENY

< Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';

That ugly cookie is our ticket for the next calls.

Get system status info

Getting the status as simple as a GET /status , just like:

If all good (and why wouldn’t be), then we should see something like:

{

"clusterStatus": {

"nodes": [{

"services": [{

"serviceName": "filestore",

"instances": [{

"code": "ACTIVE",

"processStatus": "Active",

"instanceId": "0",

"timestampUtc": 1497060680268,

"currentDeploymentState": "Enabled",

"binaryVersion": "<build>"

}],

"rollupStatus": "Running",

"rollupRequestedDeploymentState": "Enabled"

}, {

"..."

}],

"nodeId": "node1",

"rollupStatus": "Running",

"rollupRequestedDeploymentState": "Enabled"

}],

"href": "/api/0.5/status",

"rollupStatus": "Running",

"rollupRequestedDeploymentState": "Enabled"

}

}

This is exactly what we wanted, detailed status for each service (we will see more services here than in serverinfo.xml ), desired state, and last, but not least, the result is in a fancy JSON format, so we don’t have to parse XML (which sucks anyway).

Using TSM Passwordless authentication

So what if I do not want to use my precious OS user credentials in monitoring tools? How can I interact with TSM API without username and passwords — just like with systeminfo ? Fear not, Tableau supports passwordless login beginning from 2019.2. The documentation mentions two requirements:

The account you are running commands with is a member of the TSM-authorized group, by default, the tsmadmin group. The Tableau unprivileged user (by default, the tableau user) and root account may also run TSM commands.

group. The Tableau unprivileged user (by default, the user) and root account may also run TSM commands. You are running commands locally on the Tableau Server that is running the Tableau Server Administration Controller service. By default, the Tableau Server Administration Controller service is installed and configured on the initial node in a distributed deployment.

But how does this passwordless auth works? It is not documented (yet), but the process is fairly easy:

First, the application connects to a local named pipe (on Windows) or a UNIX domain socket (on Linux)

TSM validates the connecting process user id and group membership

If the user is part of the tsmadmin group it returns a cookie for the session. That cookie can be used as an AUTH_COOKIE for other API calls.

Quite simple, huh? It is, indeed.

The communication between our process and the TSM controller is based on Apache Thrift protocol. Thus, you can generate the passwordless authenticate code in any programming language of your choice using the thrift command.

enum PasswordLessLoginReturnCode {

PLL_SUCCESS = 0,

PLL_NOT_AUTHORIZED = 1,

PLL_ERROR = 2

}



struct PasswordLessLoginResult {

1: PasswordLessLoginReturnCode returnCode,

2: string username,

3: string cookieName,

4: string cookieValue,

5: i32 cookieMaxAge

}



service PasswordLessLogin {

PasswordLessLoginResult login()

}

After compiling the thrift file, you should be able to call login() function which will return with the coookieName and cookieValue .

An example implementation in Rust will look like this:

This will print the API results as:

Ok(PasswordLessLoginResult {

return_code: Some(PllSuccess),

username: Some("tsmsvc"),

cookie_name: Some("AUTH_COOKIE"),

cookie_value: Some("fSd[...]mC8DQ"),

cookie_max_age: Some(7200)

})

In the tabadmincontroller_node1–0.log log file, we can also see that TSM recognize our user — even without sending it over the communication channel.

2020-12-23 07:45:48.453 +0100 Thread-17 : INFO com.tableausoftware.tabadmin.webapp.impl.linux.LinuxPasswordLessLoginManager - Password-less login request from user 'tsmuser' with uid '1000'

2020-12-23 07:45:48.462 +0100 Thread-17 : INFO com.tableausoftware.tabadmin.webapp.impl.linux.LinuxPasswordLessLoginManager - User with uid '1000' and username 'tsmuser' is logged in via password-less auth

This is it. We can now get the server status from our code without storing or passing credentials over the line.

TSM functionalities for non-admins

In some circumstances, it would be great to limit who can use what functionalities in TSM. At some of our clients the Level2 support team is limited to certain actions like starting and stoping the Server, creating backups but they cannot change the configuration or reset the admin password for instance. Out of the box the authorization scheme in TSM is all or nothing: if you are logged in, you are superuser.

The way how we help our customers to fine tune TSM user levels and assign the right permissions to the right folks is to use setuid bit binaries along with passwordless login. Let’s say you have two users: tsmuser and support . tsmuser is part of the tsmadmin group but support isn’t. In that case, your binary that interacts with TSM API should be owned by tsmuser and have a setuid file system flag:

$ chmod 4755 your_tsm_app

$ ls -l your_tsm_app

-rwsr-xr-x. 2 tsmadmin tsmadin 8367064 Dec 26 08:12 your_tsm_binary

And the app should also change the effective uid (granted by the OS due to the setuid flag) to current uid:

#include <unistd.h> setuid(geteuid());

When our binary is executed by the support user, the application will switch its current user to tsmuser . Later, when the passwordless code is invoked, Tableau Service Manager will grant session tsmuser ‘s AUTH_COOKIE .

Passwordless over Network

If you are brave enough, you can even map or proxy the local domain sockets to a network port or a remote host using socat(1) . It will allow passwordless login from remote endpoints as well.

Putting everything together: tableau-monitoring-execd

I made a quick plugin for telegraf for monitoring Tableau Server using systeminfo.xml and TSM API. It uses all the above-mentioned technics to get system status information as easily and reliably as possible. The project page here:

This small app is an execution daemon for telegraf: a continously running process that collects information from external services (like Tableau Server thru TSM API) and return the results in InfluxDB’s line protocol. We use this as a critical part of our monitoring efforts, completed with proactive monitoring and alarming.