grafana免登陸嵌入後臺(三種方式)

語言: CN / TW / HK

點選藍字,關注我們

以下賬號跟域名脫敏處理。curl不通正常。

在後臺嵌入grafana的監控連結。

假設我們後臺域名是houtai.yanshinian.com,

grafana是 grafana.ysn.com。

三種方式

匿名登入、api key 免登陸、模擬登入種cookie,其中匿名登入不安全,那麼使用api key免登陸但是在proxy header頭中,強制加了Authorization 頭之後,似乎又成了另一種匿名登入了。那麼模擬登入種cookie似乎顯的更安全了。

匿名登陸

網上就屬這個資料最多了。簡單來說,在grafana.ini中配置下面的引數。啟動服務。由於我用的是k8s部署,配置檔案在掛載了一塊儲存卷,重啟不會丟失,所以就直接重啟pod了。

[auth.anonymous]

# enable anonymous access

enabled = true

org_name = 你想要的匿名登入的組織名,後臺同樣需要建立一個對應的組織

api key免登入

首先要先建立一個api key

curl -X POST -H "Content-Type: application/json" -d '{"name":"test", "role": "Admin"}'  'http://admin:[email protected]/grafana/api/auth/keys'
{"id":1,"name":"test","key":"eyJrIjoiaTM4dEdrSTEwVDY0bzM2N0JYNmpidWVhd2F1ECMgciLCJuIjoidGVzdCIsImlkIjoxfQ=="}

具體的文件在官網可以看到。這裡要注意的是。建立前要先選組織防止建立到了其他組織下。然後role選擇 viewer,因為是匿名免登陸,當然不能給寫許可權。

當我們建立了一個apikey ,把這 apikey 新增到閘道器, 比如下面的示例配置。 為什麼用域名

grafana-api-key.ysn.com 

而不是 grafana.ysn.com 呢?

因為你寫死了auth頭,就相當於限定了一個使用者了。 所以需要單獨用一個域名。

server { 
listen 80;
listen 443 ssl;
server_name grafana-api-key.ysn.com;
access_log /home/nginx/logs/grafana-api-key.ysn.com_access.log main;
include /home/openresty/nginx/conf/nconf/ysnssl.conf;
location / {
set $upstream grafana.ysn.com; # 這裡是複用下upstream
proxy_pass http://$upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Connection "";
proxy_set_header Authorization "Bearer eyJrIjoiaTM4dEdrSTEwVDYVCzM2N0JYNmpidWVhd2F1MW92cEMiLCJuIjoidGVzdCIsImlkIjoxfQ=="
proxy_redirect off;
client_max_body_size 500m;
client_body_buffer_size 128k;
proxy_ignore_client_abort on;
proxy_connect_timeout 60;
proxy_send_timeout 60;
proxy_read_timeout 60;
proxy_buffer_size 128k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;
proxy_next_upstream off;
proxy_http_version 1.1;
add_header Xes-App $upstream_http_server;
}
}

重點是這行:

proxy_set_header Authorization "Bearer eyJrIjoiaTM4dEdrSTEwVDYVCzM2N0JYNmpidWVhd2F1MW92cEMiLCJuIjoidGVzdCIsImlkIjoxfQ==";

種Cookie方式

首先先找到login 介面,我不知道官方文件有沒有指明,我是這麼找的。

上面是故意寫錯了賬號,為了截圖方便,下面複製一個成功登入的curl請求(Chrome可以複製出來curl)

curl 'https://grafana.ysn.com/grafana/login' \
-H 'authority: grafana.ysn.com' \
-H 'accept: application/json, text/plain, */*' \
-H 'accept-language: zh-CN,zh;q=0.9' \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-H 'origin: https://grafana.yanshinian.com' \
-H 'pragma: no-cache' \
-H 'referer: https://grafana.ysn.comm/grafana/login' \
-H 'sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: same-origin' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36' \
--data-raw '{"user":"3333","password":"3333"}' \
--compressed

登入成功後的介面我們可以看到cookie資訊 grafana_session=1bc3f73468da3633f2c18685094810ce; Path=/grafana; Max-Age=2592000; HttpOnly; SameSite=Lax。

當然 curl 加上 -vvv 執行下也可以在命令列看到。

這裡要 注意 下,種cookie 就必須是同域名或者同屬一個根域名。所以需要把grafana的地址。再解析一個域名。於是我又弄了一個域名叫做 grafana.yanshinian.com 要跟 houtai.yanshinian.com 保證是一個根域名。

php程式碼示例(hyperf框架)

模擬登入

public function login()
{
if (empty(env('GRAFANA_HOST'))) {
return [];
}
$key = 'ysn:grafana_session_config';
$redis = ApplicationContext::getContainer()->get(Redis::class);
$grafanaSessionConfigResult = $redis->get($key);
if (!empty($grafanaSessionConfigResult)) {
return json_decode($grafanaSessionConfigResult, true);
}


$loginApi = 'http://'.env('GRAFANA_HOST').'/grafana/login';
$client = new Client([
'handler' => HandlerStack::create(new CoroutineHandler()),
'timeout' => 3,
'swoole' => [
'timeout' => 3,
'socket_buffer_size' => 1024 * 1024 * 2,
],
]);
$headers = [
'content-type' => 'application/json',
];
$user = env('GRAFANA_USER');
$password = env('GRAFANA_PASSWORD');


$response = $client->post($loginApi, [
'headers' => $headers,
'body' => json_encode([
'user' => $user,
'password' => $password,
]),
]);
if (200 === $response->getStatusCode()) {
$cookies = $response->getHeader('set-cookie');
$grafanaSessionConfig = [];
foreach ($cookies as $cookie) {
if (0 === strpos($cookie, 'grafana_session=')) {
$cookieSegments = explode(';', $cookie);
foreach ($cookieSegments as $segment) {
$segment = trim($segment);
$kv = explode('=', $segment);
if ('grafana_session' === $kv[0]) {
$grafanaSessionConfig['name'] = 'grafana_session';
$grafanaSessionConfig['value'] = $kv[1];
} elseif ('Max-Age' === $kv[0]) {
$grafanaSessionConfig['ttl'] = $kv[1];
} elseif ('Path' === $kv[0]) {
$grafanaSessionConfig['path'] = $kv[1];
}
}
break;
}
}
$redis->set($key, json_encode($grafanaSessionConfig), 10);
return $grafanaSessionConfig;
}


return [];
}

下發給瀏覽器

$cookie = new Cookie(
$grafanaCookieConfig['name'],
$grafanaCookieConfig['value'],
time() + $grafanaCookieConfig['ttl'],
$grafanaCookieConfig['path'],
'.yanshinian.com',
);
$response = Context::get(ResponseInterface::class);
$response = $response->withCookie($cookie);
Context::set(ResponseInterface::class, $response);

上面程式碼注意下cookie的作用範圍是.yanshinian.com 而不是 grafana.yanshinian.com那是因為不是同一個域名,域名範圍設定成 grafana.yanshinian.com就種不成功。

進一步提升體驗

我們發現嵌入的頁面有一個按鈕是可以點的。

當我點了它之後會蹦出來

當我摁了ESC又會出來,側邊欄。

那麼我們怎麼去掉這個按鈕呢?我們能想到的是:

修改grafana的html頁面,理論上當然可行,比如是不是可以用sed 命令匹配替換成成空字串?

使用JavaScript程式碼操作iframe中的grafana頁面,理論上也可行,但是會有跨域問題。

於是選擇了用JavaScript操作iframe的方式。既然會有跨域問題。也就是houtai.yanshinian.com,無法操作grafana.yanshinian.com 的頁面。

那麼我把grafana域名搞成houtai.yanshinian.com/grafana不就可以了?

於是嵌入的頁面的連結變成了houtai.yanshinian.com/grafana/xxxxxxxx(記得在後端下發的cookie的域範圍也要響應的變更,如果是同域名那麼domain引數值不傳即可)。

下面是刪除Cycle view mode按鈕程式碼。

var iframe = document.getElementById('iframe');
var iwindow = iframe.contentWindow;
var idoc = iwindow.document;
var btns = idoc.getElementsByClassName('toolbar-button');
for (var i = 0; i < btns.length; i++) {
var btn = btns[i];
var label = btn.getAttributeNode('aria-label');
if (label.value === 'Cycle view mode') {
btn.remove();
}
}

但是,我想在iframe載入後刪除。而且,我由於我用表單給iframe連結設定引數,就會經常載入連結,那麼使用onload事件?onload 函式中執行 btn.getAttributeNode(‘aria-label’) 是有個問題的。

瀏覽器控制檯可以打印出btns內容。但是實際上btns.length為0。這是因為頁面沒有完全渲染,所以就無法拿到dom。那麼怎麼辦?目前只有一個出路了。那就是用定時器,也只能這樣了。

document.getElementById('iframe').onload = function () {
clearCycleViewModeBtn()
var timer = null
timer = setInterval(clearCycleViewModeBtn, 1000, function () {
clearInterval(timer)
})
}

禁止ESC鍵,我在網上找了下面的這樣的程式碼。但發現,沒有生效。

document.onkeydown = killesc;


function killesc()
{
if (window.event.keyCode == 27) {
window.event.keyCode = 0;
window.event.returnValue = false;
}
}

於是只好換個思路還是利用 onkeydown 事件,只是這次變成了隱藏ESC鍵執行後展示的側邊欄以及其他元素。

function hide (event) {
if (window.event.keyCode === 27) {
var iframeDoc = document.getElementById('iframe').contentWindow.document
var navs = iframeDoc.getElementsByTagName('nav')
for (var i = 0; i < navs.length; i++) {
var nav = navs[i]
const label = nav.getAttributeNode('aria-label')
if (label.value === 'Main menu') {
nav.style.display = 'none'
}
}


var divs = iframeDoc.getElementsByClassName('css-umstnt')
for (var j = 0; j < divs.length; j++) {
const div = divs[j]
div.style.display = 'none'
}


var sections = iframeDoc.getElementsByTagName('section')
for (var k = 0; k < sections.length; k++) {
var section = sections[k]
const label = section.getAttributeNode('aria-label')
if (label.value === 'Dashboard submenu') {
section.style.display = 'none'
}
}
}
}
// onload事件中記得繫結
document.getElementById('iframe').onload = function () {
document.getElementById('iframe').contentWindow.document.onkeydown = hide
}

可能你會說,檢視元素拿到iframe的連結,不就又可以各種操作了?是的,那樣就沒辦法了。

參考資料:
《通過API Key免登入訪問Grafana》https://blog.csdn.net/qq_16240085/article/details/120996841

掃描下方二維碼新增 「好未來技術」 微信官方賬號

進入好未來技術官方交流群與作者實時互動~

(若掃碼無效,可通過微訊號 TAL-111111 直接新增)

- 也許你還想看 -

給非前端夥伴的前端知識學習引導

Python併發程式設計入門

基於雙模檢測的通話錄音質檢解決方案

我知道你“在看”喲!~