temporary commit
This commit is contained in:
62
build.gradle
62
build.gradle
@@ -1,19 +1,33 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
id 'application'
|
||||||
alias catalog.plugins.kotlin.jvm
|
alias catalog.plugins.kotlin.jvm
|
||||||
|
alias catalog.plugins.graalvm.native.image
|
||||||
|
alias catalog.plugins.graalvm.jlink
|
||||||
alias catalog.plugins.envelope
|
alias catalog.plugins.envelope
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
|
||||||
import net.woggioni.gradle.envelope.EnvelopeJarTask
|
import net.woggioni.gradle.envelope.EnvelopeJarTask
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = 'net.woggioni'
|
group = 'net.woggioni'
|
||||||
|
|
||||||
version = getProperty('gbcs.version')
|
version = getProperty('gbcs.version')
|
||||||
|
|
||||||
|
application {
|
||||||
|
mainModule = 'net.woggioni.gbcs'
|
||||||
|
mainClass = 'net.woggioni.gbcs.GradleBuildCacheServer'
|
||||||
|
// mainClass = 'net.woggioni.gbcs.NettyPingServer'
|
||||||
|
}
|
||||||
|
|
||||||
|
configureNativeImage {
|
||||||
|
mainClass = 'net.woggioni.gbcs.GraalNativeImageConfiguration'
|
||||||
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url = 'https://woggioni.net/mvn'
|
url = getProperty('gitea.maven.url')
|
||||||
content {
|
content {
|
||||||
includeModule 'net.woggioni', 'jwo'
|
includeModule 'net.woggioni', 'jwo'
|
||||||
includeGroup 'com.lys'
|
includeGroup 'com.lys'
|
||||||
@@ -27,7 +41,11 @@ dependencies {
|
|||||||
implementation catalog.slf4j.api
|
implementation catalog.slf4j.api
|
||||||
implementation catalog.netty.codec.http
|
implementation catalog.netty.codec.http
|
||||||
|
|
||||||
runtimeOnly catalog.slf4j.jdk14
|
// runtimeOnly catalog.slf4j.jdk14
|
||||||
|
runtimeOnly catalog.logback.classic
|
||||||
|
// // https://mvnrepository.com/artifact/org.fusesource.jansi/jansi
|
||||||
|
// runtimeOnly group: 'org.fusesource.jansi', name: 'jansi', version: '2.4.1'
|
||||||
|
|
||||||
|
|
||||||
testImplementation catalog.junit.jupiter.api
|
testImplementation catalog.junit.jupiter.api
|
||||||
testImplementation catalog.junit.jupiter.params
|
testImplementation catalog.junit.jupiter.params
|
||||||
@@ -36,39 +54,37 @@ dependencies {
|
|||||||
|
|
||||||
java {
|
java {
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
|
modularity.inferModulePath = true
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
// vendor = JvmVendorSpec.GRAAL_VM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
|
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
|
||||||
modularity.inferModulePath = true
|
|
||||||
options.compilerArgs << '--patch-module' << 'net.woggioni.gbcs=' + project.sourceSets.main.output.asPath
|
options.compilerArgs << '--patch-module' << 'net.woggioni.gbcs=' + project.sourceSets.main.output.asPath
|
||||||
options.release = 17
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.withType(JavaCompile) {
|
||||||
|
modularity.inferModulePath = true
|
||||||
|
options.release = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
tasks.named("compileKotlin", KotlinCompile.class) {
|
tasks.named("compileKotlin", KotlinCompile.class) {
|
||||||
kotlinOptions {
|
compilerOptions.jvmTarget = JvmTarget.JVM_21
|
||||||
jvmTarget = 17
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named('envelopeJar', EnvelopeJarTask.class) {
|
Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named('envelopeJar', EnvelopeJarTask.class) {
|
||||||
mainModule = 'net.woggioni.gbcs'
|
// systemProperties['java.util.logging.config.class'] = 'net.woggioni.gbcs.LoggingConfig'
|
||||||
mainClass = 'net.woggioni.gbcs.GradleBuildCacheServer'
|
// systemProperties['log.config.source'] = 'logging.properties'
|
||||||
systemProperty 'java.util.logging.config.class', 'net.woggioni.gbcs.LoggingConfig'
|
systemProperties['logback.configurationFile'] = 'classpath:logback.xml'
|
||||||
systemProperty 'log.config.source', 'logging.properties'
|
|
||||||
|
|
||||||
manifest {
|
|
||||||
attributes([
|
|
||||||
'Add-Exports' : 'java.base/sun.security.x509'
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
envelopeRun {
|
|
||||||
|
|
||||||
mainModule = 'net.woggioni.envelope'
|
|
||||||
modularity.inferModulePath = true
|
|
||||||
jvmArgs('--add-exports=java.base/sun.security.x509=io.netty.handler')
|
|
||||||
}
|
|
||||||
|
|
||||||
def envelopeJarArtifact = artifacts.add('archives', envelopeJarTaskProvider.get().archiveFile.get().asFile) {
|
def envelopeJarArtifact = artifacts.add('archives', envelopeJarTaskProvider.get().archiveFile.get().asFile) {
|
||||||
type = 'jar'
|
type = 'jar'
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
gbcs.version = 0.1-SNAPSHOT
|
gbcs.version = 2024.12.13
|
||||||
|
|
||||||
lys.version = 0.2-SNAPSHOT
|
lys.version = 2024.12.07
|
||||||
|
|
||||||
|
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
35
gradlew
vendored
35
gradlew
vendored
@@ -55,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -80,13 +80,11 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
APP_NAME="Gradle"
|
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -133,22 +131,29 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
# double quotes to make sure that they get re-expanded; and
|
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
21
gradlew.bat
vendored
21
gradlew.bat
vendored
@@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
|||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%"=="" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@@ -42,11 +43,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
1
native-image/native-image.properties
Normal file
1
native-image/native-image.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Args=-H:Optimize=3 -H:+TraceClassInitialization
|
@@ -1,23 +1,17 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
maven {
|
maven {
|
||||||
content {
|
url = getProperty('gitea.maven.url')
|
||||||
includeModule 'net.woggioni.gradle', 'envelope'
|
|
||||||
includeModule 'net.woggioni.gradle.envelope', 'net.woggioni.gradle.envelope.gradle.plugin'
|
|
||||||
includeModule 'net.woggioni.gradle', 'lombok'
|
|
||||||
includeModule 'net.woggioni.gradle.lombok', 'net.woggioni.gradle.lombok.gradle.plugin'
|
|
||||||
}
|
|
||||||
url = 'https://woggioni.net/mvn/'
|
|
||||||
}
|
}
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
includeBuild('../envelope')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url = 'https://woggioni.net/mvn/'
|
url = getProperty('gitea.maven.url')
|
||||||
content {
|
content {
|
||||||
includeGroup 'com.lys'
|
includeGroup 'com.lys'
|
||||||
}
|
}
|
||||||
@@ -26,6 +20,7 @@ dependencyResolutionManagement {
|
|||||||
versionCatalogs {
|
versionCatalogs {
|
||||||
catalog {
|
catalog {
|
||||||
from group: 'com.lys', name: 'lys-catalog', version: getProperty('lys.version')
|
from group: 'com.lys', name: 'lys-catalog', version: getProperty('lys.version')
|
||||||
|
version('envelope', '2024.12.15')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import java.net.URLStreamHandlerFactory;
|
import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider;
|
||||||
|
|
||||||
module net.woggioni.gbcs {
|
module net.woggioni.gbcs {
|
||||||
|
requires java.sql;
|
||||||
requires java.xml;
|
requires java.xml;
|
||||||
requires java.logging;
|
requires java.logging;
|
||||||
requires kotlin.stdlib;
|
requires kotlin.stdlib;
|
||||||
@@ -14,6 +15,8 @@ module net.woggioni.gbcs {
|
|||||||
requires net.woggioni.jwo;
|
requires net.woggioni.jwo;
|
||||||
|
|
||||||
exports net.woggioni.gbcs;
|
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;
|
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
|
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) {
|
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
||||||
if (evt is SslHandshakeCompletionEvent) {
|
if (evt is SslHandshakeCompletionEvent) {
|
||||||
if (evt.isSuccess) {
|
if (evt.isSuccess) {
|
||||||
@@ -32,20 +34,17 @@ class ClientCertificateValidator private constructor(private val sslHandler : Ss
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
fun getTrustManager(trustStore : KeyStore?, certificateRevocationEnabled : Boolean) : X509TrustManager {
|
||||||
fun of(sslHandler : SslHandler, trustStore : KeyStore?) : ClientCertificateValidator {
|
return if(trustStore != null) {
|
||||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||||
|
val validator = CertPathValidator.getInstance("PKIX").apply {
|
||||||
val validator = CertPathValidator.getInstance("PKIX").apply {
|
val rc = revocationChecker as PKIXRevocationChecker
|
||||||
val rc = revocationChecker as PKIXRevocationChecker
|
rc.options = EnumSet.of(
|
||||||
rc.options = EnumSet.of(
|
PKIXRevocationChecker.Option.NO_FALLBACK)
|
||||||
PKIXRevocationChecker.Option.NO_FALLBACK,
|
}
|
||||||
PKIXRevocationChecker.Option.SOFT_FAIL,
|
val params = PKIXParameters(trustStore).apply {
|
||||||
PKIXRevocationChecker.Option.PREFER_CRLS)
|
isRevocationEnabled = certificateRevocationEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
val manager = if(trustStore != null) {
|
|
||||||
val params = PKIXParameters(trustStore)
|
|
||||||
object : X509TrustManager {
|
object : X509TrustManager {
|
||||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
|
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
|
||||||
val clientCertificateChain = certificateFactory.generateCertPath(chain.toList())
|
val clientCertificateChain = certificateFactory.generateCertPath(chain.toList())
|
||||||
@@ -56,15 +55,23 @@ class ClientCertificateValidator private constructor(private val sslHandler : Ss
|
|||||||
throw NotImplementedError()
|
throw NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<X509Certificate> {
|
private val acceptedIssuers = trustStore.aliases().asSequence()
|
||||||
throw NotImplementedError()
|
.filter (trustStore::isCertificateEntry)
|
||||||
}
|
.map(trustStore::getCertificate)
|
||||||
|
.map { it as X509Certificate }
|
||||||
|
.toList()
|
||||||
|
.toTypedArray()
|
||||||
|
|
||||||
|
override fun getAcceptedIssuers() = acceptedIssuers
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||||
trustManagerFactory.trustManagers.asSequence().filter { it is X509TrustManager }.single() as X509TrustManager
|
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(
|
data class TrustStore(
|
||||||
val file : Path,
|
val file : Path,
|
||||||
val password : String?,
|
val password : String?,
|
||||||
|
val checkCertificateStatus : Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Configuration(
|
data class Configuration(
|
||||||
@@ -28,18 +29,26 @@ data class Configuration(
|
|||||||
val host : String,
|
val host : String,
|
||||||
val port : Int,
|
val port : Int,
|
||||||
val users : Map<String, Set<Role>>,
|
val users : Map<String, Set<Role>>,
|
||||||
|
val groups : Map<String, Set<Role>>,
|
||||||
val tlsConfiguration: TlsConfiguration?,
|
val tlsConfiguration: TlsConfiguration?,
|
||||||
val serverPath : String
|
val serverPath : String,
|
||||||
|
val useVirtualThread : Boolean
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun parse(document : Element) : Configuration {
|
fun parse(document : Element) : Configuration {
|
||||||
|
|
||||||
var cacheFolder = Paths.get(System.getProperty("user.home")).resolve(".gbcs")
|
var cacheFolder = Paths.get(System.getProperty("user.home")).resolve(".gbcs")
|
||||||
var host : String = "127.0.0.1"
|
var host = "127.0.0.1"
|
||||||
var port : Int = 11080
|
var port = 11080
|
||||||
var users = emptyMap<String, Set<Role>>()
|
val users = emptyMap<String, Set<Role>>()
|
||||||
|
val groups = emptyMap<String, Set<Role>>()
|
||||||
var tlsConfiguration : TlsConfiguration? = null
|
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()) {
|
for(child in document.asIterable()) {
|
||||||
when(child.nodeName) {
|
when(child.nodeName) {
|
||||||
@@ -62,8 +71,8 @@ data class Configuration(
|
|||||||
val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
||||||
val trustStorePassword = granChild.getAttribute("password")
|
val trustStorePassword = granChild.getAttribute("password")
|
||||||
.takeIf(String::isNotEmpty)
|
.takeIf(String::isNotEmpty)
|
||||||
val keyAlias = granChild.getAttribute("server-key-alias")
|
val keyAlias = granChild.getAttribute("key-alias")
|
||||||
val keyPasswordPassword = granChild.getAttribute("server-key-password")
|
val keyPasswordPassword = granChild.getAttribute("password")
|
||||||
.takeIf(String::isNotEmpty)
|
.takeIf(String::isNotEmpty)
|
||||||
keyStore = KeyStore(
|
keyStore = KeyStore(
|
||||||
trustStoreFile,
|
trustStoreFile,
|
||||||
@@ -76,9 +85,14 @@ data class Configuration(
|
|||||||
val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
||||||
val trustStorePassword = granChild.getAttribute("password")
|
val trustStorePassword = granChild.getAttribute("password")
|
||||||
.takeIf(String::isNotEmpty)
|
.takeIf(String::isNotEmpty)
|
||||||
|
val checkCertificateStatus = granChild.getAttribute("check-certificate-status")
|
||||||
|
.takeIf(String::isNotEmpty)
|
||||||
|
?.let(String::toBoolean)
|
||||||
|
?: false
|
||||||
trustStore = TrustStore(
|
trustStore = TrustStore(
|
||||||
trustStoreFile,
|
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.PrivateKey
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.AbstractMap.SimpleEntry
|
import java.util.AbstractMap.SimpleEntry
|
||||||
|
import java.util.Arrays
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.ServiceLoader
|
import java.util.concurrent.Executors
|
||||||
import io.netty.bootstrap.ServerBootstrap
|
import io.netty.bootstrap.ServerBootstrap
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.buffer.Unpooled
|
import io.netty.buffer.Unpooled
|
||||||
import io.netty.channel.Channel
|
import io.netty.channel.Channel
|
||||||
import io.netty.channel.ChannelDuplexHandler
|
import io.netty.channel.ChannelDuplexHandler
|
||||||
|
import io.netty.channel.ChannelFuture
|
||||||
import io.netty.channel.ChannelFutureListener
|
import io.netty.channel.ChannelFutureListener
|
||||||
import io.netty.channel.ChannelHandlerContext
|
import io.netty.channel.ChannelHandlerContext
|
||||||
import io.netty.channel.ChannelInitializer
|
import io.netty.channel.ChannelInitializer
|
||||||
@@ -30,11 +32,13 @@ import io.netty.channel.EventLoopGroup
|
|||||||
import io.netty.channel.SimpleChannelInboundHandler
|
import io.netty.channel.SimpleChannelInboundHandler
|
||||||
import io.netty.channel.nio.NioEventLoopGroup
|
import io.netty.channel.nio.NioEventLoopGroup
|
||||||
import io.netty.channel.socket.nio.NioServerSocketChannel
|
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.compression.CompressionOptions
|
||||||
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
import io.netty.handler.codec.http.DefaultFullHttpResponse
|
||||||
import io.netty.handler.codec.http.DefaultHttpContent
|
import io.netty.handler.codec.http.DefaultHttpContent
|
||||||
import io.netty.handler.codec.http.DefaultHttpResponse
|
import io.netty.handler.codec.http.DefaultHttpResponse
|
||||||
import io.netty.handler.codec.http.FullHttpRequest
|
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.HttpContentCompressor
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames
|
import io.netty.handler.codec.http.HttpHeaderNames
|
||||||
import io.netty.handler.codec.http.HttpHeaderValues
|
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.HttpResponseStatus
|
||||||
import io.netty.handler.codec.http.HttpServerCodec
|
import io.netty.handler.codec.http.HttpServerCodec
|
||||||
import io.netty.handler.codec.http.HttpUtil
|
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.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.SslContextBuilder
|
||||||
import io.netty.handler.ssl.util.SelfSignedCertificate
|
|
||||||
import io.netty.handler.stream.ChunkedNioFile
|
import io.netty.handler.stream.ChunkedNioFile
|
||||||
import io.netty.handler.stream.ChunkedWriteHandler
|
import io.netty.handler.stream.ChunkedWriteHandler
|
||||||
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
||||||
|
import io.netty.util.concurrent.DefaultThreadFactory
|
||||||
import io.netty.util.concurrent.EventExecutorGroup
|
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.Application
|
||||||
import net.woggioni.jwo.JWO
|
import net.woggioni.jwo.JWO
|
||||||
import net.woggioni.jwo.Tuple2
|
import net.woggioni.jwo.Tuple2
|
||||||
|
import java.net.URI
|
||||||
|
import java.util.concurrent.ForkJoinPool
|
||||||
|
|
||||||
|
|
||||||
class GradleBuildCacheServer(private val cfg : Configuration) {
|
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 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 {
|
companion object {
|
||||||
val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
|
val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
|
||||||
fun loadKeystore(file : Path, password : String?) : KeyStore {
|
fun loadKeystore(file : Path, password : String?) : KeyStore {
|
||||||
@@ -132,13 +170,13 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
|||||||
.map(Tuple2<String, String>::get_2)
|
.map(Tuple2<String, String>::get_2)
|
||||||
.orElseThrow {
|
.orElseThrow {
|
||||||
IllegalArgumentException(
|
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")
|
"jks" -> KeyStore.getInstance("JKS")
|
||||||
"p12", "pfx" -> KeyStore.getInstance("PKCS12")
|
"p12", "pfx" -> KeyStore.getInstance("PKCS12")
|
||||||
else -> throw IllegalArgumentException(
|
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 {
|
Files.newInputStream(file).use {
|
||||||
keystore.load(it, password?.let(String::toCharArray))
|
keystore.load(it, password?.let(String::toCharArray))
|
||||||
@@ -148,34 +186,17 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun initChannel(ch: Channel) {
|
override fun initChannel(ch: Channel) {
|
||||||
|
val userAuthorizer = UserAuthorizer(cfg.users)
|
||||||
val pipeline = ch.pipeline()
|
val pipeline = ch.pipeline()
|
||||||
val tlsConfiguration = cfg.tlsConfiguration
|
if(sslContext != null) {
|
||||||
if(tlsConfiguration != null) {
|
val sslHandler = sslContext.newHandler(ch.alloc())
|
||||||
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())
|
|
||||||
pipeline.addLast(sslHandler)
|
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(HttpServerCodec())
|
||||||
pipeline.addLast(HttpChunkContentCompressor(1024))
|
pipeline.addLast(HttpChunkContentCompressor(1024))
|
||||||
pipeline.addLast(ChunkedWriteHandler())
|
pipeline.addLast(ChunkedWriteHandler())
|
||||||
pipeline.addLast(HttpObjectAggregator(Int.MAX_VALUE))
|
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(group, ServerHandler(cfg.cacheFolder, cfg.serverPath))
|
||||||
pipeline.addLast(ExceptionHandler())
|
pipeline.addLast(ExceptionHandler())
|
||||||
Files.createDirectories(cfg.cacheFolder)
|
Files.createDirectories(cfg.cacheFolder)
|
||||||
@@ -184,9 +205,25 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
|||||||
|
|
||||||
private class ExceptionHandler : ChannelDuplexHandler() {
|
private class ExceptionHandler : ChannelDuplexHandler() {
|
||||||
private val log = contextLogger()
|
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) {
|
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||||
log.error(cause.message, cause)
|
when(cause) {
|
||||||
ctx.close()
|
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() {
|
class ServerHandle(
|
||||||
// Create the multithreaded event loops for the server
|
private val httpChannel : ChannelFuture,
|
||||||
val bossGroup: EventLoopGroup = NioEventLoopGroup()
|
private val bossGroup: EventLoopGroup,
|
||||||
val workerGroup: EventLoopGroup = NioEventLoopGroup()
|
private val workerGroup: EventLoopGroup) : AutoCloseable {
|
||||||
try {
|
|
||||||
// A helper class that simplifies server configuration
|
|
||||||
val httpBootstrap = ServerBootstrap()
|
|
||||||
|
|
||||||
// Configure the server
|
private val closeFuture : ChannelFuture = httpChannel.channel().closeFuture()
|
||||||
httpBootstrap.group(bossGroup, workerGroup)
|
|
||||||
.channel(NioServerSocketChannel::class.java)
|
|
||||||
.childHandler(ServerInitializer(cfg))
|
|
||||||
.option(ChannelOption.SO_BACKLOG, 128)
|
|
||||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
|
||||||
|
|
||||||
// Bind and start to accept incoming connections.
|
fun shutdown() : ChannelFuture {
|
||||||
val bindAddress = InetSocketAddress(cfg.host, cfg.port)
|
return httpChannel.channel().close()
|
||||||
val httpChannel = httpBootstrap.bind(bindAddress).sync()
|
|
||||||
|
|
||||||
// Wait until server socket is closed
|
|
||||||
httpChannel.channel().closeFuture().sync()
|
|
||||||
} finally {
|
|
||||||
workerGroup.shutdownGracefully()
|
|
||||||
bossGroup.shutdownGracefully()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
companion object {
|
||||||
|
|
||||||
|
private val log by lazy {
|
||||||
|
contextLogger()
|
||||||
|
}
|
||||||
private const val PROTOCOL_HANDLER = "java.protocol.handler.pkgs"
|
private const val PROTOCOL_HANDLER = "java.protocol.handler.pkgs"
|
||||||
private const val HANDLERS_PACKAGE = "net.woggioni.gbcs.url"
|
private const val HANDLERS_PACKAGE = "net.woggioni.gbcs.url"
|
||||||
|
|
||||||
@@ -341,13 +405,9 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
|||||||
resetCachedUrlHandlers()
|
resetCachedUrlHandlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
fun loadConfiguration(args: Array<String>) : Configuration {
|
||||||
fun main(args: Array<String>) {
|
registerUrlProtocolHandler()
|
||||||
SelfSignedCertificate()
|
URL.setURLStreamHandlerFactory(ClasspathUrlStreamHandlerFactoryProvider())
|
||||||
ServiceLoader.load(javaClass.module.layer, URLStreamHandlerFactory::class.java).stream().forEach {
|
|
||||||
println(it.type())
|
|
||||||
}
|
|
||||||
// registerUrlProtocolHandler()
|
|
||||||
Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader
|
Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader
|
||||||
val app = Application.builder("gbcs")
|
val app = Application.builder("gbcs")
|
||||||
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
||||||
@@ -355,30 +415,39 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
|||||||
.build()
|
.build()
|
||||||
val confDir = app.computeConfigurationDirectory()
|
val confDir = app.computeConfigurationDirectory()
|
||||||
val configurationFile = confDir.resolve("gbcs.xml")
|
val configurationFile = confDir.resolve("gbcs.xml")
|
||||||
|
log.info { "here" }
|
||||||
if(!Files.exists(configurationFile)) {
|
if(!Files.exists(configurationFile)) {
|
||||||
Files.createDirectories(confDir)
|
Files.createDirectories(confDir)
|
||||||
val defaultConfigurationFileResourcePath = "net/woggioni/gbcs/gbcs-default.xml"
|
val defaultConfigurationFileResourcePath = "classpath:net/woggioni/gbcs/gbcs-default.xml"
|
||||||
val defaultConfigurationFileResource = GradleBuildCacheServer.javaClass.classLoader
|
val defaultConfigurationFileResource = URI(defaultConfigurationFileResourcePath).toURL()
|
||||||
.getResource(defaultConfigurationFileResourcePath)
|
|
||||||
?: throw IllegalStateException(
|
|
||||||
"Missing default configuration file 'classpath:$defaultConfigurationFileResourcePath'")
|
|
||||||
Files.newOutputStream(configurationFile).use { outputStream ->
|
Files.newOutputStream(configurationFile).use { outputStream ->
|
||||||
defaultConfigurationFileResource.openStream().use { inputStream ->
|
defaultConfigurationFileResource.openStream().use { inputStream ->
|
||||||
JWO.copy(inputStream, outputStream)
|
JWO.copy(inputStream, outputStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val schemaResource = "net/woggioni/gbcs/gbcs.xsd"
|
// val schemaUrl = javaClass.getResource("/net/woggioni/gbcs/gbcs.xsd")
|
||||||
val schemaUrl = URL("classpath:net/woggioni/gbcs/gbcs.xsd")
|
val schemaUrl = URL.of(URI("classpath:net/woggioni/gbcs/gbcs.xsd"), null)
|
||||||
// val schemaUrl = GradleBuildCacheServer::class.java.classLoader.getResource(schemaResource)
|
|
||||||
// ?: throw IllegalStateException("Missing configuration schema '$schemaResource'")
|
|
||||||
val schemaUrl2 = URL(schemaUrl.toString())
|
|
||||||
val dbf = Xml.newDocumentBuilderFactory()
|
val dbf = Xml.newDocumentBuilderFactory()
|
||||||
|
// dbf.schema = Xml.getSchema(this::class.java.module.getResourceAsStream("/net/woggioni/gbcs/gbcs.xsd"))
|
||||||
dbf.schema = Xml.getSchema(schemaUrl)
|
dbf.schema = Xml.getSchema(schemaUrl)
|
||||||
val doc = Files.newInputStream(configurationFile)
|
val db = dbf.newDocumentBuilder().apply {
|
||||||
.use(dbf.newDocumentBuilder()::parse)
|
setErrorHandler(Xml.ErrorHandler(schemaUrl))
|
||||||
GradleBuildCacheServer(Configuration.parse(doc.documentElement)).run()
|
}
|
||||||
|
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,
|
fun digest(data : ByteArray,
|
||||||
@@ -392,4 +461,14 @@ class GradleBuildCacheServer(private val cfg : Configuration) {
|
|||||||
return JWO.bytesToHex(digest(data, md))
|
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.nio.file.Path
|
||||||
import java.util.logging.LogManager
|
import java.util.logging.LogManager
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
inline fun <reified T> T.contextLogger() = LoggerFactory.getLogger(T::class.java)
|
inline fun <reified T> T.contextLogger() = LoggerFactory.getLogger(T::class.java)
|
||||||
|
|
||||||
inline fun Logger.traceParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
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
|
package net.woggioni.gbcs
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.InputStreamReader
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
|
import javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
|
||||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA
|
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.XMLConstants.W3C_XML_SCHEMA_NS_URI
|
||||||
import javax.xml.parsers.DocumentBuilder
|
import javax.xml.parsers.DocumentBuilder
|
||||||
import javax.xml.parsers.DocumentBuilderFactory
|
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.Schema
|
||||||
import javax.xml.validation.SchemaFactory
|
import javax.xml.validation.SchemaFactory
|
||||||
|
import net.woggioni.jwo.xml.Xml
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
import org.w3c.dom.NodeList
|
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.SAXNotRecognizedException
|
||||||
import org.xml.sax.SAXNotSupportedException
|
import org.xml.sax.SAXNotSupportedException
|
||||||
import org.xml.sax.SAXParseException
|
import org.xml.sax.SAXParseException
|
||||||
@@ -66,10 +71,10 @@ class ElementIterator(parent: Element, name: String? = null) : Iterator<Element>
|
|||||||
|
|
||||||
object Xml {
|
object Xml {
|
||||||
|
|
||||||
private class XmlErrorHandler(private val fileURL: URL) : ErrorHandler {
|
class ErrorHandler(private val fileURL: URL) : ErrHandler {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(XmlErrorHandler::class.java)
|
private val log = LoggerFactory.getLogger(ErrorHandler::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun warning(ex: SAXParseException) {
|
override fun warning(ex: SAXParseException) {
|
||||||
@@ -118,9 +123,18 @@ object Xml {
|
|||||||
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||||
// disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
// disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
||||||
// disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
// disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
||||||
|
sf.errorHandler = ErrorHandler(schema)
|
||||||
return sf.newSchema(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 {
|
fun newDocumentBuilderFactory(): DocumentBuilderFactory {
|
||||||
val dbf = DocumentBuilderFactory.newInstance()
|
val dbf = DocumentBuilderFactory.newInstance()
|
||||||
dbf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
dbf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||||
@@ -129,6 +143,7 @@ object Xml {
|
|||||||
dbf.isExpandEntityReferences = false
|
dbf.isExpandEntityReferences = false
|
||||||
dbf.isIgnoringComments = true
|
dbf.isIgnoringComments = true
|
||||||
dbf.isNamespaceAware = true
|
dbf.isNamespaceAware = true
|
||||||
|
dbf.isValidating = false
|
||||||
return dbf
|
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
|
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.filter =
|
||||||
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
|
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 =
|
java.util.logging.ConsoleHandler.encoding =
|
@@ -1,14 +1,48 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?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"/>
|
<bind host="127.0.0.1" port="11443"/>
|
||||||
<cache path="/tmp/gbcs"/>
|
<cache path="/tmp/gbcs"/>
|
||||||
<groups>
|
<authorization>
|
||||||
<readers>
|
<users>
|
||||||
<!-- <user name="reader"/>-->
|
<user name="user1" password="password"/>
|
||||||
</readers>
|
<user name="user2" password="password"/>
|
||||||
<writers>
|
<user name="user3" password="password"/>
|
||||||
<!-- <user name="writer"/>-->
|
<user name="user4" password="password"/>
|
||||||
</writers>
|
<user name="user5" password="password">
|
||||||
</groups>
|
<roles>
|
||||||
<tls verify-clients="true"/>
|
<reader/>
|
||||||
</server>
|
<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"?>
|
<?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: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:element name="server" type="gbcs:serverType"/>
|
||||||
|
|
||||||
<xs:complexType name="serverType">
|
<xs:complexType name="serverType">
|
||||||
<xs:sequence minOccurs="0">
|
<xs:sequence minOccurs="0">
|
||||||
<xs:element name="bind" type="gbcs:bindType"/>
|
<xs:element name="bind" type="gbcs:bindType" maxOccurs="1"/>
|
||||||
<xs:element name="cache" type="gbcs:cacheDirType"/>
|
<xs:element name="cache" type="gbcs:cacheDirType" maxOccurs="1"/>
|
||||||
<xs:element name="groups" type="gbcs:groupsType"/>
|
<xs:element name="authorization" type="gbcs:authorizationType" maxOccurs="1">
|
||||||
<xs:element name="tls" type="gbcs:tlsType"/>
|
<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:sequence>
|
||||||
<xs:attribute name="path" type="xs:string"/>
|
<xs:attribute name="path" type="xs:string"/>
|
||||||
|
<xs:attribute name="useVirtualThreads" type="xs:boolean" use="optional"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="bindType">
|
<xs:complexType name="bindType">
|
||||||
@@ -23,26 +34,92 @@
|
|||||||
<xs:attribute name="path" type="xs:string" use="required"/>
|
<xs:attribute name="path" type="xs:string" use="required"/>
|
||||||
</xs:complexType>
|
</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:all>
|
||||||
<xs:element name="readers" type="gbcs:groupType"/>
|
<xs:element name="users" type="gbcs:usersType"/>
|
||||||
<xs:element name="writers" type="gbcs:groupType"/>
|
<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:all>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="groupType">
|
<xs:complexType name="usersType">
|
||||||
<xs:sequence>
|
<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:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="userType">
|
<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="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>
|
||||||
|
|
||||||
<xs:complexType name="tlsType">
|
<xs:complexType name="tlsType">
|
||||||
<xs:all>
|
<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:element name="truststore" type="gbcs:trustStoreType" minOccurs="0"/>
|
||||||
</xs:all>
|
</xs:all>
|
||||||
<xs:attribute name="verify-clients" type="xs:boolean" use="optional"/>
|
<xs:attribute name="verify-clients" type="xs:boolean" use="optional"/>
|
||||||
@@ -58,6 +135,7 @@
|
|||||||
<xs:complexType name="trustStoreType">
|
<xs:complexType name="trustStoreType">
|
||||||
<xs:attribute name="file" type="xs:string" use="required"/>
|
<xs:attribute name="file" type="xs:string" use="required"/>
|
||||||
<xs:attribute name="password" type="xs:string"/>
|
<xs:attribute name="password" type="xs:string"/>
|
||||||
|
<xs:attribute name="check-certificate-status" type="xs:boolean"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="propertiesType">
|
<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