User Impersonation with X-Pack: Integrating Third Party Auth with Kibana

Editor's Note (August 3, 2021): This post uses deprecated features. Please reference the map custom regions with reverse geocoding documentation for current instructions.

X-Pack Security is a fantastic way to secure your cluster, providing both authentication and authorization via RBAC. The authentication aspect is managed by services known as realms. Security ships with a handful of built-in realms that give you the flexibility to choose how you identify your users.

For many use cases, this works extremely well -- want to manage everything internally to Elasticsearch? The Native realm is your go-to. Already using LDAP within your organization? We’ve got you covered with the LDAP realm! There’s even an Active Directory realm if you’re a Windows shop.

But what if you want to use X-Pack Security with an authentication service not covered by one of these built-in realms? You have a couple options:


  • Create a custom realm.

  • Use a reverse proxy to handle the third party authentication in conjunction with X-Pack Security’s impersonation feature and one or more of the built in realms.

  • Contact us to find out where your favorite authentication system (e.g. OAuth, SAML, Kerberos) is on our roadmap for direct support.

In this blog, I’ll walk you through a working example of the second option! We can use an authentication system like Google Sign-In to protect Kibana and translate my Google account name to a Native realm user in X-Pack Security; from there, we let Security handle authorization.

To do so, I’ll be using Bitly’s oauth2_proxy to handle the Google authentication layer and Nginx to pass the necessary* headers to Kibana. My overall architecture will look something like this:

image02.png

*Important Note: The above configuration will only work in Kibana 5.x or later. This is because we’ve implemented an additional way to trigger a login event by passing basic auth headers (rather than entering credentials in the Security UI login screen). K 5.x also allows you to whitelist headers, which allows us to pass our special “run as” header.

Install Elasticsearch + Kibana (with X-Pack for both)

First we’ll need to install an instance of Elasticsearch and Kibana (5.2+ preferred). You can pick whichever installation method you’d like, but I personally find the zipped distributions to be easiest for testing.

We’ll also need to install X-Pack for both of them:

Edit the config/kibana.yml

Uncomment the elasticsearch.requestHeadersWhitelist property and add the es-security-runas-user header to the white list. This allows Kibana to pass our “run as” header to Elasticsearch from Nginx.

elasticsearch.requestHeadersWhitelist: [ es-security-runas-user, authorization ]

If you’re using Kibana 5.2+, add the following property to allow Monitoring to function properly:

xpack.monitoring.elasticsearch.requestHeadersWhitelist: [ es-security-runas-user, authorization ]

If you’re following along with a Kibana version prior to 5.2, you’ll want to disable Monitoring as the necessary headers won’t be passed:

xpack.monitoring.enabled: false

Start Elasticsearch and Kibana

$ bin/elasticsearch
$ bin/kibana

Load Sample Data and Prepare Kibana

Our Elasticsearch instance is mostly empty at this point (there are some system indices for Monitoring and Security, but nothing really tangible to experiment with). Let’s grab some from the Kibana docs:

You can use the built-in elastic superuser account for the different API calls. I’d recommend loading all three data types (accounts, shakespeare, and logs) as it will help tie into the Security roles we’ll be defining later on.

Once the data has been ingested, log into Kibana and define your index patterns:

Create Our X-Pack Security Users and Roles

We’ll need to create a Native realm user to associate with our Google account. If my account was user1@elastic.co, I’d create a Native realm user called user1. Let’s create a user who can read the shakespeare and bank indices, but has no access to our logstash-* indices.

curl -u elastic:changeme -XPOST "http://localhost:9200/_xpack/security/role/shakespeare_bank_read" -H 'Content-Type: application/json' -d'
{
"indices": [
{
"names": ["shakespeare", "bank"],
"privileges": ["read"]
}
]
}'

curl -u elastic:changeme -XPOST "http://localhost:9200/_xpack/security/user/user1" -H 'Content-Type: application/json' -d'
{
"password" : "B&J$v,&%2SV*g9Xv",
"roles" : ["kibana_user", "shakespeare_bank_read"],
"full_name" : "My Test User 1"
}'

(note: the passwords can be randomly generated and unknown to the user; they’ll be using their Google accounts to sign in).

We must also make a “service account” for our Nginx proxy and a role that allows it to impersonate our users:

curl -u elastic:changeme -XPOST "http://localhost:9200/_xpack/security/role/nginx" -H 'Content-Type: application/json' -d'
{
"run_as": ["user1"]
}'

curl -u elastic:changeme -XPOST "http://localhost:9200/_xpack/security/user/nginx" -H 'Content-Type: application/json' -d'
{
"password" : "secretpassword",
"roles" : ["nginx"],
"full_name" : "Service Account"
}'

(note: The nginx role only grants permission to impersonate user1, it has no other privileges. This role will need to be updated via an API call to reflect new users as they’re added.)

Install, Configure, and Run oauth2_proxy

Bitly’s oauth2_proxy is an easy to use, reverse proxy that can plug into a variety of OAuth2 authentication providers (e.g. Google, Facebook, GitHub, internal, etc).

I followed these instructions to get it downloaded onto my machine, along with these to create a Google Project.

Once my project was created, I fired up oauth2_proxy with the following options:

$ ./oauth2_proxy \
--email-domain="elastic.co" \
--upstream="http://127.0.0.1:8080/" \
--approval-prompt="auto" \
--redirect-url="http://localhost:4180/oauth2/callback" \
--cookie-secret=secretsecret \
--cookie-name="_oauth2_proxy" \
--cookie-secure=false \
--provider=google \
--client-id="<your client id from your google project>" \
--client-secret="<your client secret from your google project>"

You should be able to hit http://localhost:4180 in your browser (and even login with your Google account) to confirm it’s working.

image05.png

Upon successfully authenticating, you’ll be met with a blank page since we don’t have Nginx up yet.

Install, Configure, and Run Nginx

Since I’m on a Mac, I’ll use Homebrew to install Nginx.

$ brew install nginx

Then configure Nginx to do what we want. Your server {} block can contain any number of options, but I’ll highlight the necessary parameters below:

server {
listen 8080;
server_name localhost;

location / {

# The location of our Kibana server (this is default)

proxy_pass http: //localhost:5601/;

# Send a Basic auth header to Kibana on every request to get past the log - in UI.
# "bmdpbng6c2VjcmV0cGFzc3dvcmQ="is a base64 encoded string of my service account 's credentials "nginx:secretpassword"

proxy_set_header Authorization "Basic bmdpbng6c2VjcmV0cGFzc3dvcmQ=";

# Also submit the 'es-security-runas-user'header on every request with a value of X - Forwarded - User sent from the downstream oauth2_proxy.
# X-Forwarded-User would be 'user1' if the Google account was user1 @elastic.co

proxy_set_header es-security-runas-user $http_x_forwarded_user;

# Simple rewrite to get us back to oauth2_proxy 's login page if someone uses Kibana's Logout button.

rewrite /login http: //localhost:4180/oauth2/sign_in redirect;
}
}

Finally launch Nginx (no need for sudo as we’re only listening on port 8080):

$ nginx

Try It Out

We’re ready to rock! Let’s test things out. Head over to http://localhost:4180 and sign in:

image03.png

image06.png

You may notice that the current signed in user is our “Service Account” (the full name of the nginx user we made earlier. Even though we’re “logged in” via the nginx user, every action we perform is really executed as user1 and encompasses all its authorization privileges defined in X-Pack Security. If we were to take a peek at the audit log, we can see this in action:

[2017-02-16T22:35:53,013] [transport] [run_as_granted]    origin_type=[rest], origin_address=[127.0.0.1], principal=[nginx], run_as_principal=[user1], action=[indices:data/read/search], request=[SearchRequest]
[2017-02-16T22:35:53,013] [transport] [access_granted] origin_type=[rest], origin_address=[127.0.0.1], principal=[user1], run_by_principal=[nginx], action=[indices:data/read/search], indices=[bank], request=[SearchRequest]

Our user1 can view the shakes* and ba* index patterns, but when attempting to view the logstash-* pattern we’ll see zero hits because of insufficient privileges:

image00.png

Just for fun, let’s try to access the Monitoring UI:

image07.png

Surprise! We get an error and an associated access_denied entry in the audit log:

[2017-02-16T22:42:32,291] [transport] [access_denied]    origin_type=[rest], origin_address=[127.0.0.1], principal=[user1], run_by_principal=[nginx], action=[indices:data/read/search], indices=[.monitoring-data-2], request=[SearchRequest]

Let’s go ahead and update user1 to have the monitoring_user role so we can see how our cluster’s doing.

curl -u elastic:changeme -XPOST "http://localhost:9200/_xpack/security/user/user1" -H 'Content-Type: application/json' -d'
{
"roles" : ["kibana_user", "shakespeare_bank_read", "monitoring_user"]
}'

This could also be done via the User Management UI in Kibana:

image04.png

(note: In the above screenshot, I’m logging into Kibana directly as I hadn’t linked a Google account to a Native realm user with superuser privileges).

Now if we click Retry we’ll see we have access!

image01.png

Much better :)

Closing Thoughts

Now you’ve seen a working example of a Kibana instance protected by SSO via Google Sign-In and X-Pack Security. I can seamlessly login to Kibana while logged into all my other Oauth2 protected services. However, this is merely for testing purposes and for a real production deployments, you’d also want to:


  • Harden the proxies

  • Implement TLS throughout

  • Define more restrictive users and roles where appropriate

X-Pack Security’s user impersonation feature is a powerful tool that provides a means to tie into many other authentication services that simply are not built into Security today. As long as you have some type of application layer that can handle the authentication and pass the necessary headers, you have a viable solution. User impersonation isn’t limited to just Native realm users, it also supports LDAP (and by extension, Active Directory)-- just imagine the possibilities!