As there still is no option to create a proper dashboard/panel playlist for templated dashboards (see this issue "Support for playlist with templating variables" starting 2014(!)) I searched for quite a bit and found a nice HTML/JavaScript page that could do that. But unfortunately, as browsers make someone’s life harder and harder, basic-auth credentials do not work anymore in URLs in modern browsers. So I dug in further and found this complicated but working solutions.
Prerequisites
An account in Grafana with proper permissions (in this example we use app_grafana_tv
)
Step 1 – Enable JWT-Authentication and allow embedding
Edit /etc/grafana/grafana.ini
and add for example the following lines
#################################### Auth JWT ###########################
[auth.jwt]
enabled = true
enable_login_token = true
header_name = X-JWT-Assertion
email_claim = email
username_claim = login
jwk_set_file = /etc/grafana/jwks.json
cache_ttl = 60m
expect_claims = {"iss": "https://my-server.home.arpa/"}
auto_sign_up = true
url_login = true
Further find the [security]
-section and add
# allow embedding in iframes - unfortunately needed for rotation-iframe HTML pages :|
allow_embedding = true
Step 2 – Create jwks.json and auth_token
create a new folder download the super nifty Bash-script and edit it so it looks similar like:
#!/bin/sh
## Requires openssl, nodejs, jq
## FIXES for jwt.io compliance
# use base64Url encoding
# use echo -n in pack function
header='
{
"kid": "for-grafana-tvs",
"alg": "RS256"
}'
# iat ... epoch time, after which datetime key should be active
# exp ... epoch time, when it should expire
payload='
{
"sub": "app_grafana_tv",
"login": "app_grafana_tv",
"email": "app_grafana_tv",
"isAdmin": false,
"iat": 1681992628,
"iss": "https://my-server.home.arpa/"
}'
function pack() {
# Remove line breaks and spaces
echo $1 | sed -e "s/[\r\n]\+//g" | sed -e "s/ //g"
}
function base64url_encode {
(if [ -z "$1" ]; then cat -; else echo -n "$1"; fi) |
openssl base64 -e -A |
sed s/\\+/-/g |
sed s/\\//_/g |
sed -E s/=+$//
}
# just for debugging
function base64url_decode {
INPUT=$(if [ -z "$1" ]; then echo -n $(cat -); else echo -n "$1"; fi)
MOD=$(($(echo -n "$INPUT" | wc -c) % 4))
PADDING=$(if [ $MOD -eq 2 ]; then echo -n '=='; elif [ $MOD -eq 3 ]; then echo -n '=' ; fi)
echo -n "$INPUT$PADDING" |
sed s/-/+/g |
sed s/_/\\//g |
openssl base64 -d -A
}
if [ ! -f private-key.pem ]; then
# Private and Public keys
openssl genrsa 2048 > private-key.pem
openssl rsa -in private-key.pem -pubout -out public-key.pem
fi
# Base64 Encoding
b64_header=$(pack "$header" | base64url_encode)
b64_payload=$(pack "$payload" | base64url_encode)
signature=$(echo -n $b64_header.$b64_payload | openssl dgst -sha256 -sign private-key.pem | base64url_encode)
# Export JWT
echo $b64_header.$b64_payload.$signature > jwt.txt
# Create JWK from public key
if [ ! -d ./node_modules/pem-jwk ]; then
# A tool to convert PEM to JWK
npm install pem-jwk
fi
jwk=$(./node_modules/.bin/pem-jwk public-key.pem)
# Add additional fields
jwk=$(echo '{"use":"sig"}' $jwk $header | jq -cs add)
# Export JWK
echo '{"keys":['$jwk']}'| jq . > jwks.json
echo "--- JWT ---"
cat jwt.txt
echo -e "\n--- JWK ---"
jq . jwks.json
make it executable and execute it – the output should look similar to:
writing RSA key
added 6 packages in 314ms
--- JWT ---
eyJraWQiOiJ[...]FBnnz6h94Mo3IK3X63pZrbA
--- JWK ---
{
"keys": [
{
"use": "sig",
"kty": "RSA",
"n": "znsQhes[...]JlXWY5bw",
"e": "AQAB",
"kid": "for-grafana-tvs",
"alg": "RS256"
}
]
}
Copy the JWK-json output into /etc/grafana/jwks.json
on the Grafana-server and restart the daemon.
systemctl restart grafana-server && journalctl -fu grafana-server # for checking if it's really working after grafana-server was restarted
Step 3 – Prepare the playlist HTML-page
If on the Grafana-server you use Apache as reverse proxy we need to add an additional ProxyPass
and Alias
similar to the virtualhost-config
ignore the SSL-stuff – balancer in front is handling the correct certificates
<VirtualHost 10.123.123.123:443>
ServerName my-server.home.arpa
ServerAlias grafana.example.com grafana-tv.example.com
Header set Content-Security-Policy "frame-ancestors 'self' checkmk.example.com my-server.home.arpa;"
ErrorLog /var/log/httpd/443_grafana-error.log
CustomLog /var/log/httpd/443_grafana-access.log combinedio_2022
SSLEngine on
SSLProxyEngine on
SSLProxyCheckPeerCN off
SSLProxyCheckPeerName off
SSLProtocol all -SSLv2 -SSLv3
SSLHonorCipherOrder On
SSLCipherSuite ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5
SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
RequestHeader unset Authorization
ProxyPreserveHost Off
# we exclude /rotation -- order is important!
ProxyPass /rotation !
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
# the path where we put the playlist
Alias /rotation /var/www/html/grafana/rotation
</VirtualHost>
Now get some URLs that should be shown in the playlist, e.g. from single panels.
- open a dashboard
- click on the panel and select "view"
- change variables if there are any
- save the URL shown in the browser address-bar starting from the the first
/
– e.g. fromhttps://grafana.example.com/d/6RRBSu5Vk/mydashboard?orgId=1&viewPanel=86
copy/d/6RRBSu5Vk/mydashboard?orgId=1&viewPanel=86
– note it down somewhere in the meantime or immediately copy it into the mentioned HTML-page - repeat with as many panels/dashboards as you want
Then copy/update your playlist HTML-page with the URLs – see comments in file/code below – so it looks similar to this:
// original from https://gist.github.com/czerasz/569ed1f923b610efba91375eeb35d5ab - slightly edited
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
body, html {
margin: 0; padding: 0;
height: 100%;
overflow: hidden;
background: #141414;
}
.content {
position: absolute;
left: 0; right: 0;
bottom: 0; top: 0px;
}
</style>
</head>
<body>
<iframe class="content" id="iframe1" src="" width="100%" height="100%" frameborder="0"></iframe>
<iframe class="content" id="iframe2" src="" width="100%" height="100%" frameborder="0"></iframe>
</body>
<script type="text/javascript">
const TIMEOUT = 15000; // 15 seconds
const BASE_URL = "https://my-server.home.arpa";
// replace the token accordingly with the output from the bash-script described in the howto
const AUTH_TOKEN = "&auth_token=eyJraWQiOiJ[...]FBnnz6h94Mo3IK3X63pZrbA"
const ENABLE_KIOSK = "&kiosk"
// add your panel/dashboard URLs you want to rotate through in "Kiosk"-mode
const dashboards_list = [
"/d/SoOWs5Wmk/mydashboard?orgId=1&refresh=30s&viewPanel=30&var-cluster=elk&var-name=All&var-instance=elk-node-1.home.arpa:9114&var-interval=5m",
"/d/SoOWs5Wmk/mydashboard?orgId=1&refresh=30s&var-cluster=elk&var-name=All&var-instance=elk-node-1.home.arpa:9114&var-interval=5m&viewPanel=88"
];
const iframe_1 = document.getElementById("iframe1");
const iframe_2 = document.getElementById("iframe2");
var index = 0;
// Set initial values
iframe_1.src = BASE_URL + dashboards_list[index] + AUTH_TOKEN + ENABLE_KIOSK;
iframe_1.style.zIndex = 1;
iframe_2.style.zIndex = 0;
function load_next_url() {
index = (index + 1) % dashboards_list.length;
select_bottom().src = BASE_URL + dashboards_list[index] + AUTH_TOKEN + ENABLE_KIOSK;
setTimeout(load_next_url, TIMEOUT);
// Show next slide after 3 seconds
setTimeout(change_order, 3000);
}
/**
* Return iFrame which currently hidden - iFrame which has a lower z-index.
*
* @return {Element Object} iFrame DOM element
*/
function select_bottom() {
if ( parseInt(iframe_1.style.zIndex, 10) > parseInt(iframe_2.style.zIndex, 10) ) {
return iframe_2;
} else {
return iframe_1;
}
}
/**
* Switch order of the iFrames.
* The iFrame from bottom will move on top.
*/
function change_order() {
if ( parseInt(iframe_1.style.zIndex, 10) > parseInt(iframe_2.style.zIndex, 10) ) {
iframe_1.style.zIndex = 0;
iframe_2.style.zIndex = 1;
} else {
iframe_1.style.zIndex = 1;
iframe_2.style.zIndex = 0;
}
}
setTimeout(load_next_url, TIMEOUT);
</script>
</html>
Save your changes and open the URL e.g. https://grafana-tv.example.com/rotation/rotate_test_1.html in a browser 🙂
Caution
Not sure how secure this whole setup is, so it should not be used for general public usage from the internet without any authentication ;). We use this for only from internal networks.
Kommentare