Secured Kibana dashboard with Angular and Spring Boot
The problem
There are a lot of examples of Kibana dashboard implementation on websites via iframe and Nginx, but what if you already have a secured website and you need to implement Kibana which is also secured on elastic? Furthermore, clients should not see elastic login; the iframe should fit into the website like it’s fully a part of it. I will show you how to do it with Spring Boot, Angular and Nginx.
Prerequisites
- WSL2 (if you are using Windows, for Nginx and Docker)
- Docker (if you want to run Kibana locally)
The plan
This is architecture plan that we will set up:
You can find project Git repo here
Setup
So first let’s install and run the Kibana container. Don’t forget to run elastic with the following env flag -e "discovery.type=single-node"
for local testing; otherwise, you will run into errors. Kibana depends on elastic so after starting it you will see this output with an elastic user password and enrollment token which you will need to start Kibana. Also, you can see the Kibana version here which we will need later.
After elastic, we can run the Kibana docker container as described on the linked elastic site.
Now we can wait a few seconds for the startup to complete and access Kibana on http://localhost:5601/?code=CODE_THAT_YOU_GOT
Kibana will now prompt the enrollment token that you got on the shown elastic shell and after that, you can be prompted for code which you can copy from another shell where you started Kibana from. Kibana is now started and we can add a demo dashboard by clicking on “Try sample data” from the home screen and then “Other sample data sets” and choose the one that you prefer. Under your sample, you can click to show your dashboard. When you want to share your dashboard on another website, you can click on “Share” and “Copy iFrame code”
If you paste the given code into an HTML file, you can see what our problem is. Elastic is, of course, asking for credentials.
This can be easily bypassed by disabling security or setting an API token and using it with every request through Nginx or your backend. But what if your web application should handle multiple clients with different Kibana instances or/and users? In this case, we need to log in user for each of our clients on the backend and send Cookie back to the frontend. For all this to work we need also to do reverse proxy with Nginx to make Kibana the same origin as our frontend application.
Spring Boot application
Our backend will be written in Java Spring Boot so go ahead and create a new app with Spring Initializer (or CLI if you like it more). I will name my project kibana-test-back and add only security and web dependencies + Lombok and HTTP client:
We will also set up simple Spring Security configuration with one user and restricted endpoint (basic auth):
Also, I have added a simple CORS filter to avoid CORS issues in this example project. Spring will scan and find it automatically.
We will build a classic MVC example with one endpoint for fetching the dashboard and authorized cookie, so we will not be greeted with Elastic login page and instead will see the dashboard automatically in our client app.
Our Kibana controller will look like this:
Our service will have one exposed method for fetching the dashboard for the current user:
In the method getElkCookie
, we are checking if the cookie for this user is already available to avoid another request.
Otherwise, we are calling the elkLogin method to get the elk instance cookie which we forward as a header in the getDashboard
method above.
RestTemplate is already defined as a bean and you can find it in the configuration package. In the above examples, I have been also using userElkMap
where elk instance properties are stored for each user instead of using DB.
For this example, I have exposed raw sensitive data which needs to be encrypted or provided in different ways in a production environment.
You can get kbnDashboardPath
with the above example of sharing embedded Kibana iframe. Just take that iframe src and remove the root domain. Other data are also from the start of this article. We are binding these data to user1
which we have defined in the security config.
All models defined here can be found in the code repo.
Angular application
Let’s create a simple Angular application that we will use for our Kibana dashboard. Your Angular application will most probably have secured pages and login but for the sake of simplicity, we will use just generated in-memory credentials from Spring Boot security. Also, we will not have any login setup here, but browsers will automatically ask us for credentials because of basic security on our endpoint that we will call when the client app starts. We will just use the existing app.component and our HTML will be just one iframe tag:
The safe pipe is used to sanitize potentially harmful URL-s. Without it, dynamic URL-s will not work. It is custom made and you can also find it in the code repo.
TS file will be also quite simple:
Inside OnInit we are fetching the dashboard from the previously created endpoint. The second part (iframeLoaded) is really useful if you want the iframe to fit well on the page so I added it to show how can it be done dynamically and regardless of dashboard height.
Kibana provider looks like this:
Also, I have removed the prod environment file (and also file replacement options in angular.json
) and my environment file looks like this:
As you see, all my requests (and iframe src) point to http://localhost:4200, so we will need to set a reverse proxy and get around the CORS issue. We will use Nginx installed on WSL2 on our instance. Linux users can just run it locally. This will simulate the production environment and allow us to use mentioned mutation observer on the iframe. One last thing is to run ng build
to generate static files which we will use later.
Install and configure Nginx
On WSL (Ubuntu) you can install Nginx with the following command:
sudo apt install nginx
Now we need to configure reverse proxies for both the backend server and Kibana. Inside your WSL, you can add the following configuration block to your nginx.conf
(/etc/nginx/
) or create a new conf file inside the conf.d
folder.
Full example of nginx.conf can be found in code repo.
Let’s go through the configuration:
listen
-> This is the port on which we are running the frontend app. In our case, it is the same as the Angular default port (4200).root
-> Location of static web files. In my case, this is the default project build dist folder which is created when you run ng build. Feel free to modify this.location /api
-> Here we define a reverse proxy for our backend app. In other words, we are setting IP address of our backend application location + endpoint to which we are proxying/api
request from frontend application. The IP172.25.96.1
is here because we are inside WSL and in this case, this is my Windows host address (Windows localhost) on which is also backend application running (with port 8080). You can get this address if you run this command inside WSL:grep -m 1 nameserver /etc/resolv.conf | awk '{print $2}'
. If you are on Linux, you can put localhost instead.location ~ (...)
-> This is the only practical way that I found is working for reverse proxying the Kibana dashboard. If you have noticed, Kibana has also some/api
endpoints, so I have intentionally set the same for the backend to show that it can work together. Again, inside the block, we are proxying to the Windows host IP address with Kibana port (5601)
After we save the file, we need to reload or restart Nginx with sudo service nginx reload
or sudo service Nginx restart
.
NOTE: Sometimes, Windows Firewall makes problems with connectivity, so you will need to disable Public firewall when you are testing the app.
If you try now to access http://localhost:4200 from your browser, you will need to enter previously created user credentials and should be able to see your dashboard which will also automatically change the height to fit dashboard content inside.
That’s it, we have our Kibana iframe working on our site with the option to change HTML inside it from our client app code. Also, if you open a private window and try to access this same dashboard directly, you will be greeted with elastic login instead of a Kibana dashboard. Keep in mind that the client application could have been written in any other technology (ReactJS, VueJS, etc.) and Angular serves here just as an example.