From 1f93602102b09cfcbccc1f5746a1198b2c4600dd Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Wed, 26 Feb 2025 15:26:18 +0800 Subject: [PATCH] added healthcheck role improved documentation client configuration promoted to standalone class --- README.md | 148 +++++++++++- doc/client_configuration.md | 125 ++++++++++ doc/server_configuration.md | 4 +- .../main/java/net/woggioni/rbcs/api/Role.java | 2 +- rbcs-cli/native-image/resource-config.json | 2 +- .../rbcs/cli/RemoteBuildCacheServerCli.kt | 4 +- .../cli/impl/commands/BenchmarkCommand.kt | 3 +- .../rbcs/cli/impl/commands/ClientCommand.kt | 16 +- .../cli/impl/commands/HealthCheckCommand.kt | 3 +- .../net/woggioni/rbcs/client/Configuration.kt | 61 +++++ .../{Client.kt => RemoteBuildCacheClient.kt} | 72 ------ .../net/woggioni/rbcs/client/impl/Parser.kt | 36 ++- .../rbcs/client/schema/rbcs-client.xsd | 222 +++++++++++++++--- rbcs-server-memcache/README.md | 2 +- .../server/memcache/schema/rbcs-memcache.xsd | 2 +- .../rbcs/server/auth/UserAuthorizer.kt | 5 +- .../server/cache/FileSystemCacheProvider.kt | 2 +- .../server/cache/InMemoryCacheProvider.kt | 2 +- .../rbcs/server/configuration/Parser.kt | 1 + .../net/woggioni/rbcs/server/rbcs-default.xml | 2 +- .../schema/{rbcs.xsd => rbcs-server.xsd} | 11 +- .../rbcs/server/test/AbstractTlsServerTest.kt | 1 + .../rbcs/server/test/TlsServerTest.kt | 20 +- .../test/invalid/duplicate-anonymous-user.xml | 2 +- .../invalid/duplicate-anonymous-user2.xml | 2 +- .../server/test/invalid/invalid-user-ref.xml | 2 +- .../test/invalid/multiple-user-quota.xml | 2 +- .../rbcs/server/test/valid/rbcs-default.xml | 2 +- .../server/test/valid/rbcs-memcached-tls.xml | 2 +- .../rbcs/server/test/valid/rbcs-memcached.xml | 2 +- .../rbcs/server/test/valid/rbcs-tls.xml | 2 +- rbcs-servlet/README.md | 7 + 32 files changed, 605 insertions(+), 164 deletions(-) create mode 100644 doc/client_configuration.md create mode 100644 rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Configuration.kt rename rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/{Client.kt => RemoteBuildCacheClient.kt} (88%) rename rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/{rbcs.xsd => rbcs-server.xsd} (98%) diff --git a/README.md b/README.md index 028b8bf..ca31d26 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ and throttling. ### Downloading the jar file You can download the latest version from [this link](https://gitea.woggioni.net/woggioni/-/packages/maven/net.woggioni:rbcs-cli/) + Assuming you have Java 21 or later installed, you can launch the server directly with ```bash @@ -33,19 +34,30 @@ docker pull gitea.woggioni.net/woggioni/rbcs:latest By default it will start an HTTP server bound to localhost and listening on port 8080 with no authentication, writing data to the disk, that you can use for testing +### Using the native executable +If you are on a Linux X86_64 machine you can download the native executable +from [here](https://gitea.woggioni.net/woggioni/-/packages/maven/net.woggioni:rbcs-cli/). +It behaves the same as the jar file but it doesn't require a JVM and it has faster startup times. +becausue of GraalVm's [closed-world assumption](https://www.graalvm.org/latest/reference-manual/native-image/basics/#static-analysis), +the native executable does not supports plugins, so it comes with all plugins embedded into it. + ## Usage ### Configuration -The location of the `rbcs.xml` configuration file depends on the operating system, +The location of the `rbcs-server.xml` configuration file depends on the operating system, Alternatively it can be changed setting the `RBCS_CONFIGURATION_DIR` environmental variable or `net.woggioni.rbcs.conf.dir` Java system property -to the directory that contain the `rbcs.xml` file. +to the directory that contain the `rbcs-server.xml` file. The server configuration file follows the XML format and uses XML schema for validation -(you can find the schema for the main configuration file [here](https://gitea.woggioni.net/woggioni/rbcs/src/branch/master/rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/rbcs.xsd)). +(you can find the schema for the main configuration file [here](https://gitea.woggioni.net/woggioni/rbcs/src/branch/master/rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/rbcs-server.xsd)). The configuration values are enclosed inside XML attribute and support system property / environmental variable interpolation. As an example, you can configure RBCS to read the server port number from the `RBCS_SERVER_PORT` environmental variable -and the bind address from the `rbc.bind.address` JVM system property with. +and the bind address from the `rbc.bind.address` JVM system property with + +```xml + +``` Full documentation for all tags and attributes is available [here](doc/server_configuration.md). @@ -128,10 +140,136 @@ Read [Gradle documentation](https://docs.gradle.org/current/userguide/build_cach Alternatively you can set those properties in your `/pom.xml` - Read [here](https://maven.apache.org/extensions/maven-build-cache-extension/remote-cache.html) for more informations +## Authentication + +RBCS supports 2 authentication mechanisms: + +- HTTP basic authentication +- TLS certificate authentication + +### Configure HTTP basic authentication + +Add a `` element to the `` element in your `rbcs-server.xml` +```xml + + + +``` + +### Configure TLS certificate authentication + +Add a `` element to the `` element in your `rbcs-server.xml` +```xml + + + + + + +``` +The `` here determines how the username is extracted from the +subject's X.500 name in the TLS certificate presented by the client, where `attribute-name` +is the `RelativeDistinguishedName` (RDN) identifier and pattern is a regular expression +that will be applied to extract the username from the first group present in the regex. +An error will be thrown if the regular expression contains no groups, while additional +groups are ignored. + +Similarly, the `` here determines how the group name is extracted from the +subject's X.500 name in the TLS certificate presented by the client. +Note that this allows to assign roles to incoming requests without necessarily assigning them +a username. + + + +## Access control + +RBCS supports role-based access control (RBAC), three roles are available: +- `Reader` can perform `GET` calls +- `Writer` can perform `PUT` calls +- `Healthcheck` can perform `TRACE` calls + +Roles are assigned to groups so that a user will have a role only if that roles belongs +to one of the groups he is a member of. + +There is also a special `` user +which matches any request who hasn't been authenticated and that can be assigned +to any group like a normal user. This permits to have a build cache that is +publicly readable but only writable by authenticated users (e.g. CI/CD pipeline). + +### Defining users + +Users can be defined in the `` element +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +The `password` attribute is only used for HTTP Basic authentication, so it can be omitted +if you use TLS certificate authentication. It must contain a password hash that can be derived from +the actual password using the following command + +```bash +java -jar rbcs-cli.jar password +``` + +## Reliability + +RBCS implements the [TRACE](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/TRACE) HTTP method and this functionality can be used +as a health check (mind you need to have `Healthcheck` role in order to perform it and match the server's `prefix` in the URL). + +## RBCS Client + +RBCS ships with a command line client that can be used for testing, benchmarking or to manually +upload/download files to the cache. It must be configured with the `rbcs-client.xml`, +whose location follows the same logic of the `rbcs-server.xml` + +### GET command + +```bash +java -jar rbcs-cli.jar client -p $CLIENT_PROFILE_NAME get -k $CACHE_KEY -v $FILE_WHERE_THE_VALUE_WILL_BE_STORED +``` + +### PUT command + +```bash +java -jar rbcs-cli.jar client -p $CLIENT_PROFILE_NAME put -k $CACHE_KEY -v $FILE_TO_BE_UPLOADED +``` +## Logging + +RBCS uses [logback](https://logback.qos.ch/) and ships with a [default logging configuration](./conf/logback.xml) that +can be overridden with `-Dlogback.configurationFile=path/to/custom/configuration.xml`, refer to +[Logback documentation](https://logback.qos.ch/manual/configuration.html) for more details about +how to configure Logback + ## FAQ ### Why should I use a build cache? diff --git a/doc/client_configuration.md b/doc/client_configuration.md new file mode 100644 index 0000000..1890159 --- /dev/null +++ b/doc/client_configuration.md @@ -0,0 +1,125 @@ +# XML Schema Documentation: RBCS Client Configuration + +This document provides detailed information about the XML schema for RBCS client configuration, which defines profiles for connecting to RBCS servers. + +## Root Element + +### `profiles` +The root element that contains a collection of server profiles. +- **Type**: `profilesType` +- **Contains**: Zero or more `profile` elements + +## Complex Types + +### `profilesType` +Defines the structure for the profiles collection. +- **Elements**: + - `profile`: Server connection profile (0 to unbounded) + +### `profileType` +Defines a server connection profile with authentication, connection settings, and retry policies. + +- **Attributes**: + - `name` (required): Name of the server profile, referenced with the '-p' parameter in rbcs-cli + - `base-url` (required): RBCs server URL + - `max-connections`: Maximum number of concurrent TCP connections (default: 50) + - `connection-timeout`: Timeout for establishing connections + - `enable-compression`: Whether to enable HTTP compression (default: true) + +- **Elements** (in sequence): + - **Authentication** (choice of one): + - `no-auth`: Disable authentication + - `basic-auth`: Enable HTTP basic authentication + - `tls-client-auth`: Enable TLS certificate authentication + - `connection` (optional): Connection timeout settings + - `retry-policy` (optional): Retry policy for failed requests + - `tls-trust-store` (optional): Custom truststore for server certificate validation + +### `connectionType` +Defines connection timeout settings. + +- **Attributes**: + - `idle-timeout`: Close connection after inactivity period (default: PT30S - 30 seconds) + - `read-idle-timeout`: Close connection when no read occurs (default: PT60S - 60 seconds) + - `write-idle-timeout`: Close connection when no write occurs (default: PT60S - 60 seconds) + +### `noAuthType` +Indicates no authentication should be used. +- No attributes or elements + +### `basicAuthType` +Configures HTTP Basic Authentication. + +- **Attributes**: + - `user` (required): Username for authentication + - `password` (required): Password for authentication + +### `tlsClientAuthType` +Configures TLS client certificate authentication. + +- **Attributes**: + - `key-store-file` (required): Path to the keystore file + - `key-store-password` (required): Password to open the keystore + - `key-alias` (required): Alias of the keystore entry with the private key + - `key-password` (optional): Private key entry's encryption password + +### `retryType` +Defines retry policy using exponential backoff. + +- **Attributes**: + - `max-attempts` (required): Maximum number of retry attempts + - `initial-delay`: Delay before first retry (default: PT1S - 1 second) + - `exp`: Exponent for computing next delay (default: 2.0) + +### `trustStoreType` +Configures custom truststore for server certificate validation. + +- **Attributes**: + - `file` (required): Path to the truststore file + - `password`: Truststore file password + - `check-certificate-status`: Whether to check certificate validity using CRL/OCSP + - `verify-server-certificate`: Whether to validate server certificates (default: true) + +## Sample XML Document + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +This sample XML document demonstrates three different profiles with various authentication methods and configuration options as defined in the schema. \ No newline at end of file diff --git a/doc/server_configuration.md b/doc/server_configuration.md index 006575e..cd4166d 100644 --- a/doc/server_configuration.md +++ b/doc/server_configuration.md @@ -1,4 +1,3 @@ - ### RBCS server configuration file elements and attributes #### Root Element: `server` @@ -109,6 +108,7 @@ Configures TLS encryption. - `password`: Keystore password - `key-alias` (required): Private key alias - `key-password`: Private key password + - ``: Client certificate verification **Attributes:** @@ -126,7 +126,7 @@ Configures TLS encryption. () val app = Application.builder("rbcs") .configurationDirectoryEnvVar("RBCS_CONFIGURATION_DIR") .configurationDirectoryPropertyKey("net.woggioni.rbcs.conf.dir") diff --git a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/BenchmarkCommand.kt b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/BenchmarkCommand.kt index bb54f6d..b447497 100644 --- a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/BenchmarkCommand.kt +++ b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/BenchmarkCommand.kt @@ -5,6 +5,7 @@ import net.woggioni.jwo.LongMath import net.woggioni.rbcs.api.CacheValueMetadata import net.woggioni.rbcs.cli.impl.RbcsCommand import net.woggioni.rbcs.cli.impl.converters.ByteSizeConverter +import net.woggioni.rbcs.client.Configuration import net.woggioni.rbcs.client.RemoteBuildCacheClient import net.woggioni.rbcs.common.createLogger import net.woggioni.rbcs.common.debug @@ -29,7 +30,7 @@ class BenchmarkCommand : RbcsCommand() { companion object { private val log = createLogger() - fun run(profile : RemoteBuildCacheClient.Configuration.Profile, + fun run(profile : Configuration.Profile, numberOfEntries : Int, entrySize : Int, useRandomValue : Boolean, diff --git a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/ClientCommand.kt b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/ClientCommand.kt index 4f1342c..c5589c1 100644 --- a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/ClientCommand.kt +++ b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/ClientCommand.kt @@ -2,8 +2,11 @@ package net.woggioni.rbcs.cli.impl.commands import net.woggioni.jwo.Application import net.woggioni.rbcs.cli.impl.RbcsCommand -import net.woggioni.rbcs.client.RemoteBuildCacheClient +import net.woggioni.rbcs.client.Configuration +import net.woggioni.rbcs.common.createLogger +import net.woggioni.rbcs.common.debug import picocli.CommandLine +import java.lang.IllegalArgumentException import java.nio.file.Path @CommandLine.Command( @@ -24,15 +27,20 @@ class ClientCommand(app : Application) : RbcsCommand() { names = ["-p", "--profile"], description = ["Name of the client profile to be used"], paramLabel = "PROFILE", - required = true + required = false ) var profileName : String? = null + get() = field ?: throw IllegalArgumentException("A profile name must be specified using the '-p' command line parameter") - val configuration : RemoteBuildCacheClient.Configuration by lazy { - RemoteBuildCacheClient.Configuration.parse(configurationFile) + val configuration : Configuration by lazy { + Configuration.parse(configurationFile) } override fun run() { + val log = createLogger() + log.debug { + "Using configuration file '$configurationFile'" + } println("Available profiles:") configuration.profiles.forEach { (profileName, _) -> println(profileName) diff --git a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/HealthCheckCommand.kt b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/HealthCheckCommand.kt index 4b224f9..2177c31 100644 --- a/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/HealthCheckCommand.kt +++ b/rbcs-cli/src/main/kotlin/net/woggioni/rbcs/cli/impl/commands/HealthCheckCommand.kt @@ -1,6 +1,7 @@ package net.woggioni.rbcs.cli.impl.commands import net.woggioni.rbcs.cli.impl.RbcsCommand +import net.woggioni.rbcs.client.Configuration import net.woggioni.rbcs.client.RemoteBuildCacheClient import net.woggioni.rbcs.common.createLogger import picocli.CommandLine @@ -16,7 +17,7 @@ class HealthCheckCommand : RbcsCommand() { companion object{ private val log = createLogger() - fun run(profile : RemoteBuildCacheClient.Configuration.Profile) { + fun run(profile : Configuration.Profile) { RemoteBuildCacheClient(profile).use { client -> val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong()) val nonce = ByteArray(0xa0) diff --git a/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Configuration.kt b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Configuration.kt new file mode 100644 index 0000000..d471346 --- /dev/null +++ b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Configuration.kt @@ -0,0 +1,61 @@ +package net.woggioni.rbcs.client + +import net.woggioni.rbcs.client.impl.Parser +import net.woggioni.rbcs.common.Xml +import java.net.URI +import java.nio.file.Files +import java.nio.file.Path +import java.security.PrivateKey +import java.security.cert.X509Certificate +import java.time.Duration + +data class Configuration( + val profiles: Map +) { + sealed class Authentication { + data class TlsClientAuthenticationCredentials( + val key: PrivateKey, + val certificateChain: Array + ) : Authentication() + + data class BasicAuthenticationCredentials(val username: String, val password: String) : Authentication() + } + + class TrustStore ( + var file: Path?, + var password: String?, + var checkCertificateStatus: Boolean = false, + var verifyServerCertificate: Boolean = true, + ) + + class RetryPolicy( + val maxAttempts: Int, + val initialDelayMillis: Long, + val exp: Double + ) + + class Connection( + val readIdleTimeout: Duration, + val writeIdleTimeout: Duration, + val idleTimeout: Duration, + ) + + data class Profile( + val serverURI: URI, + val connection: Connection?, + val authentication: Authentication?, + val connectionTimeout: Duration?, + val maxConnections: Int, + val compressionEnabled: Boolean, + val retryPolicy: RetryPolicy?, + val tlsTruststore : TrustStore? + ) + + companion object { + fun parse(path: Path): Configuration { + return Files.newInputStream(path).use { + Xml.parseXml(path.toUri().toURL(), it) + }.let(Parser::parse) + } + } +} \ No newline at end of file diff --git a/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Client.kt b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/RemoteBuildCacheClient.kt similarity index 88% rename from rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Client.kt rename to rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/RemoteBuildCacheClient.kt index 521ace1..06fb5c1 100644 --- a/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/Client.kt +++ b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/RemoteBuildCacheClient.kt @@ -36,20 +36,14 @@ import io.netty.handler.timeout.IdleStateHandler import io.netty.util.concurrent.Future import io.netty.util.concurrent.GenericFutureListener import net.woggioni.rbcs.api.CacheValueMetadata -import net.woggioni.rbcs.client.impl.Parser import net.woggioni.rbcs.common.RBCS.loadKeystore -import net.woggioni.rbcs.common.Xml import net.woggioni.rbcs.common.createLogger import net.woggioni.rbcs.common.debug import net.woggioni.rbcs.common.trace import java.io.IOException import java.net.InetSocketAddress import java.net.URI -import java.nio.file.Files -import java.nio.file.Path -import java.security.PrivateKey import java.security.cert.X509Certificate -import java.time.Duration import java.util.Base64 import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit @@ -69,59 +63,6 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC private val sslContext: SslContext private val pool: ChannelPool - data class Configuration( - val profiles: Map - ) { - sealed class Authentication { - data class TlsClientAuthenticationCredentials( - val key: PrivateKey, - val certificateChain: Array - ) : Authentication() - - data class BasicAuthenticationCredentials(val username: String, val password: String) : Authentication() - } - - class TrustStore ( - var file: Path?, - var password: String?, - var checkCertificateStatus: Boolean = false, - var verifyServerCertificate: Boolean = true, - ) - - class RetryPolicy( - val maxAttempts: Int, - val initialDelayMillis: Long, - val exp: Double - ) - - class Connection( - val readTimeout: Duration, - val writeTimeout: Duration, - val idleTimeout: Duration, - val readIdleTimeout: Duration, - val writeIdleTimeout: Duration - ) - - data class Profile( - val serverURI: URI, - val connection: Connection?, - val authentication: Authentication?, - val connectionTimeout: Duration?, - val maxConnections: Int, - val compressionEnabled: Boolean, - val retryPolicy: RetryPolicy?, - val tlsTruststore : TrustStore? - ) - - companion object { - fun parse(path: Path): Configuration { - return Files.newInputStream(path).use { - Xml.parseXml(path.toUri().toURL(), it) - }.let(Parser::parse) - } - } - } - init { group = NioEventLoopGroup() sslContext = SslContextBuilder.forClient().also { builder -> @@ -212,19 +153,6 @@ class RemoteBuildCacheClient(private val profile: Configuration.Profile) : AutoC val pipeline: ChannelPipeline = ch.pipeline() profile.connection?.also { conn -> - val readTimeout = conn.readTimeout.toMillis() - val writeTimeout = conn.writeTimeout.toMillis() - if (readTimeout > 0 || writeTimeout > 0) { - pipeline.addLast( - IdleStateHandler( - false, - readTimeout, - writeTimeout, - 0, - TimeUnit.MILLISECONDS - ) - ) - } val readIdleTimeout = conn.readIdleTimeout.toMillis() val writeIdleTimeout = conn.writeIdleTimeout.toMillis() val idleTimeout = conn.idleTimeout.toMillis() diff --git a/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/impl/Parser.kt b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/impl/Parser.kt index 6120a22..81a4661 100644 --- a/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/impl/Parser.kt +++ b/rbcs-client/src/main/kotlin/net/woggioni/rbcs/client/impl/Parser.kt @@ -1,7 +1,7 @@ package net.woggioni.rbcs.client.impl import net.woggioni.rbcs.api.exception.ConfigurationException -import net.woggioni.rbcs.client.RemoteBuildCacheClient +import net.woggioni.rbcs.client.Configuration import net.woggioni.rbcs.common.Xml.Companion.asIterable import net.woggioni.rbcs.common.Xml.Companion.renderAttribute import org.w3c.dom.Document @@ -16,9 +16,9 @@ import java.time.temporal.ChronoUnit object Parser { - fun parse(document: Document): RemoteBuildCacheClient.Configuration { + fun parse(document: Document): Configuration { val root = document.documentElement - val profiles = mutableMapOf() + val profiles = mutableMapOf() for (child in root.asIterable()) { val tagName = child.localName @@ -28,10 +28,10 @@ object Parser { child.renderAttribute("name") ?: throw ConfigurationException("name attribute is required") val uri = child.renderAttribute("base-url")?.let(::URI) ?: throw ConfigurationException("base-url attribute is required") - var authentication: RemoteBuildCacheClient.Configuration.Authentication? = null - var retryPolicy: RemoteBuildCacheClient.Configuration.RetryPolicy? = null - var connection : RemoteBuildCacheClient.Configuration.Connection? = null - var trustStore : RemoteBuildCacheClient.Configuration.TrustStore? = null + var authentication: Configuration.Authentication? = null + var retryPolicy: Configuration.RetryPolicy? = null + var connection : Configuration.Connection? = null + var trustStore : Configuration.TrustStore? = null for (gchild in child.asIterable()) { when (gchild.localName) { "tls-client-auth" -> { @@ -52,7 +52,7 @@ object Parser { .toList() .toTypedArray() authentication = - RemoteBuildCacheClient.Configuration.Authentication.TlsClientAuthenticationCredentials( + Configuration.Authentication.TlsClientAuthenticationCredentials( key, certChain ) @@ -64,7 +64,7 @@ object Parser { val password = gchild.renderAttribute("password") ?: throw ConfigurationException("password attribute is required") authentication = - RemoteBuildCacheClient.Configuration.Authentication.BasicAuthenticationCredentials( + Configuration.Authentication.BasicAuthenticationCredentials( username, password ) @@ -83,7 +83,7 @@ object Parser { gchild.renderAttribute("exp") ?.let(String::toDouble) ?: 2.0f - retryPolicy = RemoteBuildCacheClient.Configuration.RetryPolicy( + retryPolicy = Configuration.RetryPolicy( maxAttempts, initialDelay.toMillis(), exp.toDouble() @@ -91,22 +91,16 @@ object Parser { } "connection" -> { - val writeTimeout = gchild.renderAttribute("write-timeout") - ?.let(Duration::parse) ?: Duration.of(0, ChronoUnit.SECONDS) - val readTimeout = gchild.renderAttribute("read-timeout") - ?.let(Duration::parse) ?: Duration.of(0, ChronoUnit.SECONDS) val idleTimeout = gchild.renderAttribute("idle-timeout") ?.let(Duration::parse) ?: Duration.of(30, ChronoUnit.SECONDS) val readIdleTimeout = gchild.renderAttribute("read-idle-timeout") ?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS) val writeIdleTimeout = gchild.renderAttribute("write-idle-timeout") ?.let(Duration::parse) ?: Duration.of(60, ChronoUnit.SECONDS) - connection = RemoteBuildCacheClient.Configuration.Connection( - readTimeout, - writeTimeout, - idleTimeout, + connection = Configuration.Connection( readIdleTimeout, writeIdleTimeout, + idleTimeout, ) } @@ -118,7 +112,7 @@ object Parser { ?.let(String::toBoolean) ?: false val verifyServerCertificate = gchild.renderAttribute("verify-server-certificate") ?.let(String::toBoolean) ?: true - trustStore = RemoteBuildCacheClient.Configuration.TrustStore(file, password, checkCertificateStatus, verifyServerCertificate) + trustStore = Configuration.TrustStore(file, password, checkCertificateStatus, verifyServerCertificate) } } } @@ -131,7 +125,7 @@ object Parser { ?.let(String::toBoolean) ?: true - profiles[name] = RemoteBuildCacheClient.Configuration.Profile( + profiles[name] = Configuration.Profile( uri, connection, authentication, @@ -144,6 +138,6 @@ object Parser { } } } - return RemoteBuildCacheClient.Configuration(profiles) + return Configuration(profiles) } } \ No newline at end of file diff --git a/rbcs-client/src/main/resources/net/woggioni/rbcs/client/schema/rbcs-client.xsd b/rbcs-client/src/main/resources/net/woggioni/rbcs/client/schema/rbcs-client.xsd index 5623fb5..f7e98ab 100644 --- a/rbcs-client/src/main/resources/net/woggioni/rbcs/client/schema/rbcs-client.xsd +++ b/rbcs-client/src/main/resources/net/woggioni/rbcs/client/schema/rbcs-client.xsd @@ -15,75 +15,237 @@ - - - + + + + Disable authentication. + + + + + + + Enable HTTP basic authentication. + + + + + + + Enable TLS certificate authentication. + + + - - - + + + + Set inactivity timeouts for connections to this server, + if not present, connections are only closed on network errors. + + + + + + + Set a retry policy for this server, if not present requests won't be retried + + + + + + + If set, specify an alternative truststore to validate the server certificate. + If not present the system truststore is used. + + + - - - - - + + + + Name of this server profile, to be referred to from rbcs-cli with the '-p' parameter + + + + + + + RBCs server URL + + + + + + + Maximum number of concurrent TCP connection to open with this server + + + + + + + Enable HTTP compression when communicating to this server + + + + + + + Enable HTTP compression when communicating to this server + + + - - - - - + + + + The client will close the connection with the server + when neither a read nor a write was performed for the specified period of time. + + + + + + + The client will close the connection with the server + when no read was performed for the specified period of time. + + + + + + + The client will close the connection with the server + when no write was performed for the specified period of time. + + + - + + + + Add this tag to not use any type of authentication when talking to the RBCS server + + + - - + + + Add this tag to enable HTTP basic authentication for the communication to this server, + mind that HTTP basic authentication sends credentials directly over the network, so make sure + your communication is protected by TLS (i.e. your server's URL starts with "https") + + + + + + Username for HTTP basic authentication + + + + + + + Password used for HTTP basic authentication + + + - - - - + + + + System path to the keystore file + + + + + + + Password to open they keystore file + + + + + + + Alias of the keystore entry containing the private key + + + + + + + Private key entry's encryption password + + + - - - + + + Retry policy to use in case of failures, based on exponential backoff + https://en.wikipedia.org/wiki/Exponential_backoff + + + + + + + Maximum number of attempts, after which the call will result in an error, + throwing an exception related to the last received failure + + + + + + + Delay to apply before retrying after the first failed call + + + + + + + Exponent to apply to compute the next delay + + + - Path to the trustore file + Path to the truststore file - Trustore file password + Truststore file password - Whether or not check the certificate validity using CRL/OCSP + Whether or not check the server certificate validity using CRL/OCSP - If false, the client will blindly trust the provided server certificate + If false, the client will blindly trust the certificate provided by the server diff --git a/rbcs-server-memcache/README.md b/rbcs-server-memcache/README.md index 386b021..7badcb9 100644 --- a/rbcs-server-memcache/README.md +++ b/rbcs-server-memcache/README.md @@ -30,7 +30,7 @@ The plugins currently supports the following configuration attributes: ... - + diff --git a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/auth/UserAuthorizer.kt b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/auth/UserAuthorizer.kt index 072dfb3..541e528 100644 --- a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/auth/UserAuthorizer.kt +++ b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/auth/UserAuthorizer.kt @@ -8,8 +8,9 @@ class RoleAuthorizer : Authorizer { companion object { private val METHOD_MAP = mapOf( - Role.Reader to setOf(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE), - Role.Writer to setOf(HttpMethod.PUT, HttpMethod.POST) + Role.Reader to setOf(HttpMethod.GET, HttpMethod.HEAD), + Role.Writer to setOf(HttpMethod.PUT, HttpMethod.POST), + Role.Healthcheck to setOf(HttpMethod.TRACE) ) } diff --git a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/cache/FileSystemCacheProvider.kt b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/cache/FileSystemCacheProvider.kt index 8836597..c97c112 100644 --- a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/cache/FileSystemCacheProvider.kt +++ b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/cache/FileSystemCacheProvider.kt @@ -12,7 +12,7 @@ import java.util.zip.Deflater class FileSystemCacheProvider : CacheProvider { - override fun getXmlSchemaLocation() = "classpath:net/woggioni/rbcs/server/schema/rbcs.xsd" + override fun getXmlSchemaLocation() = "classpath:net/woggioni/rbcs/server/schema/rbcs-server.xsd" override fun getXmlType() = "fileSystemCacheType" diff --git a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/cache/InMemoryCacheProvider.kt b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/cache/InMemoryCacheProvider.kt index 023cf46..c5f8dd2 100644 --- a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/cache/InMemoryCacheProvider.kt +++ b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/cache/InMemoryCacheProvider.kt @@ -11,7 +11,7 @@ import java.util.zip.Deflater class InMemoryCacheProvider : CacheProvider { - override fun getXmlSchemaLocation() = "classpath:net/woggioni/rbcs/server/schema/rbcs.xsd" + override fun getXmlSchemaLocation() = "classpath:net/woggioni/rbcs/server/schema/rbcs-server.xsd" override fun getXmlType() = "inMemoryCacheType" diff --git a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/configuration/Parser.kt b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/configuration/Parser.kt index 998672c..1c487c8 100644 --- a/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/configuration/Parser.kt +++ b/rbcs-server/src/main/kotlin/net/woggioni/rbcs/server/configuration/Parser.kt @@ -193,6 +193,7 @@ object Parser { when (it.localName) { "reader" -> Role.Reader "writer" -> Role.Writer + "healthcheck" -> Role.Healthcheck else -> throw UnsupportedOperationException("Illegal node '${it.localName}'") } }.toSet() diff --git a/rbcs-server/src/main/resources/net/woggioni/rbcs/server/rbcs-default.xml b/rbcs-server/src/main/resources/net/woggioni/rbcs/server/rbcs-default.xml index 561a938..6d45ea1 100644 --- a/rbcs-server/src/main/resources/net/woggioni/rbcs/server/rbcs-default.xml +++ b/rbcs-server/src/main/resources/net/woggioni/rbcs/server/rbcs-default.xml @@ -2,7 +2,7 @@ + xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd"> \ No newline at end of file diff --git a/rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/rbcs.xsd b/rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/rbcs-server.xsd similarity index 98% rename from rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/rbcs.xsd rename to rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/rbcs-server.xsd index cbc4019..85a0d16 100644 --- a/rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/rbcs.xsd +++ b/rbcs-server/src/main/resources/net/woggioni/rbcs/server/schema/rbcs-server.xsd @@ -424,7 +424,8 @@ - User's password used in HTTP basic authentication + User's password hash used for HTTP basic authentication, this has to be generated with + the `password` subcommand of `rbcs-cli` @@ -498,18 +499,12 @@ - - - - - - - + diff --git a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractTlsServerTest.kt b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractTlsServerTest.kt index 5c5d6c1..fc9080c 100644 --- a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractTlsServerTest.kt +++ b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/AbstractTlsServerTest.kt @@ -47,6 +47,7 @@ abstract class AbstractTlsServerTest : AbstractServerTest() { 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 diff --git a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/TlsServerTest.kt b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/TlsServerTest.kt index 3f95cb0..4bfaed5 100644 --- a/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/TlsServerTest.kt +++ b/rbcs-server/src/test/kotlin/net/woggioni/rbcs/server/test/TlsServerTest.kt @@ -18,6 +18,7 @@ class TlsServerTest : AbstractTlsServerTest() { 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) ) @@ -140,7 +141,24 @@ class TlsServerTest : AbstractTlsServerTest() { val client: HttpClient = getHttpClient(null) val requestBuilder = newRequestBuilder("").method( "TRACE", - HttpRequest.BodyPublishers.ofByteArray("sfgsdgfaiousfiuhsd".toByteArray()) + HttpRequest.BodyPublishers.ofByteArray("this is an healthcheck".toByteArray()) + ) + + val response: HttpResponse = + 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 = diff --git a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user.xml b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user.xml index 10f25bd9..d4f7f89 100644 --- a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user.xml +++ b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user.xml @@ -1,7 +1,7 @@ + xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd"> diff --git a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user2.xml b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user2.xml index ffc82b9..0c28def 100644 --- a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user2.xml +++ b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/duplicate-anonymous-user2.xml @@ -1,7 +1,7 @@ + xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd"> diff --git a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/invalid-user-ref.xml b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/invalid-user-ref.xml index 643674f..3d0971e 100644 --- a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/invalid-user-ref.xml +++ b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/invalid-user-ref.xml @@ -1,7 +1,7 @@ + xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd"> diff --git a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/multiple-user-quota.xml b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/multiple-user-quota.xml index b6ddc17..5384928 100644 --- a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/multiple-user-quota.xml +++ b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/invalid/multiple-user-quota.xml @@ -1,7 +1,7 @@ + xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd"> diff --git a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/valid/rbcs-default.xml b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/valid/rbcs-default.xml index c6ef1d5..b25f4c7 100644 --- a/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/valid/rbcs-default.xml +++ b/rbcs-server/src/test/resources/net/woggioni/rbcs/server/test/valid/rbcs-default.xml @@ -1,7 +1,7 @@ + xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd"> + 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"> + xs:schemaLocation="urn:net.woggioni.rbcs.server jpms://net.woggioni.rbcs.server/net/woggioni/rbcs/server/schema/rbcs-server.xsd">