-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for authenticated iframe (with JWT) #326
Open
epinter
wants to merge
1
commit into
Mikesch-mp:master
Choose a base branch
from
epinter:feature-iframe-jwtauth
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,64 @@ | ||
<?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 { | ||
$privateKeyFile = JwtToken::JWT_PRIVATEKEY_FILE; | ||
|
||
$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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thorws an error in my setup, better:
if (isset($formData['grafana_jwtEnable']) && $formData['grafana_jwtEnable']) {
?
except this it works perfectly!