通過API Key鑒權方式生成免登錄的可分享的大盤鏈接。
背景信息
原生的Grafana若需要直接訪問大盤,要么使用Snapshot功能,要么開啟匿名模式。前者對訪問的數據做了鏡像,隨著時間變化無法查看后續更新的數據,后者若不配合IP白名單功能,則安全性較差。
可觀測可視化 Grafana 版支持通過API Key鑒權方式生成免登錄的可分享的大盤鏈接。
您可以將其分享給其他用戶。
使用這個鏈接的用戶將能夠訪問指定的大盤而無需登錄,因為鑒權是通過API Key完成的。
步驟一:配置Grafana參數
登錄可觀測可視化 Grafana 版控制臺,在左側導航欄單擊工作區管理。
在工作區管理頁面,單擊目標工作區ID。
在左側導航欄單擊參數設置。
在左側參數列表選擇aliyun,然后單擊修改參數。
修改api_key_share參數的運行參數為true,然后單擊保存并生效。
可選:若期望鏈接用于iFrame內嵌,則需要額外調整以下參數。
跨域情況下
域名需為HTTPS,并修改以下3個security參數。
allow_embedding=true cookie_samesite=none cookie_secure=true
未跨域情況下
修改security下的allow_embedding參數為true,開啟iFrame內嵌即可。
步驟二:創建API Key
Grafana 9.0.x和Grafana 10.0.x版本創建API Key的步驟有所不同,請確認您的Grafana版本并選擇對應的操作步驟。
如果您的Grafana是從9.0.x升級到10.0.x版本,那么在Administration > api keys頁面依然可以看到Grafana 9.0.x中創建的API Key。此時,單擊migrate to service account可以將原有的API Key遷移到Service Account,遷移完成后api keys頁面將會被永久隱藏。此外,版本升級不會影響原有API Key的使用。
Grafana 9.0.x版本
登錄可觀測可視化 Grafana 版控制臺,在左側導航欄單擊工作區管理。
在工作區管理頁面,單擊目標工作區右側的訪問地址URL鏈接進入Grafana。
說明如果需要登錄Grafana,可以使用Grafana的Admin賬號和創建工作區時設置的密碼登錄Grafana,或單擊Sign in with Alibaba Cloud直接使用當前購買工作區的阿里云賬號登錄Grafana。
單擊Grafana首頁左上角的圖標。
在Grafana左側導航欄選擇
。說明進入該菜單需要Admin權限。
單擊New API key或Add API key,然后設置以下參數。
參數
說明
Key name
API Key的名稱,不可以和已有的名稱重復。
Role
設置為Viewer。
Time to live
設置有效時間,例如60s(60秒)、10m(10分鐘)、1d(1天)。
單擊Add,在彈出的對話框中獲取并保存API Key的值。
重要對話框關閉后將無法再次查看API Key的值。
Grafana 10.0.x版本
登錄可觀測可視化 Grafana 版控制臺,在左側導航欄單擊工作區管理。
在工作區管理頁面,單擊目標工作區右側的訪問地址URL鏈接進入Grafana。
說明如果需要登錄Grafana,可以使用Grafana的Admin賬號和創建工作區時設置的密碼登錄Grafana,或單擊Sign in with Alibaba Cloud直接使用當前購買工作區的阿里云賬號登錄Grafana。
單擊Grafana首頁左上角的圖標。
在Grafana左側導航欄選擇
。重要進入該菜單需要Admin權限。
服務賬號會占用一個用戶賬號。
單擊Add service account,然后設置以下參數,然后單擊Create。
參數
說明
Display name
Service Account的名稱,不可以和已有的名稱重復。
Role
設置為Viewer。
單擊頁面右側的Add service account token,然后設置以下參數。
參數
說明
Display name
API Key的名稱,不可以和已有的名稱重復。
Expiration
設置有效時間,
No Expiration:無終止日期
Set Expiration date:設置有效期
Expiration date
如果在Expiration中選擇Set Expiration date,則您需要設置有效期的截止日。
單擊Generate token,然后在彈出的對話框單擊Copy to clipboard and close。
對話框關閉后將無法再次查看API Key的值。
步驟三:生成分享鏈接
Grafana 9.0.x版本
在Grafana頁面,進入需要分享的大盤頁面。
單擊圖標,在Link頁簽獲取大盤分享鏈接。
在鏈接最后添加
&aliyun_api_key=<API Key值>
,API Key值為上文步驟二中獲取的API Key。https://grafana-example.grafana.aliyuncs.com/d/TZWea****/test?orgId=1&from=167081684****&to=167083844****&aliyun_api_key=eyJr****WkIwNnN2c0RTSD******
使用帶有API Key的分享鏈接即可免登錄訪問Grafana大盤。
Grafana 10.0.x版本
在Grafana頁面,進入需要分享的大盤頁面。
單擊圖標,在鏈接頁簽獲取大盤分享鏈接。
在鏈接最后添加
&aliyun_api_key=<API Key值>
,API Key值為上文步驟二中獲取的API Key。https://grafana-example.grafana.aliyuncs.com/d/TZWea****/test?orgId=1&from=167081684****&to=167083844****&aliyun_api_key=eyJrIjoiWkIwNnN2c0RTSD******
使用帶有API Key的分享鏈接即可免登錄訪問Grafana大盤。
步驟四:生成高安全性分享鏈接(可選)
步驟三:生成分享鏈接中生成的分享鏈接需要定期更換API Key,以避免API Key泄露造成的數據安全風險。本步驟分別介紹在9.0.x版本API Key和10.0.x版本Service Account Token場景下生成高安全性分享鏈接的方法。10.0.x版本后API Key稱之為Service Account Token。
在進行本步驟前,您需要先修改api_key_share_version的運行參數為v2。
登錄可觀測可視化 Grafana 版控制臺,在左側導航欄單擊工作區管理。
在工作區管理頁面,單擊目標工作區ID,然后在左側導航欄單擊參數設置。
修改api_key_share_version參數的運行參數為v2,然后單擊保存并生效。
9.0.x版本API Key
將步驟二:創建API Key中獲取的API Key進行Base64解密。
Base64是網絡上常見的用于傳輸8Bit字節碼的編碼方式之一,Base64是一種基于64個可打印字符來表示二進制數據的方法。
搜索常用工具網站進行解密,例如base64。
Java語言解密。
package main import java.util.Base64; public class Base64Example{ public static void main(String[] args) { String apiKey = "eyJr****REpzZGYzd2JIa0N3ekgyWjlWWmhrSTM5bWdGT2hGSmwiLCJuIjoidGVzdDEiLCJpZCI6MX0="; String decodeKey = new String(Base64.getDecoder().decode(apiKey)); System.out.println(decodeKey); } }
運行結果:
{"k":"DJsd****HkCwzH2Z9VZhkI39mgFOhFJl","n":"test1","id":1}
Go語言解密。
package main import "fmt" import "encoding/base64" func main() { apiKey := "eyJr****REpzZGYzd2JIa0N3ekgyWjlWWmhrSTM5bWdGT2hGSmwiLCJuIjoidGVzdDEiLCJpZCI6MX0=" decodeKey, err := base64.StdEncoding.DecodeString(apiKey) if err != nil { fmt.Println(err.Error()) return } fmt.Println(string(decodeKey)) }
運行結果:
{"k":"DJsd****HkCwzH2Z9VZhkI39mgFOhFJl","n":"test1","id":1}
將上述解密的API Key值中的k進行PBKDF2加密。
PBKDF2(Password-Based Key Derivation Function 2)是一種基于密碼的密鑰導出函數,用于從用戶提供的密碼和一些其他參數(如鹽值和迭代次數)安全地派生出加密密鑰。它主要應用于需要存儲用戶密碼的場景,旨在確保即使數據庫被泄露,攻擊者也難以直接獲取到用戶的明文密碼或輕易破解派生出的密鑰。
加密有多種方式,以下是加密參數說明。
參數
說明
鹽salt
設置為API Key的名稱Name(即上面解密出來的n值),本示例中為test1。
迭代次數iteration
設置為:10000
輸出字節長度output len
設置為:50
加密密鑰長度key size
設置為:256
輸出類型
設置為:Hex
搜索常用網站工具加密,例如df2。
Java語言加密。
package main import javax.crypto.SecretKeyFactory; import java.security.GeneralSecurityException; import javax.crypto.spec.PBEKeySpec; import java.security.spec.KeySpec; public class PBKDFExapmle { public static void main(String[] args) { String password = "DJsd****HkCwzH2Z9VZhkI39mgFOhFJl"; String salt = "test1"; int iterationCount = 10000; int outputLength = 50 * 8; try { KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), iterationCount, outputLength); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] keyBytes = skf.generateSecret(spec).getEncoded(); System.out.println(bytesToHex(keyBytes)); } catch (GeneralSecurityException e) { e.printStackTrace(); } } private static String bytesToHex(byte[] bytes) { StringBuilder hexString = new StringBuilder(); for (byte b : bytes) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } return hexString.toString(); } }
輸出:PBKDF2 Password
1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****
Go語言加密。
package main import "fmt" import "encoding/hex" import "crypto/sha256" import "golang.org/x/crypto/pbkdf2" func main() { password:="DJsd****HkCwzH2Z9VZhkI39mgFOhFJl" salt="test1" newPasswd := pbkdf2.Key([]byte(password), []byte(salt), 10000, 50, sha256.New) encodePassword:= hex.EncodeToString(newPasswd) fmt.Println(encodePassword) }
輸出:PBKDF2 Password
1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****
基于PBKDF2加密后的信息構造MD5摘要簽名參數。
MD5信息摘要算法(MD5 Message-Digest Algorithm),是一種被廣泛使用的密碼散列函數,可以產出一個128位(16字節)的散列值(hash value),用于確保信息傳輸完整一致。
加密內容為:<PBKDF2 Password>+"_"+<當前系統時間整數,單位:秒>
例如:
PBKDF2 Password為:1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****
當前系統時間為:2024-09-20 17:12:13 ,則精確到秒的時間整數為:1726823533
所以需要MD5簽名的整體信息為:1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****_1726823533
MD5簽名有多種方式:
搜索常用網站工具加密,例如MD5。
Java代碼進行MD5簽名。
package main; import java.security.MessageDigest; public class MD5 { public static void main(String[] args) { String pbkdfPassword = "1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****"; long timeSeconds=System.currentTimeMillis()/1000; String key=pbkdfPassword+"_"+timeSeconds; System.out.println(MD5.getMD5String(key,"UTF-8")); } public static String getMD5String(String str, String charset) { try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); messageDigest.reset(); messageDigest.update(str.getBytes(charset)); byte[] byteArray = messageDigest.digest(); StringBuffer md5StrBuff = new StringBuffer(); for (int i = 0; i < byteArray.length; i++) { if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) { md5StrBuff.append("0").append( Integer.toHexString(0xFF & byteArray[i])); } else { md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i])); } } return md5StrBuff.toString().toLowerCase(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("MD5 error:"+e.getMessage()); } } }
Go代碼進行MD5簽名。
package main import ( "crypto/md5" "encoding/hex" "fmt" "io" "time" "strconv" ) func main() { // 需要計算MD5的字符串 pbkdfPassword := "1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****" timeSeconds:= time.Now().Unix() key:=pbkdfPassword+"_"+strconv.FormatInt(timeSeconds, 10) // 使用MD5包計算字符串的MD5值 hash := md5.New() io.WriteString(hash, key) md5Str := hash.Sum(nil) // 將二進制的MD5值轉換成十六進制字符串 md5StrHex := hex.EncodeToString(md5Str) fmt.Println("MD5 of", key, "is", md5StrHex) }
基于生成的MD5簽名信息拼接鏈接參數。
參數說明:
參數名
說明
示例
aliyun_api_key_sign
MD5簽名,隨時間變化。
例如:c3bf89b867cc88df72d507edc4d1****
aliyun_api_key_timestamp
簽名時間,當簽名時間和系統時間超過1分鐘后,簽名過期失效。
例如:1726823533
aliyun_api_key_name
API Key的名稱。
例如:test1
aliyun_api_key_org_id
API Key所在的組織Org ID。
例如:1
aliyun_api_key_expire_seconds
MD5簽名登錄后失效時間,單位:秒。
默認:3600
示例如下:
https://grafana-example.grafana.aliyuncs.com/d/TZWea****/test?orgId=1&from=167081684****&to=167083844****&aliyun_api_key_sign=c3bf89b867cc88df72d507edc4d1****&aliyun_api_key_timestamp=1726823533&aliyun_api_key_name=test1&aliyun_api_key_org_id=1
至此您可以基于程序代碼每次動態生成更安全的免密登錄Grafana的鏈接,同時可以避免API Key泄露問題。
10.0.x版本Service Account Token
將步驟二:創建API Key中的API Key(10.0.x版本后稱之為Service Account Token)切分后進行PBKDF2加密。
PBKDF2(Password-Based Key Derivation Function 2)是一種基于密碼的密鑰導出函數,用于從用戶提供的密碼和一些其他參數(如鹽值和迭代次數)安全地派生出加密密鑰。它主要應用于需要存儲用戶密碼的場景,旨在確保即使數據庫被泄露,攻擊者也難以直接獲取到用戶的明文密碼或輕易破解派生出的密鑰。
將Service Account Token按“_”進行切分。
#以下列Service Account Token為例: Token:glsa_yV9HAOVCjNKkvKoLMiypOc5T0Oov****_4f5ff3ce #切分后為: 前綴:glsa Secret:yV9HAOVCjNKkvKoLMiypOc5T0Oov**** Salt:4f5ff3ce
加密有多種方式,以下是加密參數說明。
參數
說明
鹽salt
設置為Token后綴,本示例中為4f5ff3ce。
迭代次數iteration
設置為:10000
輸出字節長度output len
設置為:50
加密密鑰長度key size
設置為:256
輸出類型
設置為:Hex
搜索常用工具網站進行解密,例如charsetpbkdf2。
Java語言解密。
package main import javax.crypto.SecretKeyFactory; import java.security.GeneralSecurityException; import javax.crypto.spec.PBEKeySpec; import java.security.spec.KeySpec; public class PBKDFExapmle { public static void main(String[] args) { String password = "yV9H****jNKkvKoLMiypOc5T0OovHXPV"; String salt = "4f5ff3ce"; int iterationCount = 10000; int outputLength = 50 * 8; try { KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), iterationCount, outputLength); SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); byte[] keyBytes = skf.generateSecret(spec).getEncoded(); System.out.println(bytesToHex(keyBytes)); } catch (GeneralSecurityException e) { e.printStackTrace(); } } private static String bytesToHex(byte[] bytes) { StringBuilder hexString = new StringBuilder(); for (byte b : bytes) { String hex = Integer.toHexString(0xff & b); if (hex.length() == 1) { hexString.append('0'); } hexString.append(hex); } return hexString.toString(); } }
輸出:PBKDF2 Password
c3cd****971bab928e4ecd6e7a00c74657696ea07d38c43f3bb5dc3190f2285cb80695cf7bf2f25c9b1f34fe1e0f9549****
Go語言加密。
package main import "fmt" import "encoding/hex" import "crypto/sha256" import "golang.org/x/crypto/pbkdf2" func main() { password:="yV9H****jNKkvKoLMiypOc5T0OovHXPV" salt="4f5ff3ce" newPasswd := pbkdf2.Key([]byte(password), []byte(salt), 10000, 50, sha256.New) encodePassword:= hex.EncodeToString(newPasswd) fmt.Println(encodePassword) }
輸出:PBKDF2 Password
c3cd****971bab928e4ecd6e7a00c74657696ea07d38c43f3bb5dc3190f2285cb80695cf7bf2f25c9b1f34fe1e0f9549****
基于PBKDF2加密后的信息構造MD5摘要簽名參數。
MD5信息摘要算法(MD5 Message-Digest Algorithm),是一種被廣泛使用的密碼散列函數,可以產出一個128位(16字節)的散列值(hash value),用于確保信息傳輸完整一致。
加密內容為:<PBKDF2 Password>+"_"+<當前系統時間整數,單位:秒>
例如:
PBKDF2 Password為:1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****
當前系統時間為2024-09-20 17:12:13 ,則精確到秒的時間整數為:1726823533
所以需要MD5簽名的整體信息為:1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****_1726823533
MD5簽名有多種方式:
搜索常用網站工具加密,例如MD5。
Java代碼進行MD5簽名。
package main; import java.security.MessageDigest; public class MD5 { public static void main(String[] args) { String pbkdfPassword = "1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****"; long timeSeconds=System.currentTimeMillis()/1000; String key=pbkdfPassword+"_"+timeSeconds; System.out.println(MD5.getMD5String(key,"UTF-8")); } public static String getMD5String(String str, String charset) { try { MessageDigest messageDigest = MessageDigest.getInstance("MD5"); messageDigest.reset(); messageDigest.update(str.getBytes(charset)); byte[] byteArray = messageDigest.digest(); StringBuffer md5StrBuff = new StringBuffer(); for (int i = 0; i < byteArray.length; i++) { if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) { md5StrBuff.append("0").append( Integer.toHexString(0xFF & byteArray[i])); } else { md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i])); } } return md5StrBuff.toString().toLowerCase(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("MD5 error:"+e.getMessage()); } } }
Go代碼進行MD5簽名。
package main import ( "crypto/md5" "encoding/hex" "fmt" "io" "time" "strconv" ) func main() { // 需要計算MD5的字符串 pbkdfPassword := "1e5b****80184e78832544aae4d2e031a3539c10b575b75d7c1d44af49fcf5a7de9c58a5f0035ce35fff0e5b0476e882****" timeSeconds:= time.Now().Unix() key:=pbkdfPassword+"_"+strconv.FormatInt(timeSeconds, 10) // 使用MD5包計算字符串的MD5值 hash := md5.New() io.WriteString(hash, key) md5Str := hash.Sum(nil) // 將二進制的MD5值轉換成十六進制字符串 md5StrHex := hex.EncodeToString(md5Str) fmt.Println("MD5 of", key, "is", md5StrHex) }
基于生成的MD5簽名信息拼接鏈接參數。
參數說明:
參數名
說明
示例
aliyun_api_key_sign
MD5簽名,隨時間變化。
例如:c3bf89b867cc88df72d507edc4d1****
aliyun_api_key_timestamp
簽名時間,當簽名時間和系統時間超過1分鐘后,簽名過期失效。
例如:1726823533
aliyun_api_key_name
Service Account Token的名稱。
例如:test1
aliyun_api_key_org_id
Service Account Token所在的組織Org ID。
例如:1
aliyun_api_key_expire_seconds
MD5簽名登錄后失效時間,單位:秒。
默認:3600
示例如下:
https://grafana-example.grafana.aliyuncs.com/d/TZWea****/test?orgId=1&from=167081684****&to=167083844****&aliyun_api_key_sign=c3bf89b867cc88df72d507edc4d1****&aliyun_api_key_timestamp=1726823533&aliyun_api_key_name=test1&aliyun_api_key_org_id=1
至此您可以基于程序代碼每次動態生成更安全的免密登錄Grafana的鏈接,同時可以避免Service Account Token泄露問題。
常見問題
通過共享連接訪問大盤時頁面報錯如下:
可能原因:內嵌大盤情況下,allow_embedding參數未設置。配置allow_embedding參數的操作,請參見上文步驟一。
無法顯示大盤數據。
內嵌大盤情況下,Cookie無法寫入導致,可能原因如下:
跨域,即根域名不同時,默認配置無法寫入Cookie。
cookie_samesite參數設置為none,但是cookie_secure參數設置為false。
域名為HTTP。由于cookie_secure參數無法在HTTP下生效,因此域名不支持HTTP。
解決方案:參考上文步驟一重新配置Grafana參數。
通過共享連接訪問大盤時頁面報錯如下:
可能原因:
瀏覽器版本過低。
內嵌大盤的情況下瀏覽器配置導致。
解決方案:
查看Cookie使用的配置,允許使用Cookie。
若使用Chrome瀏覽器,在無痕模式下需要配置允許所有Cookie。
如果用于大盤內嵌,API Key建議是設置較短的有效時間使用一次就更換,還是配置一個很長時間的免登Key?
您可以根據安全需要做配置,建議3個月換一次,若Key泄露可以通過刪除讓Key失效。
API Key是否有數量上限?
Key的創建官方源碼里并沒有做限制,由于頁面查詢時最多展示100條,建議不要超過100個。
API Key配置的有效時間到期了,這個API Key會自動刪除嗎?
Key失效后,數據仍然存在,如果擔心占用數量您可以手動刪除Key。Grafana頁面上失效的Key默認不展示,您可以單擊Include expired keys顯示已失效Key,然后刪除。