Add optional OpenTelemetry Netty server instrumentation

- Update lys.version to 2026.04.14

- Add optional compileOnly dependency on opentelemetry-netty-4.1 in rbcs-server

- Add runtime guard to only activate instrumentation when OTel classes are on classpath

- Insert OTel combined handler after HttpServerCodec in the Netty pipeline

- Add requires-static JPMS directives for optional module support

- Add enableTelemetry config attribute to rbcs:server with default false

- Update Configuration DTO, XSD schema, Parser, Serializer, and all tests
This commit is contained in:
OpenCode
2026-04-28 14:59:08 +00:00
parent 70eccf83a8
commit ee7bc7e850
198 changed files with 11040 additions and 4 deletions
+21
View File
@@ -0,0 +1,21 @@
<?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.err</target>
<encoder class="PatternLayoutEncoder">
<pattern>%d [%highlight(%-5level)] \(%thread\) %logger{36} -%kvp- %msg %n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console"/>
</root>
<logger name="io.netty" level="debug"/>
<logger name="com.google.code.yanf4j" level="warn"/>
<logger name="net.rubyeye.xmemcached" level="warn"/>
</configuration>
@@ -0,0 +1,89 @@
package net.woggioni.rbcs.server.test
import java.net.URI
import java.net.http.HttpRequest
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import java.time.Duration
import java.time.temporal.ChronoUnit
import java.util.Base64
import java.util.zip.Deflater
import kotlin.random.Random
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.api.Role
import net.woggioni.rbcs.common.RBCS.getFreePort
import net.woggioni.rbcs.common.Xml
import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration
import net.woggioni.rbcs.server.configuration.Serializer
abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
private lateinit var cacheDir : Path
protected val random = Random(101325)
protected val keyValuePair = newEntry(random)
protected val serverPath = "rbcs"
protected val readersGroup = Configuration.Group("readers", setOf(Role.Reader), null, null)
protected val writersGroup = Configuration.Group("writers", setOf(Role.Writer), null, null)
abstract protected val users : List<Configuration.User>
override fun setUp() {
this.cacheDir = testDir.resolve("cache")
cfg = Configuration.of(
"127.0.0.1",
getFreePort(),
false,
emptyList(),
50,
serverPath,
Configuration.EventExecutor(false),
Configuration.RateLimiter(true, 0x100000, 50),
Configuration.Connection(
Duration.of(60, ChronoUnit.SECONDS),
Duration.of(30, ChronoUnit.SECONDS),
Duration.of(30, ChronoUnit.SECONDS),
0x1000,
0x10000
),
users.asSequence().map { it.name to it}.toMap(),
sequenceOf(writersGroup, readersGroup).map { it.name to it}.toMap(),
FileSystemCacheConfiguration(
this.cacheDir,
maxAge = Duration.ofSeconds(3600 * 24),
digestAlgorithm = "MD5",
compressionLevel = Deflater.DEFAULT_COMPRESSION,
compressionEnabled = false
),
Configuration.BasicAuthentication(),
null,
)
Xml.write(Serializer.serialize(cfg), System.out)
}
override fun tearDown() {
}
protected fun buildAuthorizationHeader(user : Configuration.User, password : String) : String {
val b64 = Base64.getEncoder().encode("${user.name}:${password}".toByteArray(Charsets.UTF_8)).let{
String(it, StandardCharsets.UTF_8)
}
return "Basic $b64"
}
protected fun newRequestBuilder(key : String) = HttpRequest.newBuilder()
.uri(URI.create("http://${cfg.host}:${cfg.port}/$serverPath/$key"))
protected fun newEntry(random : Random) : Pair<String, ByteArray> {
val key = ByteArray(0x10).let {
random.nextBytes(it)
Base64.getUrlEncoder().encodeToString(it)
}
val value = ByteArray(0x1000).also {
random.nextBytes(it)
}
return key to value
}
}
@@ -0,0 +1,51 @@
package net.woggioni.rbcs.server.test
import java.nio.file.Path
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.server.RemoteBuildCacheServer
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.MethodOrderer
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.TestMethodOrder
import org.junit.jupiter.api.io.TempDir
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
abstract class AbstractServerTest {
protected lateinit var cfg : Configuration
protected lateinit var testDir : Path
private var serverHandle : RemoteBuildCacheServer.ServerHandle? = null
@BeforeAll
fun setUp0(@TempDir tmpDir : Path) {
this.testDir = tmpDir
setUp()
startServer(cfg)
}
@AfterAll
fun tearDown0() {
tearDown()
stopServer()
}
abstract fun setUp()
abstract fun tearDown()
private fun startServer(cfg : Configuration) {
this.serverHandle = RemoteBuildCacheServer(cfg).run()
}
private fun stopServer() {
this.serverHandle?.let {
it.sendShutdownSignal()
it.get()
}
}
}
@@ -0,0 +1,206 @@
package net.woggioni.rbcs.server.test
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.security.KeyStore
import java.security.KeyStore.PasswordProtection
import java.time.Duration
import java.time.temporal.ChronoUnit
import java.util.Base64
import java.util.zip.Deflater
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import kotlin.random.Random
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.api.Role
import net.woggioni.rbcs.common.RBCS.getFreePort
import net.woggioni.rbcs.common.Xml
import net.woggioni.rbcs.server.cache.FileSystemCacheConfiguration
import net.woggioni.rbcs.server.configuration.Serializer
import net.woggioni.rbcs.server.test.utils.CertificateUtils
import net.woggioni.rbcs.server.test.utils.CertificateUtils.X509Credentials
import org.bouncycastle.asn1.x500.X500Name
abstract class AbstractTlsServerTest : AbstractServerTest() {
companion object {
private const val CA_CERTIFICATE_ENTRY = "rbcs-ca"
private const val CLIENT_CERTIFICATE_ENTRY = "rbcs-client"
private const val SERVER_CERTIFICATE_ENTRY = "rbcs-server"
private const val PASSWORD = "password"
}
private lateinit var cacheDir: Path
private lateinit var serverKeyStoreFile: Path
private lateinit var clientKeyStoreFile: Path
private lateinit var trustStoreFile: Path
private lateinit var serverKeyStore: KeyStore
private lateinit var clientKeyStore: KeyStore
private lateinit var trustStore: KeyStore
protected lateinit var ca: X509Credentials
protected val readersGroup = Configuration.Group("readers", setOf(Role.Reader), null, null)
protected val writersGroup = Configuration.Group("writers", setOf(Role.Writer), null, null)
protected val healthCheckGroup = Configuration.Group("healthcheckers", setOf(Role.Healthcheck), null, null)
protected val random = Random(101325)
protected val keyValuePair = newEntry(random)
private val serverPath : String? = null
protected abstract val users : List<Configuration.User>
protected fun createKeyStoreAndTrustStore() {
ca = CertificateUtils.createCertificateAuthority(CA_CERTIFICATE_ENTRY, 30)
val serverCert = CertificateUtils.createServerCertificate(ca, X500Name("CN=$SERVER_CERTIFICATE_ENTRY"), 30)
val clientCert = CertificateUtils.createClientCertificate(ca, X500Name("CN=$CLIENT_CERTIFICATE_ENTRY"), 30)
serverKeyStore = KeyStore.getInstance("PKCS12").apply {
load(null, null)
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
setEntry(
SERVER_CERTIFICATE_ENTRY,
KeyStore.PrivateKeyEntry(
serverCert.keyPair().private,
arrayOf(serverCert.certificate(), ca.certificate)
),
PasswordProtection(PASSWORD.toCharArray())
)
}
Files.newOutputStream(this.serverKeyStoreFile).use {
serverKeyStore.store(it, null)
}
clientKeyStore = KeyStore.getInstance("PKCS12").apply {
load(null, null)
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
setEntry(
CLIENT_CERTIFICATE_ENTRY,
KeyStore.PrivateKeyEntry(
clientCert.keyPair().private,
arrayOf(clientCert.certificate(), ca.certificate)
),
PasswordProtection(PASSWORD.toCharArray())
)
}
Files.newOutputStream(this.clientKeyStoreFile).use {
clientKeyStore.store(it, null)
}
trustStore = KeyStore.getInstance("PKCS12").apply {
load(null, null)
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
}
Files.newOutputStream(this.trustStoreFile).use {
trustStore.store(it, null)
}
}
protected fun getClientKeyStore(ca: X509Credentials, subject: X500Name) = KeyStore.getInstance("PKCS12").apply {
val clientCert = CertificateUtils.createClientCertificate(ca, subject, 30)
load(null, null)
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
setEntry(
CLIENT_CERTIFICATE_ENTRY,
KeyStore.PrivateKeyEntry(clientCert.keyPair().private, arrayOf(clientCert.certificate(), ca.certificate)),
PasswordProtection(PASSWORD.toCharArray())
)
}
protected fun getHttpClient(clientKeyStore: KeyStore?): HttpClient {
val kmf = clientKeyStore?.let {
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply {
init(it, PASSWORD.toCharArray())
}
}
// Set up trust manager factory with the truststore
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
tmf.init(trustStore)
// Create SSL context with the key and trust managers
val sslContext = SSLContext.getInstance("TLS").apply {
init(kmf?.keyManagers ?: emptyArray(), tmf.trustManagers, null)
}
return HttpClient.newBuilder().sslContext(sslContext).build()
}
override fun setUp() {
this.clientKeyStoreFile = testDir.resolve("client-keystore.p12")
this.serverKeyStoreFile = testDir.resolve("server-keystore.p12")
this.trustStoreFile = testDir.resolve("truststore.p12")
this.cacheDir = testDir.resolve("cache")
createKeyStoreAndTrustStore()
cfg = Configuration(
"127.0.0.1",
getFreePort(),
false,
emptyList(),
100,
serverPath,
Configuration.EventExecutor(false),
Configuration.RateLimiter(true, 0x100000, 50),
Configuration.Connection(
Duration.of(60, ChronoUnit.SECONDS),
Duration.of(30, ChronoUnit.SECONDS),
Duration.of(30, ChronoUnit.SECONDS),
0x1000,
0x10000
),
users.asSequence().map { it.name to it }.toMap(),
sequenceOf(writersGroup, readersGroup).map { it.name to it }.toMap(),
FileSystemCacheConfiguration(this.cacheDir,
maxAge = Duration.ofSeconds(3600 * 24),
compressionEnabled = false,
compressionLevel = Deflater.DEFAULT_COMPRESSION,
digestAlgorithm = "MD5",
),
// InMemoryCacheConfiguration(
// maxAge = Duration.ofSeconds(3600 * 24),
// compressionEnabled = true,
// compressionLevel = Deflater.DEFAULT_COMPRESSION,
// digestAlgorithm = "MD5"
// ),
Configuration.ClientCertificateAuthentication(
Configuration.TlsCertificateExtractor("CN", "(.*)"),
null
),
Configuration.Tls(
Configuration.KeyStore(this.serverKeyStoreFile, null, SERVER_CERTIFICATE_ENTRY, PASSWORD),
Configuration.TrustStore(this.trustStoreFile, null, false, false),
)
)
Xml.write(Serializer.serialize(cfg), System.out)
}
override fun tearDown() {
}
protected fun newRequestBuilder(key: String) = HttpRequest.newBuilder()
.uri(URI.create("https://${cfg.host}:${cfg.port}/${serverPath ?: ""}/$key"))
private fun buildAuthorizationHeader(user: Configuration.User, password: String): String {
val b64 = Base64.getEncoder().encode("${user.name}:${password}".toByteArray(Charsets.UTF_8)).let {
String(it, StandardCharsets.UTF_8)
}
return "Basic $b64"
}
protected fun newEntry(random: Random): Pair<String, ByteArray> {
val key = ByteArray(0x10).let {
random.nextBytes(it)
Base64.getUrlEncoder().encodeToString(it)
}
val value = ByteArray(0x1000).also {
random.nextBytes(it)
}
return key to value
}
}
@@ -0,0 +1,192 @@
package net.woggioni.rbcs.server.test
import io.netty.handler.codec.http.HttpResponseStatus
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
import java.time.temporal.ChronoUnit
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.api.Role
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
class BasicAuthServerTest : AbstractBasicAuthServerTest() {
companion object {
private const val PASSWORD = "password"
}
override val users = listOf(
Configuration.User("user1", hashPassword(PASSWORD), setOf(readersGroup), null),
Configuration.User("user2", hashPassword(PASSWORD), setOf(writersGroup), null),
Configuration.User("user3", hashPassword(PASSWORD), setOf(readersGroup, writersGroup), null),
Configuration.User("", null, setOf(readersGroup), null),
Configuration.User("user4", hashPassword(PASSWORD), setOf(readersGroup),
Configuration.Quota(1, Duration.of(1, ChronoUnit.DAYS), 0, 1)
),
Configuration.User("user5", hashPassword(PASSWORD), setOf(readersGroup),
Configuration.Quota(1, Duration.of(5, ChronoUnit.SECONDS), 0, 1)
)
)
@Test
@Order(1)
fun putWithNoAuthorizationHeader() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode())
}
@Test
@Order(2)
fun putAsAReaderUser() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val user = cfg.users.values.find {
Role.Reader in it.roles && Role.Writer !in it.roles
} ?: throw RuntimeException("Reader user not found")
val requestBuilder = newRequestBuilder(key)
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
.header("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode())
}
@Test
@Order(3)
fun getAsAWriterUser() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, _) = keyValuePair
val user = cfg.users.values.find {
Role.Writer in it.roles
} ?: throw RuntimeException("Reader user not found")
val requestBuilder = newRequestBuilder(key)
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
.GET()
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode())
}
@Test
@Order(4)
fun putAsAWriterUser() {
val client: HttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build()
val (key, value) = keyValuePair
val user = cfg.users.values.find {
Role.Writer in it.roles
} ?: throw RuntimeException("Reader user not found")
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.CREATED.code(), response.statusCode())
}
@Test
@Order(5)
fun getAsAReaderUser() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val user = cfg.users.values.find {
Role.Reader in it.roles
} ?: throw RuntimeException("Reader user not found")
val requestBuilder = newRequestBuilder(key)
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
.GET()
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
Assertions.assertArrayEquals(value, response.body())
}
@Test
@Order(6)
fun getAsAnonymousUser() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val requestBuilder = newRequestBuilder(key)
.GET()
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
Assertions.assertArrayEquals(value, response.body())
}
@Test
@Order(7)
fun getMissingKeyAsAReaderUser() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, _) = newEntry(random)
val user = cfg.users.values.find {
Role.Reader in it.roles
} ?: throw RuntimeException("Reader user not found")
val requestBuilder = newRequestBuilder(key)
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
.GET()
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode())
}
@Test
@Order(8)
fun getAsAThrottledUser() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val user = cfg.users.values.find {
it.name == "user4"
} ?: throw RuntimeException("user4 not found")
val requestBuilder = newRequestBuilder(key)
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
.GET()
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.TOO_MANY_REQUESTS.code(), response.statusCode())
}
@Test
@Order(9)
fun getAsAThrottledUser2() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val user = cfg.users.values.find {
it.name == "user5"
} ?: throw RuntimeException("user5 not found")
val requestBuilder = newRequestBuilder(key)
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
.GET()
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
Assertions.assertArrayEquals(value, response.body())
}
}
@@ -0,0 +1,58 @@
package net.woggioni.rbcs.server.test
import java.nio.file.Files
import java.nio.file.Path
import net.woggioni.rbcs.common.RBCS.toUrl
import net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory
import net.woggioni.rbcs.common.Xml
import net.woggioni.rbcs.server.configuration.Parser
import net.woggioni.rbcs.server.configuration.Serializer
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.io.TempDir
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import org.xml.sax.SAXParseException
class ConfigurationTest {
@ValueSource(
strings = [
"classpath:net/woggioni/rbcs/server/test/valid/rbcs-default.xml",
"classpath:net/woggioni/rbcs/server/test/valid/rbcs-memcached.xml",
"classpath:net/woggioni/rbcs/server/test/valid/rbcs-tls.xml",
"classpath:net/woggioni/rbcs/server/test/valid/rbcs-memcached-tls.xml",
"classpath:net/woggioni/rbcs/server/test/valid/rbcs-redis.xml",
"classpath:net/woggioni/rbcs/server/test/valid/rbcs-redis-tls.xml",
]
)
@ParameterizedTest
fun test(configurationUrl: String, @TempDir testDir: Path) {
RbcsUrlStreamHandlerFactory.install()
val doc = Xml.parseXml(configurationUrl.toUrl())
val cfg = Parser.parse(doc)
val configFile = testDir.resolve("rbcs.xml")
Files.newOutputStream(configFile).use {
Xml.write(Serializer.serialize(cfg), it)
}
Xml.write(Serializer.serialize(cfg), System.out)
val parsed = Parser.parse(Xml.parseXml(configFile.toUri().toURL()))
Assertions.assertEquals(cfg, parsed)
}
@ValueSource(
strings = [
"classpath:net/woggioni/rbcs/server/test/invalid/invalid-user-ref.xml",
"classpath:net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user.xml",
"classpath:net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user2.xml",
"classpath:net/woggioni/rbcs/server/test/invalid/multiple-user-quota.xml",
]
)
@ParameterizedTest
fun invalidConfigurationTest(configurationUrl: String) {
RbcsUrlStreamHandlerFactory.install()
Assertions.assertThrows(SAXParseException::class.java) {
Xml.parseXml(configurationUrl.toUrl())
}
}
}
@@ -0,0 +1,52 @@
package net.woggioni.rbcs.server.test
import io.netty.handler.codec.http.HttpResponseStatus
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.common.PasswordSecurity.hashPassword
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
class NoAnonymousUserBasicAuthServerTest : AbstractBasicAuthServerTest() {
companion object {
private const val PASSWORD = "anotherPassword"
}
override val users = listOf(
Configuration.User("user1", hashPassword(PASSWORD), setOf(readersGroup), null),
Configuration.User("user2", hashPassword(PASSWORD), setOf(writersGroup), null),
Configuration.User("user3", hashPassword(PASSWORD), setOf(readersGroup, writersGroup), null),
)
@Test
@Order(1)
fun putWithNoAuthorizationHeader() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
}
@Test
@Order(2)
fun getWithNoAuthorizationHeader() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val requestBuilder = newRequestBuilder(key)
.GET()
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
}
}
@@ -0,0 +1,47 @@
package net.woggioni.rbcs.server.test
import io.netty.handler.codec.http.HttpResponseStatus
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import net.woggioni.rbcs.api.Configuration
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
class NoAnonymousUserTlsServerTest : AbstractTlsServerTest() {
override val users = listOf(
Configuration.User("user1", null, setOf(readersGroup), null),
Configuration.User("user2", null, setOf(writersGroup), null),
Configuration.User("user3", null, setOf(readersGroup, writersGroup), null),
)
@Test
@Order(1)
fun getAsAnonymousUser() {
val (key, _) = keyValuePair
val client: HttpClient = getHttpClient(null)
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.GET()
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
}
@Test
@Order(2)
fun putAsAnonymousUser() {
val (key, value) = keyValuePair
val client: HttpClient = getHttpClient(null)
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
}
}
@@ -0,0 +1,186 @@
package net.woggioni.rbcs.server.test
import io.netty.handler.codec.http.HttpResponseStatus
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.nio.file.Path
import java.time.Duration
import java.time.temporal.ChronoUnit
import java.util.Base64
import java.util.zip.Deflater
import kotlin.random.Random
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.common.RBCS.getFreePort
import net.woggioni.rbcs.common.Xml
import net.woggioni.rbcs.server.cache.InMemoryCacheConfiguration
import net.woggioni.rbcs.server.configuration.Serializer
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
class NoAuthServerTest : AbstractServerTest() {
private lateinit var cacheDir: Path
private val random = Random(101325)
private val keyValuePair = newEntry(random)
private val serverPath = "/some/nested/path"
override fun setUp() {
this.cacheDir = testDir.resolve("cache")
cfg = Configuration(
"127.0.0.1",
getFreePort(),
false,
emptyList(),
100,
serverPath,
Configuration.EventExecutor(false),
Configuration.RateLimiter(true, 0x100000, 50),
Configuration.Connection(
Duration.of(60, ChronoUnit.SECONDS),
Duration.of(30, ChronoUnit.SECONDS),
Duration.of(30, ChronoUnit.SECONDS),
0x1000,
0x10000
),
emptyMap(),
emptyMap(),
InMemoryCacheConfiguration(
maxAge = Duration.ofSeconds(3600 * 24),
compressionEnabled = true,
digestAlgorithm = "MD5",
compressionLevel = Deflater.DEFAULT_COMPRESSION,
maxSize = 0x1000000,
),
null,
null,
)
Xml.write(Serializer.serialize(cfg), System.out)
}
override fun tearDown() {
}
fun newRequestBuilder(key: String) = HttpRequest.newBuilder()
.uri(URI.create("http://${cfg.host}:${cfg.port}/$serverPath/$key"))
fun newEntry(random: Random): Pair<String, ByteArray> {
val key = ByteArray(0x10).let {
random.nextBytes(it)
Base64.getUrlEncoder().encodeToString(it)
}
val value = ByteArray(0x1000).also {
random.nextBytes(it)
}
return key to value
}
@Test
@Order(1)
fun putWithNoAuthorizationHeader() {
val client: HttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build()
val (key, value) = keyValuePair
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.CREATED.code(), response.statusCode())
}
@Test
@Order(2)
fun getWithNoAuthorizationHeader() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val requestBuilder = newRequestBuilder(key)
.GET()
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
Assertions.assertArrayEquals(value, response.body())
}
@Test
@Order(3)
fun getMissingKey() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, _) = newEntry(random)
val requestBuilder = newRequestBuilder(key).GET()
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode())
}
@Test
@Order(4)
fun getUnhandledPath() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, _) = newEntry(random)
val requestBuilder = HttpRequest.newBuilder()
.uri(URI.create("http://${cfg.host}:${cfg.port}/some/other/path/$key"))
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.BAD_REQUEST.code(), response.statusCode())
}
@Test
@Order(5)
fun putUnhandledPath() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = newEntry(random)
val requestBuilder = HttpRequest.newBuilder()
.uri(URI.create("http://${cfg.host}:${cfg.port}/some/other/path/$key"))
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.BAD_REQUEST.code(), response.statusCode())
}
@Test
@Order(6)
fun getRelativeUnhandledPath() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, _) = newEntry(random)
val requestBuilder = HttpRequest.newBuilder()
.uri(URI.create("http://${cfg.host}:${cfg.port}/some/nested/path/../../../some/other/path/$key"))
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.BAD_REQUEST.code(), response.statusCode())
}
@Test
@Order(7)
fun getRelativePath() {
val client: HttpClient = HttpClient.newHttpClient()
val (key, value) = keyValuePair
val requestBuilder = HttpRequest.newBuilder()
.uri(URI.create("http://${cfg.host}:${cfg.port}/some/other/path/../../nested/path/$key"))
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
Assertions.assertArrayEquals(value, response.body())
}
@Test
@Order(10)
fun traceTest() {
val client: HttpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build()
val requestBuilder = newRequestBuilder("").method(
"TRACE",
HttpRequest.BodyPublishers.ofByteArray("sfgsdgfaiousfiuhsd".toByteArray())
)
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
println(String(response.body()))
}
}
@@ -0,0 +1,182 @@
package net.woggioni.rbcs.server.test
import io.netty.handler.codec.http.HttpResponseStatus
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.api.Role
import org.bouncycastle.asn1.x500.X500Name
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Order
import org.junit.jupiter.api.Test
class TlsServerTest : AbstractTlsServerTest() {
override val users = listOf(
Configuration.User("user1", null, setOf(readersGroup), null),
Configuration.User("user2", null, setOf(writersGroup), null),
Configuration.User("user3", null, setOf(readersGroup, writersGroup), null),
Configuration.User("user4", null, setOf(healthCheckGroup), null),
Configuration.User("", null, setOf(readersGroup), null)
)
@Test
@Order(1)
fun putAsAReaderUser() {
val (key, value) = keyValuePair
val user = cfg.users.values.find {
Role.Reader in it.roles && Role.Writer !in it.roles
} ?: throw RuntimeException("Reader user not found")
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode())
}
@Test
@Order(2)
fun getAsAWriterUser() {
val (key, _) = keyValuePair
val user = cfg.users.values.find {
Role.Writer in it.roles
} ?: throw RuntimeException("Reader user not found")
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
val requestBuilder = newRequestBuilder(key)
.GET()
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode())
}
@Test
@Order(3)
fun putAsAWriterUser() {
val (key, value) = keyValuePair
val user = cfg.users.values.find {
Role.Writer in it.roles
} ?: throw RuntimeException("Reader user not found")
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.CREATED.code(), response.statusCode())
}
@Test
@Order(4)
fun getAsAReaderUser() {
val (key, value) = keyValuePair
val user = cfg.users.values.find {
Role.Reader in it.roles
} ?: throw RuntimeException("Reader user not found")
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
val requestBuilder = newRequestBuilder(key)
.GET()
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
Assertions.assertArrayEquals(value, response.body())
}
@Test
@Order(5)
fun getMissingKeyAsAReaderUser() {
val (key, _) = newEntry(random)
val user = cfg.users.values.find {
Role.Reader in it.roles
} ?: throw RuntimeException("Reader user not found")
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
val requestBuilder = newRequestBuilder(key)
.GET()
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode())
}
@Test
@Order(6)
fun getAsAnonymousUser() {
val (key, value) = keyValuePair
val client: HttpClient = getHttpClient(null)
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.GET()
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
Assertions.assertArrayEquals(value, response.body())
}
@Test
@Order(7)
fun putAsAnonymousUser() {
val (key, value) = keyValuePair
val client: HttpClient = getHttpClient(null)
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode())
}
@Test
@Order(8)
fun traceAsAnonymousUser() {
val client: HttpClient = getHttpClient(null)
val requestBuilder = newRequestBuilder("").method(
"TRACE",
HttpRequest.BodyPublishers.ofByteArray("this is an healthcheck".toByteArray())
)
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode())
}
@Test
@Order(9)
fun traceAsHealthcheckUser() {
val user = cfg.users.values.find {
Role.Healthcheck in it.roles
} ?: throw RuntimeException("Reader user not found")
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
val requestBuilder = newRequestBuilder("").method(
"TRACE",
HttpRequest.BodyPublishers.ofByteArray("this is an healthcheck".toByteArray())
)
val response: HttpResponse<ByteArray> =
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
println(String(response.body()))
}
@Test
@Order(10)
fun putAsUnknownUserUser() {
val (key, value) = keyValuePair
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=Unknown user")))
val requestBuilder = newRequestBuilder(key)
.header("Content-Type", "application/octet-stream")
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
Assertions.assertEquals(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), response.statusCode())
}
}
@@ -0,0 +1,19 @@
package net.woggioni.rbcs.server.test
import javax.naming.ldap.LdapName
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
class X500NameTest {
@Test
fun test() {
val name =
"C=SG, L=Bugis, CN=woggioni@f6aa5663ef26, emailAddress=oggioni.walter@gmail.com, street=1 Fraser Street\\, Duo Residences #23-05, postalCode=189350, GN=Walter, SN=Oggioni, pseudonym=woggioni"
val ldapName = LdapName(name)
val value = ldapName.rdns.asSequence().find {
it.type == "CN"
}!!.value
Assertions.assertEquals("woggioni@f6aa5663ef26", value)
}
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
<bind host="127.0.0.1" port="11443"/>
<cache xs:type="rbcs:fileSystemCacheType" path="/tmp/rbcs" max-age="P7D"/>
<authorization>
<users>
<user name="user1" password="password1"/>
<user name="user2" password="password2"/>
<anonymous>
<quota calls="10" period="P3D"/>
</anonymous>
<anonymous>
<quota calls="15" period="P3D"/>
</anonymous>
</users>
</authorization>
</rbcs:server>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
<bind host="127.0.0.1" port="11443"/>
<cache xs:type="rbcs:fileSystemCacheType" path="/tmp/rbcs" max-age="P7D"/>
<authorization>
<users>
<user name="user1" password="password1"/>
<user name="user2" password="password2"/>
</users>
<groups>
<group name="group1">
<users>
<anonymous/>
<user ref="user1"/>
<anonymous/>
</users>
<roles>
<reader/>
</roles>
</group>
</groups>
</authorization>
</rbcs:server>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
<bind host="127.0.0.1" port="11443"/>
<cache xs:type="rbcs:fileSystemCacheType" path="/tmp/rbcs" max-age="P7D"/>
<authorization>
<users>
<user name="user1" password="password1"/>
<user name="user2" password="password2"/>
</users>
<groups>
<group name="readers">
<users>
<user ref="user1"/>
<user ref="user5"/>
</users>
<roles>
<reader/>
</roles>
</group>
</groups>
</authorization>
</rbcs:server>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
<bind host="127.0.0.1" port="11443"/>
<cache xs:type="rbcs:fileSystemCacheType" path="/tmp/rbcs" max-age="P7D"/>
<authorization>
<users>
<user name="user1" password="password1">
<quota calls="10" period="PT20S"/>
<quota calls="20" period="PT20S"/>
</user>
</users>
</authorization>
</rbcs:server>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="22" proxy-protocol="true">
<trusted-proxies>
<allow cidr="192.168.0.11/32"/>
<allow cidr="::1/128"/>
<allow cidr="fda7:9b54:5678::2f9/128"/>
</trusted-proxies>
</bind>
<connection
read-idle-timeout="PT10M"
write-idle-timeout="PT11M"
idle-timeout="PT30M"
max-request-size="101325"
chunk-size="0xa910"/>
<event-executor use-virtual-threads="false"/>
<rate-limiter delay-response="false" message-buffer-size="0x1234" max-queued-messages="13"/>
<cache xs:type="rbcs:fileSystemCacheType" path="/tmp/rbcs" max-age="P7D"/>
<authentication>
<none/>
</authentication>
</rbcs:server>
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xmlns:rbcs-memcache="urn:net.woggioni.rbcs.server.memcache"
xs:schemaLocation="urn:net.woggioni.rbcs.server.memcache jpms://net.woggioni.rbcs.server.memcache/net/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd"
>
<bind host="0.0.0.0" port="8443" incoming-connections-backlog-size="4096"/>
<connection
max-request-size="67108864"
idle-timeout="PT30S"
read-idle-timeout="PT60S"
write-idle-timeout="PT60S"
chunk-size="123"/>
<event-executor use-virtual-threads="true"/>
<rate-limiter delay-response="false" message-buffer-size="12000" max-queued-messages="53"/>
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" key-prefix="some-prefix-string">
<server host="memcached" port="11211"/>
</cache>
<authorization>
<users>
<user name="woggioni">
<quota calls="1000" period="PT1S"/>
</user>
<user name="gitea">
<quota calls="10" period="PT1S" initial-available-calls="100" max-available-calls="100"/>
</user>
<anonymous>
<quota calls="2" period="PT5S"/>
</anonymous>
</users>
<groups>
<group name="writers">
<users>
<user ref="woggioni"/>
<user ref="gitea"/>
</users>
<roles>
<reader/>
<writer/>
</roles>
</group>
</groups>
</authorization>
<authentication>
<client-certificate>
<user-extractor attribute-name="CN" pattern="(.*)"/>
</client-certificate>
</authentication>
<tls>
<keystore file="/home/luser/ssl/rbcs.woggioni.net.pfx" key-alias="rbcs.woggioni.net" password="KEYSTORE_PASSWOR" key-password="KEY_PASSWORD"/>
<truststore file="/home/luser/ssl/woggioni.net.pfx" check-certificate-status="false" password="TRUSTSTORE_PASSWORD"/>
</tls>
</rbcs:server>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xmlns:rbcs-memcache="urn:net.woggioni.rbcs.server.memcache"
xs:schemaLocation="urn:net.woggioni.rbcs.server.memcache jpms://net.woggioni.rbcs.server.memcache/net/woggioni/rbcs/server/memcache/schema/rbcs-memcache.xsd urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="50"/>
<connection
read-idle-timeout="PT10M"
write-idle-timeout="PT11M"
idle-timeout="PT30M"
max-request-size="101325"
chunk-size="456"/>
<event-executor use-virtual-threads="false"/>
<rate-limiter delay-response="true" message-buffer-size="65432" max-queued-messages="21"/>
<cache xs:type="rbcs-memcache:memcacheCacheType" max-age="P7D" key-prefix="some-prefix-string" digest="SHA-256" compression-mode="deflate" compression-level="7">
<server host="127.0.0.1" port="11211" max-connections="10" connection-timeout="PT20S"/>
</cache>
<authentication>
<none/>
</authentication>
</rbcs:server>
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xmlns:rbcs-redis="urn:net.woggioni.rbcs.server.redis"
xs:schemaLocation="urn:net.woggioni.rbcs.server.redis jpms://net.woggioni.rbcs.server.redis/net/woggioni/rbcs/server/redis/schema/rbcs-redis.xsd urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd"
>
<bind host="0.0.0.0" port="8443" incoming-connections-backlog-size="4096"/>
<connection
max-request-size="67108864"
idle-timeout="PT30S"
read-idle-timeout="PT60S"
write-idle-timeout="PT60S"
chunk-size="123"/>
<event-executor use-virtual-threads="true"/>
<rate-limiter delay-response="false" message-buffer-size="12000" max-queued-messages="53"/>
<cache xs:type="rbcs-redis:redisCacheType" max-age="P7D" key-prefix="some-prefix-string">
<server host="redis-server" port="6379" password="secret123"/>
</cache>
<authorization>
<users>
<user name="woggioni">
<quota calls="1000" period="PT1S"/>
</user>
<user name="gitea">
<quota calls="10" period="PT1S" initial-available-calls="100" max-available-calls="100"/>
</user>
<anonymous>
<quota calls="2" period="PT5S"/>
</anonymous>
</users>
<groups>
<group name="writers">
<users>
<user ref="woggioni"/>
<user ref="gitea"/>
</users>
<roles>
<reader/>
<writer/>
</roles>
</group>
</groups>
</authorization>
<authentication>
<client-certificate>
<user-extractor attribute-name="CN" pattern="(.*)"/>
</client-certificate>
</authentication>
<tls>
<keystore file="/home/luser/ssl/rbcs.woggioni.net.pfx" key-alias="rbcs.woggioni.net" password="KEYSTORE_PASSWOR" key-password="KEY_PASSWORD"/>
<truststore file="/home/luser/ssl/woggioni.net.pfx" check-certificate-status="false" password="TRUSTSTORE_PASSWORD"/>
</tls>
</rbcs:server>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xmlns:rbcs-redis="urn:net.woggioni.rbcs.server.redis"
xs:schemaLocation="urn:net.woggioni.rbcs.server.redis jpms://net.woggioni.rbcs.server.redis/net/woggioni/rbcs/server/redis/schema/rbcs-redis.xsd urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="50"/>
<connection
read-idle-timeout="PT10M"
write-idle-timeout="PT11M"
idle-timeout="PT30M"
max-request-size="101325"
chunk-size="456"/>
<event-executor use-virtual-threads="false"/>
<rate-limiter delay-response="true" message-buffer-size="65432" max-queued-messages="21"/>
<cache xs:type="rbcs-redis:redisCacheType" max-age="P7D" key-prefix="some-prefix-string" digest="SHA-256" compression-mode="deflate" compression-level="7">
<server host="127.0.0.1" port="6379" max-connections="10" connection-timeout="PT20S"/>
</cache>
<authentication>
<none/>
</authentication>
</rbcs:server>
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<rbcs:server xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rbcs="urn:net.woggioni.rbcs.server"
xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">
<bind host="127.0.0.1" port="11443" incoming-connections-backlog-size="180"/>
<connection
read-idle-timeout="PT10M"
write-idle-timeout="PT11M"
idle-timeout="PT30M"
max-request-size="4096"
chunk-size="0xa91f"/>
<event-executor use-virtual-threads="false"/>
<cache xs:type="rbcs:inMemoryCacheType" max-age="P7D"/>
<authorization>
<users>
<user name="user1" password="password1">
<quota calls="3600" period="PT1H"/>
</user>
<user name="user2" password="password2"/>
<user name="user3" password="password3"/>
<anonymous>
<quota calls="10" period="PT1M"/>
</anonymous>
</users>
<groups>
<group name="readers">
<users>
<user ref="user1"/>
<anonymous/>
</users>
<roles>
<reader/>
</roles>
<user-quota calls="30" period="PT1M"/>
<group-quota calls="10" period="PT1S"/>
</group>
<group name="writers">
<users>
<user ref="user2"/>
</users>
<roles>
<writer/>
</roles>
</group>
<group name="readers-writers">
<users>
<user ref="user3"/>
</users>
<roles>
<reader/>
<writer/>
</roles>
<group-quota calls="1000" period="P1D"/>
</group>
</groups>
</authorization>
<authentication>
<client-certificate>
<group-extractor pattern="group-pattern" attribute-name="O"/>
<user-extractor pattern="user-pattern" attribute-name="CN"/>
</client-certificate>
</authentication>
<tls>
<keystore file="keystore.pfx" key-alias="key1" password="password" key-password="key-password"/>
<truststore file="truststore.pfx" password="password" check-certificate-status="true" require-client-certificate="true"/>
</tls>
</rbcs:server>