feat(metrics): 运行指标服务端支持设置 HTTP 认证.

支持对运行指标服务端设置 HTTP 认证, 以防止运行指标被非法获取.
This commit is contained in:
2022-06-18 09:20:46 +08:00
parent c5fe96c02d
commit 215a4670db
4 changed files with 187 additions and 7 deletions

View File

@ -5,10 +5,7 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import mu.KotlinLogging
import net.lamgc.scalabot.util.ArtifactSerializer
import net.lamgc.scalabot.util.AuthenticationSerializer
import net.lamgc.scalabot.util.MavenRepositoryConfigSerializer
import net.lamgc.scalabot.util.ProxyTypeSerializer
import net.lamgc.scalabot.util.*
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.repository.Authentication
import org.eclipse.aether.repository.Proxy
@ -92,7 +89,8 @@ internal data class ProxyConfig(
internal data class MetricsConfig(
val enable: Boolean = false,
val port: Int = 9386,
val bindAddress: String? = "0.0.0.0"
val bindAddress: String? = "0.0.0.0",
val authenticator: UsernameAuthenticator? = null
)
/**
@ -322,6 +320,7 @@ private object GsonConst {
.registerTypeAdapter(DefaultBotOptions.ProxyType::class.java, ProxyTypeSerializer)
.registerTypeAdapter(MavenRepositoryConfig::class.java, MavenRepositoryConfigSerializer)
.registerTypeAdapter(Authentication::class.java, AuthenticationSerializer)
.registerTypeAdapter(UsernameAuthenticator::class.java, UsernameAuthenticatorSerializer)
.create()
val botConfigGson: Gson = baseGson.newBuilder()

View File

@ -39,14 +39,15 @@ fun main(args: Array<String>): Unit = runBlocking {
* 启动运行指标服务器.
* 使用 Prometheus 指标格式.
*/
internal fun startMetricsServer(config: MetricsConfig = Const.config.metrics) {
internal fun startMetricsServer(config: MetricsConfig = Const.config.metrics): HTTPServer? {
if (!config.enable) {
log.debug { "运行指标服务器已禁用." }
return
return null
}
val builder = HTTPServer.Builder()
.withDaemonThreads(true)
.withAuthenticator(config.authenticator)
.withPort(config.port)
.withHostname(config.bindAddress)
@ -54,6 +55,7 @@ internal fun startMetricsServer(config: MetricsConfig = Const.config.metrics) {
.build()
.registerShutdownHook()
log.info { "运行指标服务器已启动. (Port: ${httpServer.port})" }
return httpServer
}
internal class Launcher(private val config: AppConfig = Const.config) : AutoCloseable {

View File

@ -0,0 +1,65 @@
package net.lamgc.scalabot.util
import com.google.gson.*
import com.sun.net.httpserver.BasicAuthenticator
import java.lang.reflect.Type
class UsernameAuthenticator(private val username: String, private val password: String) :
BasicAuthenticator("metrics") {
override fun checkCredentials(username: String?, password: String?): Boolean =
this.username == username && this.password == password
fun toJsonObject(): JsonObject = JsonObject().apply {
addProperty("username", username)
addProperty("password", password)
}
override fun equals(other: Any?): Boolean {
return other is UsernameAuthenticator && this.username == other.username && this.password == other.password
}
override fun hashCode(): Int {
var result = username.hashCode()
result = 31 * result + password.hashCode()
return result
}
}
object UsernameAuthenticatorSerializer : JsonSerializer<UsernameAuthenticator>,
JsonDeserializer<UsernameAuthenticator> {
override fun serialize(
src: UsernameAuthenticator,
typeOfSrc: Type?,
context: JsonSerializationContext?
): JsonElement {
return src.toJsonObject()
}
override fun deserialize(
json: JsonElement,
typeOfT: Type?,
context: JsonDeserializationContext?
): UsernameAuthenticator? {
if (json.isJsonNull) {
return null
} else if (!json.isJsonObject) {
throw JsonParseException("Invalid attribute value type.")
}
val jsonObj = json.asJsonObject
if (jsonObj["username"]?.isJsonPrimitive != true) {
throw JsonParseException("Invalid attribute value: username")
} else if (jsonObj["password"]?.isJsonPrimitive != true) {
throw JsonParseException("Invalid attribute value: password")
}
if (jsonObj["username"].asString.isEmpty() || jsonObj["password"].asString.isEmpty()) {
throw JsonParseException("`username` or `password` is empty.")
}
return UsernameAuthenticator(jsonObj["username"].asString, jsonObj["password"].asString)
}
}