How to password protect AWS Elasticsearch’s Kibana

So you’ve set up an Elasticsearch instance on your AWS account. You got through all the various IAM policy stuff and it’s all doing what you want. Hooray! Once you want to expose your hosted Kibana dashboard to a limited public, you realize that there’s no way to restrict access aside from IP whitelists and signed requests using your AWS tokens. Where do you go from here?

Backstory

At 352 Inc., we recently deployed a product on AWS Lambda, DynamoDB, and Elasticsearch. AWS’s Elasticsearch service is sweet, the configuration is good to go out of the box and it even includes Kibana so you can get your sweet data-driven dashboard up and running.

While in development, whitelisting specific IP addresses using a policy like the one shown below worked pretty well.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "es:*",
"Resource": "arn:aws:es:REGION:ACCOUNT#:domain/ES_DOMAIN/*", // ES ARN Here
"Condition": {
"IpAddress": {
"aws:SourceIp": [
"1.2.3.4" // Your public IP address here
]
}
}
}
]
}

Once we launched, however, we needed to be able to grant access to Kibana to our internal stakeholders. Pretty quickly, I realized we were going to have trouble doing this strictly with AWS IAM policies. What we really needed was some basic HTTP authentication or something this got me looking at various proxy solutions when I came accross AWS ElasticSearch Kibana Proxy.

AWS ElasticSearch Kibana Proxy

aws-es-kibana is a CLI utility available on npm, the basic usage can be found here. Essentially, aws-es-kibana starts a local Express server that allows the user to proxy requests to AWS Kibana. It signs each request with the user's AWS token/secret so access can now be granted per user instead of just by IP address:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::ACCOUNT#:USER", // IAM ARN for User
]
},
"Action": "es:*",
"Resource": "arn:aws:es:REGION:ACCOUNT#:domain/ES_DOMAIN/*", // ES ARN Here
}
]
}

This is fantastic but it didn’t complete the problem I was hoping to solve. aws-es-kibana is awesome for local proxying but I needed to make the proxy public while also still adding HTTP authentication. I got to work on a pull request that would accomplish the necessary goals:

  • Allow for aws-es-kibana to be configured with environment variables so it could easily be run on a cheap PaaS
  • Add HTTP authentication
  • Add app.json and "Deploy to Heroku" button for easy deployment

This all came together rapidly and I opened a pull request. After swift review, the changes have been integrated into aws-es-kibana.

Deploying to Heroku

Thanks to the snazzy “Deploy to Heroku”, I was able to add an additional convenience to the whole experience… something to save others with the same problem as me some much needed time. Here’s the process:

  1. Log in or sign up for a heroku account
  2. Head to the aws-eb-kibana repo
  3. Click the “Deploy to Heroku” button at the top of the readme:
  1. Give your App a name and update all of the environment variables:
  1. If assigned, the USER and PASSWORD variables will be required in order to access the proxy.
  2. Deploy! (Optional: Refer to the next section regarding IAM policies for the AWS Access Key.)

AWS IAM Policies for Kibana

It’s always a good idea to lock down IAM users and especially in instances where you’ll be storing the access credentials outside of your own workstation. In this case, the access credentials are being stored on Heroku which means that they could potentially be compromised if anyone discovers an exploit within aws-es-kibana, Express, or any other part of the stack. As such, I recommend the following procedure.

  1. Create a new IAM user just for the proxy
  2. Copy the newly created user’s Access Key ID and Secret Key over to the Heroku app’s environment variables
  3. Remove any existing policies and add the following policy to give the user access to nothing but your Elasticsearch instance
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Action": [
"es:ESHttpGet",
"es:ESHttpPut",
"es:ESHttpPost",
"es:ESHttpHead"
],
"Resource": "arn:aws:es:REGION:ACCOUNT#:domain/ES_DOMAIN/*", // ES ARN Here
}
]
}

Important: The access policy associated with the actual Elasticsearch instance will take precedence over the user’s policy above so you’ll also need to update it accordingly, taking care to both ensure that the user created above has access to it but also whatever services need to write to it.

Conclusion

This solution provides an easy way to get off the ground with public access restricted via HTTP Authentication. Give it a try and let me know how it works out for you in the comments below.

Thanks for reading!