-
Notifications
You must be signed in to change notification settings - Fork 95
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for authenticated iframe (with JWT)
The image renderer from Grafana is very slow and cpu heavy. Iframe is not an option for most cases because it needs anonymous access to Grafana. This commit adds JWT support to secure the Grafana access when using iframe. When a graph is loaded in Icinga web interface, the signed JWT token is sent to Grafana in the request, if JWT is validated graph is displayed, if anything goes wrong with the token validation, Grafana will refuse the access. The library Firebase PHP-JWT is used to create the token. For now, the library is included in the vendor directory. The JWT token uses RSA keys, these keys are generated automatically in /etc when the user saves the configuration with jwt enabled.
- Loading branch information
Showing
31 changed files
with
3,555 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# JWT Configuration | ||
|
||
JWT is used to send a signed token to Grafana, so the graphs only loads if the JWT token is validated by Grafana. If the token is expired or not validated, Grafana will redirect the iframe to the login page. | ||
|
||
### Icinga configuration | ||
In the Icinga configuration: | ||
|
||
1. Change "Grafana access" to Iframe and Enable JWT | ||
|
||
2. Choose an expiration, issuer and user. | ||
- A low expiration is recommended, specially because the token is being sent in the url. | ||
- Set an issuer, for a better validation. Must be set the same on both sides. The default is empty, no issuer. | ||
- Set and existing Grafana username so the graphs open using that user. | ||
|
||
3. When you save the configuration, the RSA keys will be created at /etc/icingaweb2/modules/grafana/ (jwt.key.priv and jwt.key.pub). | ||
- For now, other directories are not supported, the filenames are hard coded in the file library/Grafana/Helpers/JwtToken.php. | ||
- If any kind of errors happens while creating the keys (e.g. permission denied), you will have to create the keys and copy them to the directory /etc/icingaweb2/modules/grafana/, use the commands below. | ||
|
||
4. The private key (jwt.key.priv), should kept safe, Grafana server only needs the public key. If you have multiple IcingaWeb servers, copy the keys to the other servers. | ||
|
||
``` | ||
openssl genrsa -out /etc/icingaweb2/modules/grafana/jwt.key.priv 2048 | ||
openssl rsa -in /etc/icingaweb2/modules/grafana/jwt.key.priv -pubout -outform PEM -out /etc/icingaweb2/modules/grafana/jwt.key.pub | ||
``` | ||
|
||
### Grafana | ||
|
||
The configuration options for Grafana JWT Auth can be found at the website: [https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/jwt/](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/jwt/). | ||
|
||
Basic grafana.ini: | ||
|
||
``` | ||
[auth.jwt] | ||
# By default, auth.jwt is disabled. | ||
enabled = true | ||
# HTTP header to look into to get a JWT token. | ||
header_name = X-JWT-Assertion | ||
# Specify a claim to use as a username to sign in. | ||
username_claim = sub | ||
# Specify a claim to use as an email to sign in. | ||
email_claim = sub | ||
# enable JWT authentication in the URL | ||
url_login = true | ||
# PEM-encoded key file in PKIX, PKCS #1, PKCS #8 or SEC 1 format. | ||
key_file = /etc/grafana/icinga.pem | ||
# This can be seen as a required "subset" of a JWT Claims Set. | ||
# expect_claims = {"iss": "https://icinga.yourdomain"} | ||
# role_attribute_path = contains(roles[*], 'admin') && 'Admin' || contains(roles[*], 'editor') && 'Editor' || 'Viewer' | ||
# To skip the assignment of roles and permissions upon login via JWT and handle them via other mechanisms like the user interface, we can skip the organization role synchronization with the following configuration. | ||
skip_org_role_sync = true | ||
``` | ||
|
||
1. Read the docs, and configure your grafana.ini | ||
|
||
2. Copy the PUBLIC key from Icinga (/etc/icingaweb2/modules/grafana/jwt.key.pub) to the path configured in "key_file". | ||
|
||
3. Enable url_login, header_name and username_claim/email_claim these options are required. | ||
|
||
4. Enable allow_embedding in the security section. | ||
|
||
5. Restart grafana |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?php | ||
|
||
namespace Icinga\Module\Grafana\Helpers; | ||
|
||
use Firebase\JWT\JWT; | ||
use Firebase\JWT\Key; | ||
|
||
class JwtToken { | ||
const RSA_KEY_BITS = 2048; | ||
const JWT_PRIVATEKEY_FILE = '/etc/icingaweb2/modules/grafana/jwt.key.priv'; | ||
const JWT_PUBLICKEY_FILE = '/etc/icingaweb2/modules/grafana/jwt.key.pub'; | ||
|
||
|
||
/** | ||
* Create JWT Token | ||
*/ | ||
public static function create(string $sub, int $exp = 0, string $iss = null, array $claims = null) : string { | ||
// Your private key file, generate your keys with: | ||
//openssl genrsa -out jwt.key.priv 2048 | ||
//openssl rsa -in jwt.key.priv -pubout -outform PEM -out jwt.key.pub | ||
$privateKeyFile = JwtToken::JWT_PRIVATEKEY_FILE; | ||
|
||
// Create a private key of type "resource" | ||
$privateKey = openssl_pkey_get_private( | ||
file_get_contents($privateKeyFile), | ||
); | ||
|
||
$payload = [ | ||
'sub' => $sub, | ||
'iat' => time(), | ||
'nbf' => time(), | ||
]; | ||
|
||
if(isset($claims)) { | ||
$payload = array_merge($payload, $claims); | ||
} | ||
|
||
if (!empty($iss)) { | ||
$payload['iss'] = $iss; | ||
} | ||
if ($exp > 0) { | ||
$payload['exp'] = $exp; | ||
} | ||
|
||
return JWT::encode($payload, $privateKey, 'RS256'); | ||
} | ||
|
||
/** | ||
* Generate Private and Public RSA Keys | ||
*/ | ||
public static function generateRsaKeys() | ||
{ | ||
if(!file_exists(JwtToken::JWT_PRIVATEKEY_FILE)) { | ||
$config = array( | ||
"private_key_bits" => JwtToken::RSA_KEY_BITS, | ||
"private_key_type" => OPENSSL_KEYTYPE_RSA, | ||
); | ||
|
||
$res = openssl_pkey_new($config); | ||
openssl_pkey_export($res, $privKey); | ||
$pubKey = openssl_pkey_get_details($res); | ||
$pubKey = $pubKey["key"]; | ||
|
||
file_put_contents(JwtToken::JWT_PRIVATEKEY_FILE, $privKey); | ||
file_put_contents(JwtToken::JWT_PUBLICKEY_FILE, $pubKey); | ||
} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
library/Grafana/ProvidedHook/Icingadb/GeneralConfigFormHook.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?php | ||
|
||
namespace Icinga\Module\Grafana\ProvidedHook\Icingadb; | ||
|
||
use Icinga\Application\Hook\ConfigFormEventsHook; | ||
use Icinga\Module\Grafana\Forms\Config\GeneralConfigForm; | ||
use Icinga\Web\Form; | ||
use Icinga\Module\Grafana\Helpers\JwtToken; | ||
|
||
class GeneralConfigFormHook extends ConfigFormEventsHook | ||
{ | ||
|
||
public function appliesTo(Form $form) | ||
{ | ||
return $form instanceof GeneralConfigForm; | ||
} | ||
|
||
public function onSuccess(Form $form) | ||
{ | ||
if($form->getElement('grafana_jwtEnable')->getValue()) { | ||
JwtToken::generateRsaKeys(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,12 @@ | ||
<?php | ||
|
||
use Icinga\Module\Grafana\ProvidedHook\Icingadb\IcingadbSupport; | ||
use Icinga\Module\Grafana\ProvidedHook\Icingadb\GeneralConfigFormHook; | ||
|
||
$this->provideHook('icingadb/HostActions'); | ||
$this->provideHook('icingadb/IcingadbSupport'); | ||
$this->provideHook('icingadb/HostDetailExtension'); | ||
$this->provideHook('icingadb/ServiceDetailExtension'); | ||
$this->provideHook('ConfigFormEvents', GeneralConfigFormHook::class); | ||
|
||
require_once __DIR__ . '/vendor/autoload.php'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?php | ||
|
||
// autoload.php @generated by Composer | ||
|
||
if (PHP_VERSION_ID < 50600) { | ||
if (!headers_sent()) { | ||
header('HTTP/1.1 500 Internal Server Error'); | ||
} | ||
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; | ||
if (!ini_get('display_errors')) { | ||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { | ||
fwrite(STDERR, $err); | ||
} elseif (!headers_sent()) { | ||
echo $err; | ||
} | ||
} | ||
trigger_error( | ||
$err, | ||
E_USER_ERROR | ||
); | ||
} | ||
|
||
require_once __DIR__ . '/composer/autoload_real.php'; | ||
|
||
return ComposerAutoloaderInit795e51b6d89d2c6da53b5c5f25b52645::getLoader(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"require": { | ||
"firebase/php-jwt": "^6.8" | ||
} | ||
} |
Oops, something went wrong.