Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 61 additions & 3 deletions docs/modules/servers/partials/configure/webadmin.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ The web administration supports for now the CRUD operations on:
- Performing cassandra migrations [small]*_(only for Distributed James Server that uses cassandra as backend)_*
- And much more, as described in the following sections.

*WARNING*: This API allows authentication only via the use of JWT. If not
configured with JWT, an administrator should ensure an attacker can not
*WARNING*: This API supports authentication via JWT or static passwords. If not
configured with either, an administrator should ensure an attacker cannot
use this API.

By the way, some endpoints are not filtered by authentication. Those endpoints are not related to data stored in James,
Expand Down Expand Up @@ -41,7 +41,7 @@ to get some examples and hints.

| password
| Uses a configured static value for authentication. It relies on the Password header.
It supports several passwords, configured as a coma separated list.
It supports several passwords, configured as a comma separated list.

....
password=secretA,secretB,secretC
Expand All @@ -62,6 +62,54 @@ Password: secretD

As well as request without the password header.

| password.readonly
| Configures passwords that only allow read operations (GET requests).
These passwords can view data but cannot modify it.
Multiple passwords can be configured as a comma-separated list.

....
password.readonly=aaa,bbb
....

Requests with these passwords will be allowed for:

....
GET /domains
GET /users
....

But denied for:

....
POST /domains
DELETE /users
PUT /mailboxes
....

| password.nodelete
| Configures passwords that allow all operations except DELETE.
These passwords can read and create/modify data but cannot delete.
Multiple passwords can be configured as a comma-separated list.

....
password.nodelete=ccc,ddd
....

Requests with these passwords will be allowed for:

....
GET /domains
POST /users
PUT /mailboxes
....
+
But denied for:
+
....
DELETE /domains
DELETE /users
....

| jwt.enable
| Allow JSON Web Token as an authentication mechanism (default: false)

Expand Down Expand Up @@ -98,6 +146,16 @@ xref:{pages-path}/customization/webadmin-routes.adoc[creating you own webadmin r

|===

*Note on HTTP status codes:*

- `401 Unauthorized` is returned when:
- No password header is provided
- The password is not recognized (not in any of the configured lists)

- `403 Forbidden` is returned when:
- The password is valid but the HTTP method is not allowed for that password type
- For example, using a readonly password for a POST request

== Generating a JWT key pair

The {server-name} enforces the use of RSA-SHA-256.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,16 @@ https.enabled=false
# List of fully qualified class names that should be exposed over webadmin
# in addition to your product default routes. Routes needs to be located
# within the classpath or in the ./extensions-jars folder.
#extensions.routes=
#extensions.routes=

# Password authentication settings
# Configure one or more passwords (comma separated) for WebAdmin authentication
#password=secret1,secret2

# Read-only passwords - only allow GET requests
# These passwords can only perform read operations
#password.readonly=aaa,bbb

# No-delete passwords - allow all operations except DELETE
# These passwords can perform read and write operations but not delete
#password.nodelete=ccc,ddd
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,16 @@ https.enabled=false
# List of fully qualified class names that should be exposed over webadmin
# in addition to your product default routes. Routes needs to be located
# within the classpath or in the ./extensions-jars folder.
#extensions.routes=
#extensions.routes=

# Password authentication settings
# Configure one or more passwords (comma separated) for WebAdmin authentication
#password=secret1,secret2

# Read-only passwords - only allow GET requests
# These passwords can only perform read operations
#password.readonly=aaa,bbb

# No-delete passwords - allow all operations except DELETE
# These passwords can perform read and write operations but not delete
#password.nodelete=ccc,ddd
14 changes: 13 additions & 1 deletion server/apps/jpa-app/sample-configuration/webadmin.properties
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,16 @@ https.enabled=false
# List of fully qualified class names that should be exposed over webadmin
# in addition to your product default routes. Routes needs to be located
# within the classpath or in the ./extensions-jars folder.
#extensions.routes=
#extensions.routes=

# Password authentication settings
# Configure one or more passwords (comma separated) for WebAdmin authentication
#password=secret1,secret2

# Read-only passwords - only allow GET requests
# These passwords can only perform read operations
#password.readonly=aaa,bbb

# No-delete passwords - allow all operations except DELETE
# These passwords can perform read and write operations but not delete
#password.nodelete=ccc,ddd
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,16 @@ https.enabled=false
# List of fully qualified class names that should be exposed over webadmin
# in addition to your product default routes. Routes needs to be located
# within the classpath or in the ./extensions-jars folder.
#extensions.routes=
#extensions.routes=

# Password authentication settings
# Configure one or more passwords (comma separated) for WebAdmin authentication
#password=secret1,secret2

# Read-only passwords - only allow GET requests
# These passwords can only perform read operations
#password.readonly=aaa,bbb

# No-delete passwords - allow all operations except DELETE
# These passwords can perform read and write operations but not delete
#password.nodelete=ccc,ddd
14 changes: 13 additions & 1 deletion server/apps/memory-app/sample-configuration/webadmin.properties
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,16 @@ https.enabled=false
# List of fully qualified class names that should be exposed over webadmin
# in addition to your product default routes. Routes needs to be located
# within the classpath or in the ./extensions-jars folder.
#extensions.routes=
#extensions.routes=

# Password authentication settings
# Configure one or more passwords (comma separated) for WebAdmin authentication
#password=secret1,secret2

# Read-only passwords - only allow GET requests
# These passwords can only perform read operations
#password.readonly=aaa,bbb

# No-delete passwords - allow all operations except DELETE
# These passwords can perform read and write operations but not delete
#password.nodelete=ccc,ddd
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,16 @@ https.enabled=false
# List of fully qualified class names that should be exposed over webadmin
# in addition to your product default routes. Routes needs to be located
# within the classpath or in the ./extensions-jars folder.
#extensions.routes=
#extensions.routes=

# Password authentication settings
# Configure one or more passwords (comma separated) for WebAdmin authentication
#password=secret1,secret2

# Read-only passwords - only allow GET requests
# These passwords can only perform read operations
#password.readonly=aaa,bbb

# No-delete passwords - allow all operations except DELETE
# These passwords can perform read and write operations but not delete
#password.nodelete=ccc,ddd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
m# Licensed to the Apache Software Foundation (ASF) under one
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
Expand Down Expand Up @@ -46,4 +46,16 @@ https.enabled=false
# List of fully qualified class names that should be exposed over webadmin
# in addition to your product default routes. Routes needs to be located
# within the classpath or in the ./extensions-jars folder.
#extensions.routes=
#extensions.routes=

# Password authentication settings
# Configure one or more passwords (comma separated) for WebAdmin authentication
#password=secret1,secret2

# Read-only passwords - only allow GET requests
# These passwords can only perform read operations
#password.readonly=aaa,bbb

# No-delete passwords - allow all operations except DELETE
# These passwords can perform read and write operations but not delete
#password.nodelete=ccc,ddd
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ public WebAdminConfiguration provideWebAdminConfiguration(FileSystem fileSystem,
.maxThreadCount(Optional.ofNullable(configurationFile.getInteger("maxThreadCount", null)))
.minThreadCount(Optional.ofNullable(configurationFile.getInteger("minThreadCount", null)))
.password(Optional.ofNullable(configurationFile.getString("password", null)))
.readOnlyPassword(Optional.ofNullable(configurationFile.getString("password.readonly", null)))
.noDeletePassword(Optional.ofNullable(configurationFile.getString("password.nodelete", null)))
.build();
} catch (FileNotFoundException e) {
LOGGER.info("No webadmin.properties file. Disabling WebAdmin interface.");
Expand Down Expand Up @@ -191,14 +193,23 @@ public AuthenticationFilter providesAuthenticationFilter(PropertiesProvider prop
if (configurationFile.getBoolean("jwt.enabled", DEFAULT_JWT_DISABLED)) {
return new JwtFilter(jwtTokenVerifier);
}
return webAdminConfiguration.getPassword()
.<AuthenticationFilter>map(PasswordFilter::new)
.orElse(new NoAuthenticationFilter());
if (isPasswordPresent(webAdminConfiguration)) {
return new PasswordFilter(webAdminConfiguration.getPassword(),
webAdminConfiguration.getReadOnlyPassword(),
webAdminConfiguration.getNoDeletePassword());
}
return new NoAuthenticationFilter();
} catch (FileNotFoundException e) {
return new NoAuthenticationFilter();
}
}

private boolean isPasswordPresent(WebAdminConfiguration webAdminConfiguration) {
return webAdminConfiguration.getPassword().isPresent()
|| webAdminConfiguration.getNoDeletePassword().isPresent()
|| webAdminConfiguration.getReadOnlyPassword().isPresent();
}

@Provides
@Singleton
@Named("webadmin")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public static class Builder {
private Optional<String> urlCORSOrigin = Optional.empty();
private Optional<String> host = Optional.empty();
private Optional<String> password = Optional.empty();
private Optional<String> readOnlyPassword = Optional.empty();
private Optional<String> noDeletePassword = Optional.empty();
private ImmutableList.Builder<String> additionalRoutes = ImmutableList.builder();
private Optional<String> jwtPublicKey = Optional.empty();
private Optional<Integer> maxThreadCount = Optional.empty();
Expand Down Expand Up @@ -134,6 +136,26 @@ public Builder password(Optional<String> password) {
return this;
}

public Builder readOnlyPassword(String readOnlyPassword) {
this.readOnlyPassword = Optional.ofNullable(readOnlyPassword);
return this;
}

public Builder readOnlyPassword(Optional<String> readOnlyPassword) {
this.readOnlyPassword = readOnlyPassword;
return this;
}

public Builder noDeletePassword(String noDeletePassword) {
this.noDeletePassword = Optional.ofNullable(noDeletePassword);
return this;
}

public Builder noDeletePassword(Optional<String> noDeletePassword) {
this.noDeletePassword = noDeletePassword;
return this;
}

public Builder additionalRoute(String additionalRoute) {
this.additionalRoutes.add(additionalRoute);
return this;
Expand Down Expand Up @@ -167,6 +189,8 @@ public WebAdminConfiguration build() {
additionalRoutes.build(),
jwtPublicKey,
password,
readOnlyPassword,
noDeletePassword,
maxThreadCount,
minThreadCount);
}
Expand All @@ -181,12 +205,16 @@ public WebAdminConfiguration build() {
private final List<String> additionalRoutes;
private final Optional<String> jwtPublicKey;
private final Optional<String> password;
private final Optional<String> readOnlyPassword;
private final Optional<String> noDeletePassword;
private final Optional<Integer> maxThreadCount;
private final Optional<Integer> minThreadCount;

@VisibleForTesting
WebAdminConfiguration(boolean enabled, Optional<PortSupplier> port, Optional<TlsConfiguration> tlsConfiguration,
boolean enableCORS, String urlCORSOrigin, String host, List<String> additionalRoutes, Optional<String> jwtPublicKey, Optional<String> password, Optional<Integer> maxThreadCount, Optional<Integer> minThreadCount) {
boolean enableCORS, String urlCORSOrigin, String host, List<String> additionalRoutes,
Optional<String> jwtPublicKey, Optional<String> password, Optional<String> readOnlyPassword,
Optional<String> noDeletePassword, Optional<Integer> maxThreadCount, Optional<Integer> minThreadCount) {
this.enabled = enabled;
this.port = port;
this.tlsConfiguration = tlsConfiguration;
Expand All @@ -196,6 +224,8 @@ public WebAdminConfiguration build() {
this.additionalRoutes = additionalRoutes;
this.jwtPublicKey = jwtPublicKey;
this.password = password;
this.readOnlyPassword = readOnlyPassword;
this.noDeletePassword = noDeletePassword;
this.maxThreadCount = maxThreadCount;
this.minThreadCount = minThreadCount;
}
Expand Down Expand Up @@ -248,6 +278,14 @@ public Optional<String> getPassword() {
return password;
}

public Optional<String> getReadOnlyPassword() {
return readOnlyPassword;
}

public Optional<String> getNoDeletePassword() {
return noDeletePassword;
}

@Override
public final boolean equals(Object o) {
if (o instanceof WebAdminConfiguration) {
Expand All @@ -261,6 +299,8 @@ public final boolean equals(Object o) {
&& Objects.equals(this.urlCORSOrigin, that.urlCORSOrigin)
&& Objects.equals(this.host, that.host)
&& Objects.equals(this.password, that.password)
&& Objects.equals(this.readOnlyPassword, that.readOnlyPassword)
&& Objects.equals(this.noDeletePassword, that.noDeletePassword)
&& Objects.equals(this.additionalRoutes, that.additionalRoutes)
&& Objects.equals(this.minThreadCount, that.minThreadCount)
&& Objects.equals(this.maxThreadCount, that.maxThreadCount);
Expand All @@ -270,6 +310,7 @@ public final boolean equals(Object o) {

@Override
public final int hashCode() {
return Objects.hash(enabled, port, tlsConfiguration, enableCORS, jwtPublicKey, urlCORSOrigin, host, additionalRoutes, minThreadCount, maxThreadCount, password);
return Objects.hash(enabled, port, tlsConfiguration, enableCORS, jwtPublicKey, urlCORSOrigin, host,
password, readOnlyPassword, noDeletePassword, additionalRoutes, minThreadCount, maxThreadCount);
}
}
Loading