temporary commit
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider;
|
||||
|
||||
module net.woggioni.gbcs {
|
||||
requires java.sql;
|
||||
requires java.xml;
|
||||
requires java.logging;
|
||||
requires kotlin.stdlib;
|
||||
@@ -14,6 +15,8 @@ module net.woggioni.gbcs {
|
||||
requires net.woggioni.jwo;
|
||||
|
||||
exports net.woggioni.gbcs;
|
||||
opens net.woggioni.gbcs to net.woggioni.envelope;
|
||||
exports net.woggioni.gbcs.url;
|
||||
// opens net.woggioni.gbcs to net.woggioni.envelope;
|
||||
provides java.net.URLStreamHandlerFactory with ClasspathUrlStreamHandlerFactoryProvider;
|
||||
uses java.net.URLStreamHandlerFactory;
|
||||
}
|
88
src/main/java/net/woggioni/gbcs/NettyPingServer.java
Normal file
88
src/main/java/net/woggioni/gbcs/NettyPingServer.java
Normal file
@@ -0,0 +1,88 @@
|
||||
package net.woggioni.gbcs;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.codec.string.StringEncoder;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
public class NettyPingServer {
|
||||
private final int port;
|
||||
|
||||
public NettyPingServer(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public void start() throws Exception {
|
||||
// Create event loop groups for handling incoming connections and processing
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup();
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
|
||||
try {
|
||||
// Create server bootstrap configuration
|
||||
ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast(
|
||||
new StringDecoder(CharsetUtil.UTF_8),
|
||||
new StringEncoder(CharsetUtil.UTF_8),
|
||||
new PingServerHandler()
|
||||
);
|
||||
}
|
||||
})
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
|
||||
// Bind and start the server
|
||||
ChannelFuture future = bootstrap.bind(port).sync();
|
||||
System.out.println("Ping Server started on port: " + port);
|
||||
try(final var handle = new GradleBuildCacheServer.ServerHandle(future, bossGroup, workerGroup)) {
|
||||
Thread.sleep(5000);
|
||||
future.channel().close();
|
||||
// Wait until the server socket is closed
|
||||
future.channel().closeFuture().sync();
|
||||
}
|
||||
} finally {
|
||||
// Shutdown event loop groups
|
||||
// workerGroup.shutdownGracefully();
|
||||
// bossGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
// Custom handler for processing ping requests
|
||||
private static class PingServerHandler extends SimpleChannelInboundHandler<String> {
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
// Check if the received message is a ping request
|
||||
if ("ping".equalsIgnoreCase(msg.trim())) {
|
||||
// Respond with "pong"
|
||||
ctx.writeAndFlush("pong\n");
|
||||
System.out.println("Received ping, sent pong");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
// Log and close the connection in case of any errors
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Main method to start the server
|
||||
public static void main(String[] args) throws Exception {
|
||||
int port = 8080; // Default port
|
||||
if (args.length > 0) {
|
||||
port = Integer.parseInt(args[0]);
|
||||
}
|
||||
|
||||
new NettyPingServer(port).start();
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,41 @@
|
||||
package net.woggioni.gbcs.url;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
|
||||
public class ClasspathUrlStreamHandlerFactoryProvider implements URLStreamHandlerFactory {
|
||||
|
||||
private static class Handler extends URLStreamHandler {
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public Handler() {
|
||||
this.classLoader = getClass().getClassLoader();
|
||||
}
|
||||
|
||||
public Handler(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) throws IOException {
|
||||
final URL resourceUrl = classLoader.getResource(u.getPath());
|
||||
return resourceUrl.openConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URLStreamHandler createURLStreamHandler(String protocol) {
|
||||
URLStreamHandler result;
|
||||
switch (protocol) {
|
||||
case "classpath":
|
||||
result = new Handler();
|
||||
break;
|
||||
default:
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -16,7 +16,9 @@ import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
|
||||
class ClientCertificateValidator private constructor(private val sslHandler : SslHandler, private val x509TrustManager: X509TrustManager) : ChannelInboundHandlerAdapter() {
|
||||
class ClientCertificateValidator private constructor(
|
||||
private val sslHandler : SslHandler,
|
||||
private val x509TrustManager: X509TrustManager) : ChannelInboundHandlerAdapter() {
|
||||
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
||||
if (evt is SslHandshakeCompletionEvent) {
|
||||
if (evt.isSuccess) {
|
||||
@@ -32,20 +34,17 @@ class ClientCertificateValidator private constructor(private val sslHandler : Ss
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun of(sslHandler : SslHandler, trustStore : KeyStore?) : ClientCertificateValidator {
|
||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
|
||||
val validator = CertPathValidator.getInstance("PKIX").apply {
|
||||
val rc = revocationChecker as PKIXRevocationChecker
|
||||
rc.options = EnumSet.of(
|
||||
PKIXRevocationChecker.Option.NO_FALLBACK,
|
||||
PKIXRevocationChecker.Option.SOFT_FAIL,
|
||||
PKIXRevocationChecker.Option.PREFER_CRLS)
|
||||
}
|
||||
|
||||
val manager = if(trustStore != null) {
|
||||
val params = PKIXParameters(trustStore)
|
||||
fun getTrustManager(trustStore : KeyStore?, certificateRevocationEnabled : Boolean) : X509TrustManager {
|
||||
return if(trustStore != null) {
|
||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
val validator = CertPathValidator.getInstance("PKIX").apply {
|
||||
val rc = revocationChecker as PKIXRevocationChecker
|
||||
rc.options = EnumSet.of(
|
||||
PKIXRevocationChecker.Option.NO_FALLBACK)
|
||||
}
|
||||
val params = PKIXParameters(trustStore).apply {
|
||||
isRevocationEnabled = certificateRevocationEnabled
|
||||
}
|
||||
object : X509TrustManager {
|
||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
|
||||
val clientCertificateChain = certificateFactory.generateCertPath(chain.toList())
|
||||
@@ -56,15 +55,23 @@ class ClientCertificateValidator private constructor(private val sslHandler : Ss
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
private val acceptedIssuers = trustStore.aliases().asSequence()
|
||||
.filter (trustStore::isCertificateEntry)
|
||||
.map(trustStore::getCertificate)
|
||||
.map { it as X509Certificate }
|
||||
.toList()
|
||||
.toTypedArray()
|
||||
|
||||
override fun getAcceptedIssuers() = acceptedIssuers
|
||||
}
|
||||
} else {
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
trustManagerFactory.trustManagers.asSequence().filter { it is X509TrustManager }.single() as X509TrustManager
|
||||
}
|
||||
return ClientCertificateValidator(sslHandler, manager)
|
||||
}
|
||||
|
||||
fun of(sslHandler : SslHandler, trustStore : KeyStore?, certificateRevocationEnabled : Boolean) : ClientCertificateValidator {
|
||||
return ClientCertificateValidator(sslHandler, getTrustManager(trustStore, certificateRevocationEnabled))
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,6 +21,7 @@ data class KeyStore(
|
||||
data class TrustStore(
|
||||
val file : Path,
|
||||
val password : String?,
|
||||
val checkCertificateStatus : Boolean
|
||||
)
|
||||
|
||||
data class Configuration(
|
||||
@@ -28,18 +29,26 @@ data class Configuration(
|
||||
val host : String,
|
||||
val port : Int,
|
||||
val users : Map<String, Set<Role>>,
|
||||
val groups : Map<String, Set<Role>>,
|
||||
val tlsConfiguration: TlsConfiguration?,
|
||||
val serverPath : String
|
||||
val serverPath : String,
|
||||
val useVirtualThread : Boolean
|
||||
) {
|
||||
companion object {
|
||||
fun parse(document : Element) : Configuration {
|
||||
|
||||
var cacheFolder = Paths.get(System.getProperty("user.home")).resolve(".gbcs")
|
||||
var host : String = "127.0.0.1"
|
||||
var port : Int = 11080
|
||||
var users = emptyMap<String, Set<Role>>()
|
||||
var host = "127.0.0.1"
|
||||
var port = 11080
|
||||
val users = emptyMap<String, Set<Role>>()
|
||||
val groups = emptyMap<String, Set<Role>>()
|
||||
var tlsConfiguration : TlsConfiguration? = null
|
||||
var serverPath = "/"
|
||||
val serverPath = document.getAttribute("path")
|
||||
.takeIf(String::isNotEmpty)
|
||||
?: "/"
|
||||
val useVirtualThread = document.getAttribute("useVirtualThreads")
|
||||
.takeIf(String::isNotEmpty)
|
||||
?.let(String::toBoolean) ?: false
|
||||
|
||||
for(child in document.asIterable()) {
|
||||
when(child.nodeName) {
|
||||
@@ -62,8 +71,8 @@ data class Configuration(
|
||||
val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
||||
val trustStorePassword = granChild.getAttribute("password")
|
||||
.takeIf(String::isNotEmpty)
|
||||
val keyAlias = granChild.getAttribute("server-key-alias")
|
||||
val keyPasswordPassword = granChild.getAttribute("server-key-password")
|
||||
val keyAlias = granChild.getAttribute("key-alias")
|
||||
val keyPasswordPassword = granChild.getAttribute("password")
|
||||
.takeIf(String::isNotEmpty)
|
||||
keyStore = KeyStore(
|
||||
trustStoreFile,
|
||||
@@ -76,9 +85,14 @@ data class Configuration(
|
||||
val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
||||
val trustStorePassword = granChild.getAttribute("password")
|
||||
.takeIf(String::isNotEmpty)
|
||||
val checkCertificateStatus = granChild.getAttribute("check-certificate-status")
|
||||
.takeIf(String::isNotEmpty)
|
||||
?.let(String::toBoolean)
|
||||
?: false
|
||||
trustStore = TrustStore(
|
||||
trustStoreFile,
|
||||
trustStorePassword
|
||||
trustStorePassword,
|
||||
checkCertificateStatus
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -89,7 +103,7 @@ data class Configuration(
|
||||
|
||||
}
|
||||
|
||||
return Configuration(cacheFolder, host, port, users, tlsConfiguration, serverPath)
|
||||
return Configuration(cacheFolder, host, port, users, groups, tlsConfiguration, serverPath, useVirtualThread)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,13 +13,15 @@ import java.security.MessageDigest
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.AbstractMap.SimpleEntry
|
||||
import java.util.Arrays
|
||||
import java.util.Base64
|
||||
import java.util.ServiceLoader
|
||||
import java.util.concurrent.Executors
|
||||
import io.netty.bootstrap.ServerBootstrap
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.Unpooled
|
||||
import io.netty.channel.Channel
|
||||
import io.netty.channel.ChannelDuplexHandler
|
||||
import io.netty.channel.ChannelFuture
|
||||
import io.netty.channel.ChannelFutureListener
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.channel.ChannelInitializer
|
||||
@@ -30,11 +32,13 @@ import io.netty.channel.EventLoopGroup
|
||||
import io.netty.channel.SimpleChannelInboundHandler
|
||||
import io.netty.channel.nio.NioEventLoopGroup
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel
|
||||
import io.netty.handler.codec.DecoderException
|
||||
import io.netty.handler.codec.compression.CompressionOptions
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
||||
import io.netty.handler.codec.http.DefaultHttpContent
|
||||
import io.netty.handler.codec.http.DefaultHttpResponse
|
||||
import io.netty.handler.codec.http.FullHttpRequest
|
||||
import io.netty.handler.codec.http.FullHttpResponse
|
||||
import io.netty.handler.codec.http.HttpContentCompressor
|
||||
import io.netty.handler.codec.http.HttpHeaderNames
|
||||
import io.netty.handler.codec.http.HttpHeaderValues
|
||||
@@ -44,16 +48,23 @@ import io.netty.handler.codec.http.HttpRequest
|
||||
import io.netty.handler.codec.http.HttpResponseStatus
|
||||
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.ssl.ClientAuth
|
||||
import io.netty.handler.ssl.SslContext
|
||||
import io.netty.handler.ssl.SslContextBuilder
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate
|
||||
import io.netty.handler.stream.ChunkedNioFile
|
||||
import io.netty.handler.stream.ChunkedWriteHandler
|
||||
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
||||
import io.netty.util.concurrent.DefaultThreadFactory
|
||||
import io.netty.util.concurrent.EventExecutorGroup
|
||||
import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider
|
||||
import javax.net.ssl.SSLPeerUnverifiedException
|
||||
import net.woggioni.jwo.Application
|
||||
import net.woggioni.jwo.JWO
|
||||
import net.woggioni.jwo.Tuple2
|
||||
import java.net.URI
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
|
||||
|
||||
class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
@@ -125,6 +136,33 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
|
||||
private class ServerInitializer(private val cfg : Configuration) : ChannelInitializer<Channel>() {
|
||||
|
||||
private fun createSslCtx(tlsConfiguration : TlsConfiguration) : SslContext {
|
||||
val keyStore = tlsConfiguration.keyStore
|
||||
return if(keyStore == null) {
|
||||
throw IllegalArgumentException("No keystore configured")
|
||||
} else {
|
||||
val javaKeyStore = loadKeystore(keyStore.file, keyStore.password)
|
||||
val serverKey = javaKeyStore.getKey(
|
||||
keyStore.keyAlias, keyStore.keyPassword?.let(String::toCharArray)) as PrivateKey
|
||||
val serverCert : Array<X509Certificate> = Arrays.stream(javaKeyStore.getCertificateChain(keyStore.keyAlias))
|
||||
.map { it as X509Certificate }
|
||||
.toArray {size -> Array<X509Certificate?>(size) { null } }
|
||||
SslContextBuilder.forServer(serverKey, *serverCert).apply {
|
||||
if(tlsConfiguration.verifyClients) {
|
||||
clientAuth(ClientAuth.REQUIRE)
|
||||
val trustStore = tlsConfiguration.trustStore
|
||||
if (trustStore != null) {
|
||||
val ts = loadKeystore(trustStore.file, trustStore.password)
|
||||
trustManager(
|
||||
ClientCertificateValidator.getTrustManager(ts, trustStore.checkCertificateStatus))
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
||||
private val sslContext : SslContext? = cfg.tlsConfiguration?.let(this::createSslCtx)
|
||||
|
||||
companion object {
|
||||
val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
|
||||
fun loadKeystore(file : Path, password : String?) : KeyStore {
|
||||
@@ -132,13 +170,13 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
.map(Tuple2<String, String>::get_2)
|
||||
.orElseThrow {
|
||||
IllegalArgumentException(
|
||||
"Keystore file '${file}' must have .jks or p12 extension")
|
||||
"Keystore file '${file}' must have .jks, .p12, .pfx extension")
|
||||
}
|
||||
val keystore = when(ext.lowercase()) {
|
||||
val keystore = when(ext.substring(1).lowercase()) {
|
||||
"jks" -> KeyStore.getInstance("JKS")
|
||||
"p12", "pfx" -> KeyStore.getInstance("PKCS12")
|
||||
else -> throw IllegalArgumentException(
|
||||
"Keystore file '${file}' must have .jks or p12 extension")
|
||||
"Keystore file '${file}' must have .jks, .p12, .pfx extension")
|
||||
}
|
||||
Files.newInputStream(file).use {
|
||||
keystore.load(it, password?.let(String::toCharArray))
|
||||
@@ -148,34 +186,17 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
}
|
||||
|
||||
override fun initChannel(ch: Channel) {
|
||||
val userAuthorizer = UserAuthorizer(cfg.users)
|
||||
val pipeline = ch.pipeline()
|
||||
val tlsConfiguration = cfg.tlsConfiguration
|
||||
if(tlsConfiguration != null) {
|
||||
val ssc = SelfSignedCertificate()
|
||||
val keyStore = tlsConfiguration.keyStore
|
||||
val sslCtx = if(keyStore == null) {
|
||||
SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build()
|
||||
} else {
|
||||
val javaKeyStore = loadKeystore(keyStore.file, keyStore.password)
|
||||
val serverKey = javaKeyStore.getKey(
|
||||
keyStore.keyAlias, keyStore.keyPassword?.let(String::toCharArray)) as PrivateKey
|
||||
val serverCert = javaKeyStore.getCertificateChain(keyStore.keyAlias) as Array<X509Certificate>
|
||||
SslContextBuilder.forServer(serverKey, *serverCert).build()
|
||||
}
|
||||
val sslHandler = sslCtx.newHandler(ch.alloc())
|
||||
if(sslContext != null) {
|
||||
val sslHandler = sslContext.newHandler(ch.alloc())
|
||||
pipeline.addLast(sslHandler)
|
||||
if(tlsConfiguration.verifyClients) {
|
||||
val trustStore = tlsConfiguration.trustStore?.let {
|
||||
loadKeystore(it.file, it.password)
|
||||
}
|
||||
pipeline.addLast(ClientCertificateValidator.of(sslHandler, trustStore))
|
||||
}
|
||||
}
|
||||
pipeline.addLast(HttpServerCodec())
|
||||
pipeline.addLast(HttpChunkContentCompressor(1024))
|
||||
pipeline.addLast(ChunkedWriteHandler())
|
||||
pipeline.addLast(HttpObjectAggregator(Int.MAX_VALUE))
|
||||
// pipeline.addLast(NettyHttpBasicAuthenticator(mapOf("user" to "password")) { user, _ -> user == "user" })
|
||||
// pipeline.addLast(NettyHttpBasicAuthenticator(mapOf("user" to "password")), userAuthorizer)
|
||||
pipeline.addLast(group, ServerHandler(cfg.cacheFolder, cfg.serverPath))
|
||||
pipeline.addLast(ExceptionHandler())
|
||||
Files.createDirectories(cfg.cacheFolder)
|
||||
@@ -184,9 +205,25 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
|
||||
private class ExceptionHandler : ChannelDuplexHandler() {
|
||||
private val log = contextLogger()
|
||||
|
||||
private val NOT_AUTHORIZED: FullHttpResponse = DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1, HttpResponseStatus.FORBIDDEN, Unpooled.EMPTY_BUFFER).apply {
|
||||
headers()[HttpHeaderNames.CONTENT_LENGTH] = "0"
|
||||
}
|
||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||
log.error(cause.message, cause)
|
||||
ctx.close()
|
||||
when(cause) {
|
||||
is DecoderException -> {
|
||||
log.error(cause.message, cause)
|
||||
ctx.close()
|
||||
}
|
||||
is SSLPeerUnverifiedException -> {
|
||||
ctx.writeAndFlush(NOT_AUTHORIZED.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE_ON_FAILURE)
|
||||
}
|
||||
else -> {
|
||||
log.error(cause.message, cause)
|
||||
ctx.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,35 +325,62 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
}
|
||||
}
|
||||
|
||||
fun run() {
|
||||
// Create the multithreaded event loops for the server
|
||||
val bossGroup: EventLoopGroup = NioEventLoopGroup()
|
||||
val workerGroup: EventLoopGroup = NioEventLoopGroup()
|
||||
try {
|
||||
// A helper class that simplifies server configuration
|
||||
val httpBootstrap = ServerBootstrap()
|
||||
class ServerHandle(
|
||||
private val httpChannel : ChannelFuture,
|
||||
private val bossGroup: EventLoopGroup,
|
||||
private val workerGroup: EventLoopGroup) : AutoCloseable {
|
||||
|
||||
// Configure the server
|
||||
httpBootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel::class.java)
|
||||
.childHandler(ServerInitializer(cfg))
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
private val closeFuture : ChannelFuture = httpChannel.channel().closeFuture()
|
||||
|
||||
// Bind and start to accept incoming connections.
|
||||
val bindAddress = InetSocketAddress(cfg.host, cfg.port)
|
||||
val httpChannel = httpBootstrap.bind(bindAddress).sync()
|
||||
|
||||
// Wait until server socket is closed
|
||||
httpChannel.channel().closeFuture().sync()
|
||||
} finally {
|
||||
workerGroup.shutdownGracefully()
|
||||
bossGroup.shutdownGracefully()
|
||||
fun shutdown() : ChannelFuture {
|
||||
return httpChannel.channel().close()
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
try {
|
||||
closeFuture.sync()
|
||||
} finally {
|
||||
val fut1 = workerGroup.shutdownGracefully()
|
||||
val fut2 = if(bossGroup !== workerGroup) {
|
||||
bossGroup.shutdownGracefully()
|
||||
} else null
|
||||
fut1.sync()
|
||||
fut2?.sync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun run() : ServerHandle {
|
||||
// Create the multithreaded event loops for the server
|
||||
val bossGroup = NioEventLoopGroup()
|
||||
val serverSocketChannel = NioServerSocketChannel::class.java
|
||||
val workerGroup = if(cfg.useVirtualThread) {
|
||||
NioEventLoopGroup(0, Executors.newVirtualThreadPerTaskExecutor())
|
||||
} else {
|
||||
NioEventLoopGroup(0, Executors.newWorkStealingPool())
|
||||
}
|
||||
// A helper class that simplifies server configuration
|
||||
val bootstrap = ServerBootstrap().apply {
|
||||
// Configure the server
|
||||
group(bossGroup, workerGroup)
|
||||
channel(serverSocketChannel)
|
||||
childHandler(ServerInitializer(cfg))
|
||||
option(ChannelOption.SO_BACKLOG, 128)
|
||||
childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
}
|
||||
|
||||
|
||||
// Bind and start to accept incoming connections.
|
||||
val bindAddress = InetSocketAddress(cfg.host, cfg.port)
|
||||
val httpChannel = bootstrap.bind(bindAddress).sync()
|
||||
return ServerHandle(httpChannel, bossGroup, workerGroup)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val log by lazy {
|
||||
contextLogger()
|
||||
}
|
||||
private const val PROTOCOL_HANDLER = "java.protocol.handler.pkgs"
|
||||
private const val HANDLERS_PACKAGE = "net.woggioni.gbcs.url"
|
||||
|
||||
@@ -341,13 +405,9 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
resetCachedUrlHandlers()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
SelfSignedCertificate()
|
||||
ServiceLoader.load(javaClass.module.layer, URLStreamHandlerFactory::class.java).stream().forEach {
|
||||
println(it.type())
|
||||
}
|
||||
// registerUrlProtocolHandler()
|
||||
fun loadConfiguration(args: Array<String>) : Configuration {
|
||||
registerUrlProtocolHandler()
|
||||
URL.setURLStreamHandlerFactory(ClasspathUrlStreamHandlerFactoryProvider())
|
||||
Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader
|
||||
val app = Application.builder("gbcs")
|
||||
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
||||
@@ -355,30 +415,39 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
.build()
|
||||
val confDir = app.computeConfigurationDirectory()
|
||||
val configurationFile = confDir.resolve("gbcs.xml")
|
||||
|
||||
log.info { "here" }
|
||||
if(!Files.exists(configurationFile)) {
|
||||
Files.createDirectories(confDir)
|
||||
val defaultConfigurationFileResourcePath = "net/woggioni/gbcs/gbcs-default.xml"
|
||||
val defaultConfigurationFileResource = GradleBuildCacheServer.javaClass.classLoader
|
||||
.getResource(defaultConfigurationFileResourcePath)
|
||||
?: throw IllegalStateException(
|
||||
"Missing default configuration file 'classpath:$defaultConfigurationFileResourcePath'")
|
||||
val defaultConfigurationFileResourcePath = "classpath:net/woggioni/gbcs/gbcs-default.xml"
|
||||
val defaultConfigurationFileResource = URI(defaultConfigurationFileResourcePath).toURL()
|
||||
Files.newOutputStream(configurationFile).use { outputStream ->
|
||||
defaultConfigurationFileResource.openStream().use { inputStream ->
|
||||
JWO.copy(inputStream, outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
val schemaResource = "net/woggioni/gbcs/gbcs.xsd"
|
||||
val schemaUrl = URL("classpath:net/woggioni/gbcs/gbcs.xsd")
|
||||
// val schemaUrl = GradleBuildCacheServer::class.java.classLoader.getResource(schemaResource)
|
||||
// ?: throw IllegalStateException("Missing configuration schema '$schemaResource'")
|
||||
val schemaUrl2 = URL(schemaUrl.toString())
|
||||
// val schemaUrl = javaClass.getResource("/net/woggioni/gbcs/gbcs.xsd")
|
||||
val schemaUrl = URL.of(URI("classpath:net/woggioni/gbcs/gbcs.xsd"), null)
|
||||
val dbf = Xml.newDocumentBuilderFactory()
|
||||
// dbf.schema = Xml.getSchema(this::class.java.module.getResourceAsStream("/net/woggioni/gbcs/gbcs.xsd"))
|
||||
dbf.schema = Xml.getSchema(schemaUrl)
|
||||
val doc = Files.newInputStream(configurationFile)
|
||||
.use(dbf.newDocumentBuilder()::parse)
|
||||
GradleBuildCacheServer(Configuration.parse(doc.documentElement)).run()
|
||||
val db = dbf.newDocumentBuilder().apply {
|
||||
setErrorHandler(Xml.ErrorHandler(schemaUrl))
|
||||
}
|
||||
val doc = Files.newInputStream(configurationFile).use(db::parse)
|
||||
return Configuration.parse(doc.documentElement)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
val configuration = loadConfiguration(args)
|
||||
// Runtime.getRuntime().addShutdownHook(Thread {
|
||||
// Thread.sleep(5000)
|
||||
// javaClass.classLoader.loadClass("net.woggioni.jwo.exception.ChildProcessException")
|
||||
// }
|
||||
// )
|
||||
GradleBuildCacheServer(configuration).run().use {
|
||||
}
|
||||
}
|
||||
|
||||
fun digest(data : ByteArray,
|
||||
@@ -392,4 +461,14 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
return JWO.bytesToHex(digest(data, md))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object GraalNativeImageConfiguration {
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
val conf = GradleBuildCacheServer.loadConfiguration(args)
|
||||
val handle = GradleBuildCacheServer(conf).run()
|
||||
Thread.sleep(10_000)
|
||||
handle.shutdown()
|
||||
}
|
||||
}
|
@@ -8,9 +8,6 @@ import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.logging.LogManager
|
||||
|
||||
|
||||
|
||||
|
||||
inline fun <reified T> T.contextLogger() = LoggerFactory.getLogger(T::class.java)
|
||||
|
||||
inline fun Logger.traceParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
|
22
src/main/kotlin/net/woggioni/gbcs/UserAuthorizer.kt
Normal file
22
src/main/kotlin/net/woggioni/gbcs/UserAuthorizer.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import io.netty.handler.codec.http.HttpMethod
|
||||
import io.netty.handler.codec.http.HttpRequest
|
||||
|
||||
class UserAuthorizer(private val users: Map<String, Set<Role>>) : Authorizer {
|
||||
|
||||
companion object {
|
||||
private val METHOD_MAP = mapOf(
|
||||
Role.Reader to setOf(HttpMethod.GET, HttpMethod.HEAD),
|
||||
Role.Writer to setOf(HttpMethod.PUT, HttpMethod.POST)
|
||||
)
|
||||
}
|
||||
|
||||
override fun authorize(user: String, request: HttpRequest) = users[user]?.let { roles ->
|
||||
val allowedMethods = roles.asSequence()
|
||||
.mapNotNull(METHOD_MAP::get)
|
||||
.flatten()
|
||||
.toSet()
|
||||
request.method() in allowedMethods
|
||||
} ?: false
|
||||
}
|
@@ -1,5 +1,7 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.net.URL
|
||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
|
||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA
|
||||
@@ -7,14 +9,17 @@ import javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING
|
||||
import javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI
|
||||
import javax.xml.parsers.DocumentBuilder
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.Source
|
||||
import javax.xml.transform.stream.StreamSource
|
||||
import javax.xml.validation.Schema
|
||||
import javax.xml.validation.SchemaFactory
|
||||
import net.woggioni.jwo.xml.Xml
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.NodeList
|
||||
import org.xml.sax.ErrorHandler
|
||||
import org.xml.sax.ErrorHandler as ErrHandler
|
||||
import org.xml.sax.SAXNotRecognizedException
|
||||
import org.xml.sax.SAXNotSupportedException
|
||||
import org.xml.sax.SAXParseException
|
||||
@@ -66,10 +71,10 @@ class ElementIterator(parent: Element, name: String? = null) : Iterator<Element>
|
||||
|
||||
object Xml {
|
||||
|
||||
private class XmlErrorHandler(private val fileURL: URL) : ErrorHandler {
|
||||
class ErrorHandler(private val fileURL: URL) : ErrHandler {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(XmlErrorHandler::class.java)
|
||||
private val log = LoggerFactory.getLogger(ErrorHandler::class.java)
|
||||
}
|
||||
|
||||
override fun warning(ex: SAXParseException) {
|
||||
@@ -118,9 +123,18 @@ object Xml {
|
||||
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
||||
sf.errorHandler = ErrorHandler(schema)
|
||||
return sf.newSchema(schema)
|
||||
}
|
||||
|
||||
fun getSchema(inputStream: InputStream): Schema {
|
||||
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
||||
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
||||
return sf.newSchema(StreamSource(inputStream))
|
||||
}
|
||||
|
||||
fun newDocumentBuilderFactory(): DocumentBuilderFactory {
|
||||
val dbf = DocumentBuilderFactory.newInstance()
|
||||
dbf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
@@ -129,6 +143,7 @@ object Xml {
|
||||
dbf.isExpandEntityReferences = false
|
||||
dbf.isIgnoringComments = true
|
||||
dbf.isNamespaceAware = true
|
||||
dbf.isValidating = false
|
||||
return dbf
|
||||
}
|
||||
|
||||
|
18
src/main/resources/logback.xml
Normal file
18
src/main/resources/logback.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?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>
|
||||
</configuration>
|
@@ -2,8 +2,8 @@
|
||||
|
||||
handlers = java.util.logging.ConsoleHandler
|
||||
|
||||
java.util.logging.ConsoleHandler.level = FINEST
|
||||
java.util.logging.ConsoleHandler.level = FINER
|
||||
java.util.logging.ConsoleHandler.filter =
|
||||
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
|
||||
java.util.logging.SimpleFormatter.format = %1$tF %1$tT [%4$s] %2$s %5$s %n
|
||||
java.util.logging.SimpleFormatter.format = %1$tF %1$tT [%4$s] %2$s %5$s %6$s%n
|
||||
java.util.logging.ConsoleHandler.encoding =
|
@@ -1,14 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<server xmlns="urn:gradle-build-cache-server" path="/cache">
|
||||
<gbcs:server xmlns:gbcs="urn:net.woggioni.gbcs" path="/cache" useVirtualThreads="false">
|
||||
<bind host="127.0.0.1" port="11443"/>
|
||||
<cache path="/tmp/gbcs"/>
|
||||
<groups>
|
||||
<readers>
|
||||
<!-- <user name="reader"/>-->
|
||||
</readers>
|
||||
<writers>
|
||||
<!-- <user name="writer"/>-->
|
||||
</writers>
|
||||
</groups>
|
||||
<tls verify-clients="true"/>
|
||||
</server>
|
||||
<authorization>
|
||||
<users>
|
||||
<user name="user1" password="password"/>
|
||||
<user name="user2" password="password"/>
|
||||
<user name="user3" password="password"/>
|
||||
<user name="user4" password="password"/>
|
||||
<user name="user5" password="password">
|
||||
<roles>
|
||||
<reader/>
|
||||
<writer/>
|
||||
</roles>
|
||||
</user>
|
||||
</users>
|
||||
<groups>
|
||||
<group name="group1">
|
||||
<users>
|
||||
<user ref="user1"/>
|
||||
</users>
|
||||
<roles>
|
||||
<reader/>
|
||||
<writer/>
|
||||
</roles>
|
||||
</group>
|
||||
<group name="group2">
|
||||
<users>
|
||||
<user ref="user1"/>
|
||||
<user ref="user2"/>
|
||||
</users>
|
||||
<roles>
|
||||
<reader/>
|
||||
<writer/>
|
||||
</roles>
|
||||
</group>
|
||||
</groups>
|
||||
</authorization>
|
||||
<tls-certificate-authorization>
|
||||
<group-extractor attribute-name="CN" pattern="(.*)"/>
|
||||
<user-extractor attribute-name="CN" pattern="(.*)"/>
|
||||
</tls-certificate-authorization>
|
||||
<!-- <tls verify-clients="false">-->
|
||||
<!-- <keystore file="" key-alias=""/>-->
|
||||
<!-- <truststore file=""/>-->
|
||||
<!-- </tls>-->
|
||||
</gbcs:server>
|
@@ -1,17 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<xs:schema elementFormDefault="qualified" targetNamespace="urn:gradle-build-cache-server" version="1.0"
|
||||
<xs:schema targetNamespace="urn:net.woggioni.gbcs"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:gbcs="urn:gradle-build-cache-server">
|
||||
xmlns:gbcs="urn:net.woggioni.gbcs">
|
||||
<xs:element name="server" type="gbcs:serverType"/>
|
||||
|
||||
<xs:complexType name="serverType">
|
||||
<xs:sequence minOccurs="0">
|
||||
<xs:element name="bind" type="gbcs:bindType"/>
|
||||
<xs:element name="cache" type="gbcs:cacheDirType"/>
|
||||
<xs:element name="groups" type="gbcs:groupsType"/>
|
||||
<xs:element name="tls" type="gbcs:tlsType"/>
|
||||
<xs:element name="bind" type="gbcs:bindType" maxOccurs="1"/>
|
||||
<xs:element name="cache" type="gbcs:cacheDirType" maxOccurs="1"/>
|
||||
<xs:element name="authorization" type="gbcs:authorizationType" maxOccurs="1">
|
||||
<xs:key name="userId">
|
||||
<xs:selector xpath="users/user"/>
|
||||
<xs:field xpath="@name"/>
|
||||
</xs:key>
|
||||
<xs:keyref name="userRef" refer="gbcs:userId">
|
||||
<xs:selector xpath="groups/group/users/user"/>
|
||||
<xs:field xpath="@ref"/>
|
||||
</xs:keyref>
|
||||
</xs:element>
|
||||
<xs:element name="tls-certificate-authorization" type="gbcs:tlsCertificateAuthorizationType" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="tls" type="gbcs:tlsType" minOccurs="0" maxOccurs="1"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="path" type="xs:string"/>
|
||||
<xs:attribute name="useVirtualThreads" type="xs:boolean" use="optional"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="bindType">
|
||||
@@ -23,26 +34,92 @@
|
||||
<xs:attribute name="path" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="groupsType">
|
||||
<xs:complexType name="tlsCertificateAuthorizationType">
|
||||
<xs:sequence>
|
||||
<xs:element name="group-extractor" type="gbcs:X500NameExtractorType"/>
|
||||
<xs:element name="user-extractor" type="gbcs:X500NameExtractorType"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="X500NameExtractorType">
|
||||
<xs:attribute name="attribute-name" type="xs:string"/>
|
||||
<xs:attribute name="pattern" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="authorizationType">
|
||||
<xs:all>
|
||||
<xs:element name="readers" type="gbcs:groupType"/>
|
||||
<xs:element name="writers" type="gbcs:groupType"/>
|
||||
<xs:element name="users" type="gbcs:usersType"/>
|
||||
<xs:element name="groups" type="gbcs:groupsType">
|
||||
<xs:unique name="groupKey">
|
||||
<xs:selector xpath="group"/>
|
||||
<xs:field xpath="@name"/>
|
||||
</xs:unique>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="groupType">
|
||||
<xs:complexType name="usersType">
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" name="user" type="gbcs:userType"/>
|
||||
<xs:element name="user" type="gbcs:userType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="userType">
|
||||
<xs:all minOccurs="0" maxOccurs="1">
|
||||
<xs:element name="roles" type="gbcs:rolesType"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||
<xs:attribute name="password" type="xs:string" use="optional"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="groupsType">
|
||||
<xs:sequence>
|
||||
<xs:element name="group" type="gbcs:groupType" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="groupType">
|
||||
<xs:sequence>
|
||||
<xs:element name="users" type="gbcs:userRefsType" maxOccurs="1" minOccurs="0">
|
||||
<xs:unique name="userRefWriterKey">
|
||||
<xs:selector xpath="user"/>
|
||||
<xs:field xpath="@ref"/>
|
||||
</xs:unique>
|
||||
</xs:element>
|
||||
<xs:element name="roles" type="gbcs:rolesType" maxOccurs="1" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:simpleType name="role" final="restriction" >
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="READER" />
|
||||
<xs:enumeration value="WRITER" />
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
|
||||
<xs:complexType name="rolesType">
|
||||
<xs:sequence>
|
||||
<xs:choice maxOccurs="unbounded">
|
||||
<xs:element name="writer"/>
|
||||
<xs:element name="reader"/>
|
||||
</xs:choice>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="userRefsType">
|
||||
<xs:sequence>
|
||||
<xs:element name="user" type="gbcs:userRefType" maxOccurs="unbounded" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="userRefType">
|
||||
<xs:attribute name="ref" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="tlsType">
|
||||
<xs:all>
|
||||
<xs:element name="keystore" type="gbcs:keyStoreType" minOccurs="0"/>
|
||||
<xs:element name="keystore" type="gbcs:keyStoreType" />
|
||||
<xs:element name="truststore" type="gbcs:trustStoreType" minOccurs="0"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="verify-clients" type="xs:boolean" use="optional"/>
|
||||
@@ -58,6 +135,7 @@
|
||||
<xs:complexType name="trustStoreType">
|
||||
<xs:attribute name="file" type="xs:string" use="required"/>
|
||||
<xs:attribute name="password" type="xs:string"/>
|
||||
<xs:attribute name="check-certificate-status" type="xs:boolean"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="propertiesType">
|
||||
|
@@ -0,0 +1,35 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertPathValidator
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.PKIXParameters
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class CertificateValidationTest {
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
val keystore = KeyStore.getInstance("PKCS12")
|
||||
val keystorePath = Path.of("/home/woggioni/ssl/woggioni@f6aa5663ef26.pfx")
|
||||
Files.newInputStream(keystorePath).use {
|
||||
keystore.load(it, System.getenv("KEYPASS").toCharArray())
|
||||
}
|
||||
val pkix = CertPathValidator.getInstance("PKIX")
|
||||
val trustStore = KeyStore.getInstance("PKCS12")
|
||||
val trustStorePath = Path.of("/home/woggioni/ssl/truststore.pfx")
|
||||
|
||||
Files.newInputStream(trustStorePath).use {
|
||||
trustStore.load(it, "123456".toCharArray())
|
||||
}
|
||||
|
||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
val cert = keystore.getCertificateChain("woggioni@f6aa5663ef26").toList()
|
||||
.let(certificateFactory::generateCertPath)
|
||||
val params = PKIXParameters(trustStore)
|
||||
params.isRevocationEnabled = false
|
||||
pkix.validate(cert, params)
|
||||
}
|
||||
}
|
22
src/test/kotlin/net/woggioni/gbcs/ConfigurationTest.kt
Normal file
22
src/test/kotlin/net/woggioni/gbcs/ConfigurationTest.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ConfigurationTest {
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
GradleBuildCacheServer.registerUrlProtocolHandler()
|
||||
val schemaUrl = this::class.java.getResource("/net/woggioni/gbcs/gbcs.xsd")
|
||||
val dbf = Xml.newDocumentBuilderFactory()
|
||||
dbf.schema = Xml.getSchema(schemaUrl)
|
||||
val db = dbf.newDocumentBuilder().apply {
|
||||
setErrorHandler(Xml.ErrorHandler(schemaUrl))
|
||||
}
|
||||
val configurationUrl = this::class.java.getResource("/net/woggioni/gbcs/gbcs-default.xml")
|
||||
val doc = configurationUrl.openStream().use(db::parse)
|
||||
Configuration.parse(doc.documentElement)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user