通过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,然后删除。