added dedicated cli module

This commit is contained in:
2025-01-09 16:58:02 +08:00
parent d5a2c4a591
commit 01d5b1462c
27 changed files with 512 additions and 107 deletions

View File

@@ -2,7 +2,7 @@ import net.woggioni.gbcs.api.CacheProvider;
import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider;
import net.woggioni.gbcs.cache.FileSystemCacheProvider;
open module net.woggioni.gbcs {
module net.woggioni.gbcs {
requires java.sql;
requires java.xml;
requires java.logging;
@@ -11,6 +11,7 @@ open module net.woggioni.gbcs {
requires io.netty.buffer;
requires io.netty.transport;
requires io.netty.codec.http;
requires io.netty.codec.http2;
requires io.netty.common;
requires io.netty.handler;
requires io.netty.codec;
@@ -19,9 +20,12 @@ open module net.woggioni.gbcs {
requires net.woggioni.gbcs.base;
requires net.woggioni.gbcs.api;
provides java.net.URLStreamHandlerFactory with ClasspathUrlStreamHandlerFactoryProvider;
uses java.net.URLStreamHandlerFactory;
uses CacheProvider;
exports net.woggioni.gbcs;
opens net.woggioni.gbcs;
opens net.woggioni.gbcs.schema;
uses java.net.URLStreamHandlerFactory;
provides java.net.URLStreamHandlerFactory with ClasspathUrlStreamHandlerFactoryProvider;
uses CacheProvider;
provides CacheProvider with FileSystemCacheProvider;
}

View File

@@ -34,6 +34,9 @@ import io.netty.handler.codec.http.HttpServerCodec
import io.netty.handler.codec.http.HttpUtil
import io.netty.handler.codec.http.HttpVersion
import io.netty.handler.codec.http.LastHttpContent
import io.netty.handler.codec.http2.Http2FrameCodecBuilder
import io.netty.handler.ssl.ApplicationProtocolNames
import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler
import io.netty.handler.ssl.ClientAuth
import io.netty.handler.ssl.SslContext
import io.netty.handler.ssl.SslContextBuilder
@@ -46,7 +49,13 @@ import net.woggioni.gbcs.api.Cache
import net.woggioni.gbcs.api.Configuration
import net.woggioni.gbcs.api.Role
import net.woggioni.gbcs.api.exception.ContentTooLargeException
import net.woggioni.gbcs.auth.AbstractNettyHttpAuthenticator
import net.woggioni.gbcs.auth.Authorizer
import net.woggioni.gbcs.auth.ClientCertificateValidator
import net.woggioni.gbcs.auth.RoleAuthorizer
import net.woggioni.gbcs.base.GBCS.toUrl
import net.woggioni.gbcs.base.PasswordSecurity.decodePasswordHash
import net.woggioni.gbcs.base.PasswordSecurity.hashPassword
import net.woggioni.gbcs.base.Xml
import net.woggioni.gbcs.base.contextLogger
import net.woggioni.gbcs.base.debug
@@ -58,6 +67,7 @@ import net.woggioni.jwo.Application
import net.woggioni.jwo.JWO
import net.woggioni.jwo.Tuple2
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.net.InetSocketAddress
import java.net.URL
import java.net.URLStreamHandlerFactory
@@ -213,6 +223,22 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
companion object {
private fun getServerAPNHandler(): ApplicationProtocolNegotiationHandler {
val serverAPNHandler: ApplicationProtocolNegotiationHandler =
object : ApplicationProtocolNegotiationHandler(ApplicationProtocolNames.HTTP_2) {
override fun configurePipeline(ctx: ChannelHandlerContext, protocol: String) {
if (ApplicationProtocolNames.HTTP_2 == protocol) {
ctx.pipeline().addLast(
Http2FrameCodecBuilder.forServer().build()
)
return
}
throw IllegalStateException("Protocol: $protocol not supported")
}
}
return serverAPNHandler
}
fun loadKeystore(file: Path, password: String?): KeyStore {
val ext = JWO.splitExtension(file)
.map(Tuple2<String, String>::get_2)
@@ -273,7 +299,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
}
if (sslContext != null) {
val sslHandler = sslContext.newHandler(ch.alloc())
pipeline.addLast(sslHandler)
if (auth is Configuration.ClientCertificateAuthentication) {
val roleAuthorizer = RoleAuthorizer()
@@ -285,6 +310,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
)
}
}
// pipeline.addLast(getServerAPNHandler())
pipeline.addLast(HttpServerCodec())
pipeline.addLast(HttpChunkContentCompressor(1024))
pipeline.addLast(ChunkedWriteHandler())
@@ -537,6 +563,17 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
resetCachedUrlHandlers()
}
fun loadConfiguration(configurationFile: Path): Configuration {
val dbf = Xml.newDocumentBuilderFactory(null)
val db = dbf.newDocumentBuilder()
val doc = Files.newInputStream(configurationFile).use(db::parse)
return Parser.parse(doc)
}
fun dumpConfiguration(conf : Configuration, outputStream: OutputStream) {
Xml.write(Serializer.serialize(conf), outputStream)
}
fun loadConfiguration(args: Array<String>): Configuration {
// Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader
val app = Application.builder("gbcs")

View File

@@ -1,4 +1,4 @@
package net.woggioni.gbcs
package net.woggioni.gbcs.auth
import io.netty.buffer.Unpooled
import io.netty.channel.ChannelFutureListener
@@ -12,18 +12,12 @@ import io.netty.handler.codec.http.HttpResponseStatus
import io.netty.handler.codec.http.HttpVersion
import io.netty.util.ReferenceCountUtil
import net.woggioni.gbcs.api.Role
import java.security.SecureRandom
import java.security.spec.KeySpec
import java.util.Base64
import javax.crypto.SecretKeyFactory
import javax.crypto.spec.PBEKeySpec
abstract class AbstractNettyHttpAuthenticator(private val authorizer : Authorizer)
: ChannelInboundHandlerAdapter() {
companion object {
private const val KEY_LENGTH = 256
private val AUTHENTICATION_FAILED: FullHttpResponse = DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED, Unpooled.EMPTY_BUFFER).apply {
headers()[HttpHeaderNames.CONTENT_LENGTH] = "0"
@@ -33,42 +27,6 @@ abstract class AbstractNettyHttpAuthenticator(private val authorizer : Authorize
HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN, Unpooled.EMPTY_BUFFER).apply {
headers()[HttpHeaderNames.CONTENT_LENGTH] = "0"
}
private fun concat(arr1: ByteArray, arr2: ByteArray): ByteArray {
val result = ByteArray(arr1.size + arr2.size)
var j = 0
for(element in arr1) {
result[j] = element
j += 1
}
for(element in arr2) {
result[j] = element
j += 1
}
return result
}
fun hashPassword(password : String, salt : String? = null) : String {
val actualSalt = salt?.let(Base64.getDecoder()::decode) ?: SecureRandom().run {
val result = ByteArray(16)
nextBytes(result)
result
}
val spec: KeySpec = PBEKeySpec(password.toCharArray(), actualSalt, 10, KEY_LENGTH)
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
val hash = factory.generateSecret(spec).encoded
return String(Base64.getEncoder().encode(concat(hash, actualSalt)))
}
fun decodePasswordHash(passwordHash : String) : Pair<ByteArray, ByteArray> {
val decoded = Base64.getDecoder().decode(passwordHash)
val hash = ByteArray(KEY_LENGTH / 8)
val salt = ByteArray(decoded.size - KEY_LENGTH / 8)
System.arraycopy(decoded, 0, hash, 0, hash.size)
System.arraycopy(decoded, hash.size, salt, 0, salt.size)
return hash to salt
}
}

View File

@@ -1,4 +1,4 @@
package net.woggioni.gbcs
package net.woggioni.gbcs.auth
import io.netty.handler.codec.http.HttpRequest
import net.woggioni.gbcs.api.Role

View File

@@ -1,4 +1,4 @@
package net.woggioni.gbcs
package net.woggioni.gbcs.auth
import java.security.KeyStore
import java.security.cert.CertPathValidator

View File

@@ -1,4 +1,4 @@
package net.woggioni.gbcs
package net.woggioni.gbcs.auth
import io.netty.handler.codec.http.HttpMethod
import io.netty.handler.codec.http.HttpRequest

View File

@@ -2,7 +2,7 @@
<gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:gbcs="urn:net.woggioni.gbcs"
xs:schemaLocation="urn:net.woggioni.gbcs classpath:net/woggioni/gbcs/schema/gbcs.xsd">
<bind host="127.0.0.1" port="11443"/>
<bind host="127.0.0.1" port="8080"/>
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
<authentication>
<none/>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>
<configuration>
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
<import class="ch.qos.logback.core.ConsoleAppender"/>
<appender name="console" class="ConsoleAppender">
<target>System.out</target>
<encoder class="PatternLayoutEncoder">
<pattern>%d [%highlight(%-5level)] \(%thread\) %logger{36} -%kvp- %msg %n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="console"/>
</root>
<logger name="io.netty" level="debug"/>
<logger name="com.google.code.yanf4j" level="warn"/>
<logger name="net.rubyeye.xmemcached" level="warn"/>
</configuration>

View File

@@ -1,7 +1,7 @@
package net.woggioni.gbcs.test
import io.netty.handler.codec.http.HttpResponseStatus
import net.woggioni.gbcs.AbstractNettyHttpAuthenticator.Companion.hashPassword
import net.woggioni.gbcs.auth.AbstractNettyHttpAuthenticator.Companion.hashPassword
import net.woggioni.gbcs.api.Role
import net.woggioni.gbcs.base.Xml
import net.woggioni.gbcs.api.Configuration