From e1d14835cf98e722635fa164764d3401cb03b55e Mon Sep 17 00:00:00 2001 From: Yeray Borges Date: Fri, 20 Feb 2026 18:01:58 +0000 Subject: [PATCH 1/6] [WFCORE-7247] Bump Core Management Subsystem schemas to Community 1.0 and Preview 2.0 --- .../CoreManagementSubsystemSchema.java | 7 +- .../wildfly-core-management_community_1_0.xsd | 104 ++++++++++++++ .../wildfly-core-management_preview_2_0.xsd | 128 ++++++++++++++++++ .../core-management-community-1.0.xml | 13 ++ .../core-management-preview-2.0.xml | 14 ++ 5 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 core-management/core-management-subsystem/src/main/resources/schema/wildfly-core-management_community_1_0.xsd create mode 100644 core-management/core-management-subsystem/src/main/resources/schema/wildfly-core-management_preview_2_0.xsd create mode 100644 core-management/core-management-subsystem/src/test/resources/org/wildfly/extension/core/management/core-management-community-1.0.xml create mode 100644 core-management/core-management-subsystem/src/test/resources/org/wildfly/extension/core/management/core-management-preview-2.0.xml diff --git a/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/CoreManagementSubsystemSchema.java b/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/CoreManagementSubsystemSchema.java index 371c7d30fb0..d2c0241369d 100644 --- a/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/CoreManagementSubsystemSchema.java +++ b/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/CoreManagementSubsystemSchema.java @@ -28,8 +28,11 @@ public enum CoreManagementSubsystemSchema implements PersistentSubsystemSchema { VERSION_1_0(1), - VERSION_1_0_PREVIEW(1, Stability.PREVIEW); - static final Map CURRENT = Feature.map(EnumSet.of(VERSION_1_0, VERSION_1_0_PREVIEW)); + VERSION_1_0_PREVIEW(1, Stability.PREVIEW), + VERSION_1_0_COMMUNITY(1, Stability.COMMUNITY), + VERSION_2_0_PREVIEW(2, Stability.PREVIEW), + ; + static final Map CURRENT = Feature.map(EnumSet.of(VERSION_1_0, VERSION_1_0_COMMUNITY, VERSION_2_0_PREVIEW)); private final VersionedNamespace namespace; diff --git a/core-management/core-management-subsystem/src/main/resources/schema/wildfly-core-management_community_1_0.xsd b/core-management/core-management-subsystem/src/main/resources/schema/wildfly-core-management_community_1_0.xsd new file mode 100644 index 00000000000..1eb7b23e1e1 --- /dev/null +++ b/core-management/core-management-subsystem/src/main/resources/schema/wildfly-core-management_community_1_0.xsd @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + Configuration for the history of configuration changes. + + + + + + + Number of configuration changes that are available in history. + + + + + + + Whether sensitive data in configuration changes should be redacted. + + + + + + + + + Configuration for a process state listener. + + + + + + + + Configuration properties for the process state listener. + + + + + + + + Name of the process state listener. + + + + + + + ControlledProcessStateListener class implementation. + + + + + + + Module where the ControlledProcessStateListener implementation class may be found. + + + + + + + Timeout used in seconds, for listener operations. + If an individual listener operation takes longer than this timeout it will be canceled. + + + + + + + + + + + + + + + + + + + + + diff --git a/core-management/core-management-subsystem/src/main/resources/schema/wildfly-core-management_preview_2_0.xsd b/core-management/core-management-subsystem/src/main/resources/schema/wildfly-core-management_preview_2_0.xsd new file mode 100644 index 00000000000..95b820021c0 --- /dev/null +++ b/core-management/core-management-subsystem/src/main/resources/schema/wildfly-core-management_preview_2_0.xsd @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + Configuration for the history of configuration changes. + + + + + + + Number of configuration changes that are available in history. + + + + + + + Whether sensitive data in configuration changes should be redacted. + + + + + + + + + Configuration of the handling of finding unstable api annotations in the + user's code + + + + + + + Whether to log or throw an error if use of unstable api annotations are found + + + + + + + + + + + + + + + + Configuration for a process state listener. + + + + + + + + Configuration properties for the process state listener. + + + + + + + + Name of the process state listener. + + + + + + + ControlledProcessStateListener class implementation. + + + + + + + Module where the ControlledProcessStateListener implementation class may be found. + + + + + + + Timeout used in seconds, for listener operations. + If an individual listener operation takes longer than this timeout it will be canceled. + + + + + + + + + + + + + + + + + + + + + diff --git a/core-management/core-management-subsystem/src/test/resources/org/wildfly/extension/core/management/core-management-community-1.0.xml b/core-management/core-management-subsystem/src/test/resources/org/wildfly/extension/core/management/core-management-community-1.0.xml new file mode 100644 index 00000000000..f40add4b80b --- /dev/null +++ b/core-management/core-management-subsystem/src/test/resources/org/wildfly/extension/core/management/core-management-community-1.0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/core-management/core-management-subsystem/src/test/resources/org/wildfly/extension/core/management/core-management-preview-2.0.xml b/core-management/core-management-subsystem/src/test/resources/org/wildfly/extension/core/management/core-management-preview-2.0.xml new file mode 100644 index 00000000000..d933113e86e --- /dev/null +++ b/core-management/core-management-subsystem/src/test/resources/org/wildfly/extension/core/management/core-management-preview-2.0.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file From 6ec1da251a70dfc3c056c97089f6e4a29b396a3d Mon Sep 17 00:00:00 2001 From: Yeray Borges Date: Fri, 20 Feb 2026 19:23:46 +0000 Subject: [PATCH 2/6] [WFCORE-7247] Bump Management Subsystem schemas to Community 21.0 --- .../controller/parsing/ManagementSchemas.java | 12 +- .../host_resource_constraints_community.xml | 2 +- ...ndalone_resource_constraints_community.xml | 2 +- .../schema/wildfly-config_community_21_0.xsd | 3916 +++++++++++++++++ 4 files changed, 3925 insertions(+), 7 deletions(-) create mode 100644 server/src/main/resources/schema/wildfly-config_community_21_0.xsd diff --git a/controller/src/main/java/org/jboss/as/controller/parsing/ManagementSchemas.java b/controller/src/main/java/org/jboss/as/controller/parsing/ManagementSchemas.java index bdfb9147751..e1072136f01 100644 --- a/controller/src/main/java/org/jboss/as/controller/parsing/ManagementSchemas.java +++ b/controller/src/main/java/org/jboss/as/controller/parsing/ManagementSchemas.java @@ -6,7 +6,9 @@ package org.jboss.as.controller.parsing; import java.util.Collections; +import java.util.EnumMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import org.jboss.as.controller.Feature; @@ -57,6 +59,7 @@ private enum Version implements Feature { VERSION_19(19), VERSION_20(20), VERSION_20_COMMUNITY(20, Stability.COMMUNITY), + VERSION_21_COMMUNITY(21, Stability.COMMUNITY), ; private final int majorVersion; @@ -100,18 +103,17 @@ public Stability getStability() { protected ManagementSchemas(final Stability stability, final ManagementXmlReaderWriter readerWriterDelegate, final String localName) { Set allSchemas = new HashSet<>(); - int maxVersion = 0; + Map maxVersionPerStability = new EnumMap<>(Stability.class); for (Version version : Version.values()) { - if (version.getMajorVersion() > maxVersion) { - maxVersion = version.getMajorVersion(); - } + maxVersionPerStability.merge(version.getStability(), version.getMajorVersion(), Math::max); allSchemas.add(ManagementSchema.create(stability.enables(version.getStability()) ? readerWriterDelegate : UnstableManagementReaderWriter.INSTANCE, version.getStability(), version.getMajorVersion(), version.getMinorVersion(), localName)); } Set current = new HashSet<>(); for (ManagementXmlSchema schema : allSchemas) { - if (schema.getNamespace().getVersion().major() == maxVersion) { + Integer maxVersion = maxVersionPerStability.get(schema.getStability()); + if (maxVersion != null && schema.getNamespace().getVersion().major() == maxVersion) { current.add(schema); } } diff --git a/core-model-test/tests/src/test/resources/org/jboss/as/core/model/test/mgmt_interfaces/host_resource_constraints_community.xml b/core-model-test/tests/src/test/resources/org/jboss/as/core/model/test/mgmt_interfaces/host_resource_constraints_community.xml index d12ddd76af8..4c6c36b9dec 100644 --- a/core-model-test/tests/src/test/resources/org/jboss/as/core/model/test/mgmt_interfaces/host_resource_constraints_community.xml +++ b/core-model-test/tests/src/test/resources/org/jboss/as/core/model/test/mgmt_interfaces/host_resource_constraints_community.xml @@ -5,7 +5,7 @@ ~ SPDX-License-Identifier: Apache-2.0 --> - + diff --git a/core-model-test/tests/src/test/resources/org/jboss/as/core/model/test/mgmt_interfaces/standalone_resource_constraints_community.xml b/core-model-test/tests/src/test/resources/org/jboss/as/core/model/test/mgmt_interfaces/standalone_resource_constraints_community.xml index 1cd27e53d21..7c746029788 100644 --- a/core-model-test/tests/src/test/resources/org/jboss/as/core/model/test/mgmt_interfaces/standalone_resource_constraints_community.xml +++ b/core-model-test/tests/src/test/resources/org/jboss/as/core/model/test/mgmt_interfaces/standalone_resource_constraints_community.xml @@ -5,7 +5,7 @@ ~ SPDX-License-Identifier: Apache-2.0 --> - + + + + + + + + + + + + Root element for the main document specifying the core configuration + for the servers in a domain. There should be one such main + document per domain, available to the host controller that + is configured to act as the domain controller. + + + + + + + + + + + + + + + + + + + + + The name to use for the domain controller. Useful for administrators who need to work with multiple domains. + + + + + + + The name of the organization running this domain. + + + + + + + + + + Root element for a document configuring a host controller and + the group of servers under the control of that host controller. + The standard usage would be for a domain to have one such host controller + on each physical (or virtual) host machine. Emphasis in this + document is on enumerating the servers, configuring items that + are specific to the host environment (e.g. IP addresses), and + on any server-specific configuration settings. + + + + + + + + + + + + + + + + + + + The name to use for this host's host controller. Must be unique across the domain. + If not set, defaults to the runtime value "HOSTNAME" or "COMPUTERNAME" environment variables, + or, if neither environment variable is present, to the value of InetAddress.getLocalHost().getHostName(). + + If the special value "jboss.domain.uuid" is used, a java.util.UUID will be created + and used, based on the value of InetAddress.getLocalHost(). + + + + + + + The name of the organization running this host. + + + + + + + + + + Root element for a document specifying the configuration + of a single "standalone" server that does not operate + as part of a domain. + + Note that this element is distinct from the 'serverType' + specified in this schema. The latter type forms part of the + configuration of a server that operates as part of a domain. + + + + + + + + + + + + + + + + + + The name to use for this server. + If not set, defaults to the runtime value "HOSTNAME" or "COMPUTERNAME" environment variables, + or, if neither environment variable is present, to the value of InetAddress.getLocalHost().getHostName(). + + If the special value "jboss.domain.uuid" is used, a java.util.UUID will be created + and used, based on the value of InetAddress.getLocalHost(). + + + + + + + The name of the organization running this server. + + + + + + + + + + Domain-wide default configuration settings for the management of standalone servers and a Host Controller. + + + + + + + Configuration for the history of configuration changes. + + + + + + + Number of configuration changes that are available in history. + + + + + + + + + + + + + The centralized configuration for the management of a Host Controller. + + + + + + + + + + + + + + + + + + + + + + + + + The centralized configuration for the management of standalone server. + + + + + + + + + + + + + + + + + + + + + + + + + The centralized configuration for domain-wide management. + + + + + + + + + + A list of URLs. + + + + + + + A list of String. + + + + + + + + Configuration of the secret/password-based identity of this server. + + + + + + + Credential to be used by as protection parameter for the Credential Store. + + + + + + + + The secret / password - Base64 Encoded + + + + + + + + + The keystore configuration for the server. + + + + + + + Credential reference to be used by as protection parameter for the Keystore. + + + + + + + + The password to open the keystore. + + + + + + + + + This is a more complex keystore definition which also allows for an alias + and key password to be specified. + + + + + + + + + Credential reference to be used by as protection parameter when loading keys from the keystore. + + + + + + + + The alias of the entry to use from the keystore, if specified all remaining + entries in the keystore will be ignored. + + Note: The use of aliases is only available for JKS based stores, for other store types this will be ignored. + + + + + + + The password to use when loading keys from the keystore. + + + + + + + If this is set and the key store does not exist then a new keystore will be created and a + self-signed certificate will be generated. The host name for the self-signed certificate + will be the value of this attribute. + + This is not intended for production use. + + + + + + + + + + + An extension of keyStoreType used for audit logging configuration. + + + + + + + + The path of the keystore, this is required if the provider is JKS otherwise it will be ignored. + + + + + + + The name of another previously named path, or of one of the + standard paths provided by the system. If 'relative-to' is + provided, the value of the 'path' attribute is treated as + relative to the path specified by this + attribute. + + + + + + + + + + + An audit specific extension of the extended key store type. + + + + + + + + The path of the keystore, this is required if the provider is JKS otherwise it will be ignored. + + + + + + + The name of another previously named path, or of one of the + standard paths provided by the system. If 'relative-to' is + provided, the value of the 'path' attribute is treated as + relative to the path specified by this + attribute. + + + + + + + + + + + Declaration of management operation audit logging formatters. + + + + + + + + + + + Shared configuration for audit log formatters.. + + + + + + The name of the formatter. Must be unique across all types of formatter + (there is only the JSON formatter at present but more are planned for the + future) + + + + + + + Whether or not to include the date in the formatted log record + + + + + + + The date format to use as understood by {@link java.text.SimpleDateFormat}. + Will be ignored if include-date="false". + + + + + + + The separator between the date and the rest of the formatted log message. + Will be ignored if include-date="false". + + + + + + + + + Configuration of a JSON formatter for the audit log. + + + + + + + + If true will format the JSON on one line. There may still be + values containing new lines, so if having the whole record on + one line is important, set escape-new-line or escape-control-characters to true. + + + + + + + If true will escape all new lines with the ascii code in octal, + e.g. #012. + + + + + + + If true will escape all control characters (ascii entries with a decimal + value less than 32) with the ascii code in octal, e.g.'\n\ becomes '#012'. + If this is true, it will override escape-new-line="false" + + + + + + + + + + + Declaration of management operation audit logging handlers. + + + + + + + + + + + + Configuration of a in memory handler for the audit log. + + + + + + The name of the handler. The name must be unique across all types of handler. + + + + + + + The number of logging entries stored in memory. + + + + + + + + + + + + Common configuration of a handler for the audit log. + + + + + + The name of the handler. The name must be unique across all types of handler. + + + + + + + The name of the formatter to use for the handler. + + + + + + + The number of logging failures before this handler is disabled. + + + + + + + + + Configuration of a simple file handler for the audit log. This writes to a local file. + + + + + + + + The path of the audit log. + + + + + + + The name of another previously named path, or of one of the + standard paths provided by the system. If 'relative-to' is + provided, the value of the 'path' attribute is treated as + relative to the path specified by this attribute. + + + + + + + + + + + Configuration of a simple file handler for the audit log. This writes to a local file. + + + + + + + + Whether or not should an old log file be rotated during a handler initialization. + + + + + + + + + + + Configuration of a size rotating file handler for the audit log. This writes to a local file, + rotating the log after the size of the file grows beyond a certain point and keeping + a fixed number of backups.. + + + + + + + + The size at which to rotate the log file. + + + + + + + The maximum number of backups to keep. + + + + + + + + + + + Configuration of a periodic rotating file handler for the audit log. This writes to a local file, + rotating the log after a time period derived from the given suffix string, + which should be in a format understood by java.text.SimpleDateFormat. + + + + + + + + The suffix string in a format which can be understood by java.text.SimpleDateFormat. + The period of the rotation is automatically calculated based on the suffix. + + + + + + + + + + A positive number optionally followed by one of [bBkKmMgGtT] + + + + + + + + + + Configuration of a syslog file handler for the audit log on a server. This writes to syslog server. + + + + + + + + The configuration of the protocol to use communication with the syslog server. See your + syslog provider's documentation for configuration options. + + + + + + + + + + + + + The format to use for the syslog messages. See your syslog provider's documentation for what is supported. + + + + + + + Format the syslog data according to the RFC-5424 standard + + + + + Format the syslog data according to the RFC-3164 standard + + + + + + + + + The maximum length in bytes a log message, including the header, is allowed to be. If undefined, it will default to 1024 bytes if the syslog-format is RFC3164, or 2048 bytes if the syslog-format is RFC5424. + + + + + + + Whether or not a message, including the header, should truncate the message if the length in bytes is greater than the maximum length. If set to false messages will be split and sent with the same header values. + + + + + + + The facility to use for syslog logging as defined in section 6.2.1 of RFC-5424, and section 4.1.1 of RFC-3164. + The numerical values in the enumeration entries, is the numerical value as defined in the RFC. + + + + + + + 0 + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + 9 + + + + + 10 + + + + + 11 + + + + + 12 + + + + + 13 + + + + + 14 + + + + + 15 + + + + + 16 + + + + + 17 + + + + + 18 + + + + + 19 + + + + + 20 + + + + + 21 + + + + + 22 + + + + + 23 + + + + + + + + + The application name to add to the syslog records as defined in section 6.2.5 of RFC-5424. If not specified it will default to the name of the product. + + + + + + + + + + + + The host of the syslog server. + + + + + + + The port of the syslog server. + + + + + + + + Configure udp as the protocol for communicating with the syslog server + + + + + + + + + Configure tcp as the protocol for communicating with the syslog server + + + + + + + The message transfer setting as described in section 3.4 of RFC-6587. See your syslog provider's + documentation for what is supported + + + + + + + + Use the octet counting format for message transfer as described in section 3.4.1 of RFC-6587. + + + + + + + Use the non-transparent-framing format for message transfer as described in section 3.4.1 of RFC-6587. + + + + + + + + + + If a connection drop is detected, the number of seconds to wait before reconnecting. A negative number means + don't reconnect automatically. + + + + + + + + + + Configure tls as the protocol for communicating with the syslog server + + + + + + + + Configuration of a keystore to use to create a trust manager to verify the server + certificate for encrypted communications. If the server certificate is signed off by a + signing authority, tls can be used without a truststore. + + + + + + + Configuration of a keystore containing a client certificate and a private key, e.g. in + PKCS12 format. This turns on authenticating the clients against the syslog server. + + + + + + + + + + + + Declaration of management operation audit logging configuration coming from the model controller core. + + + + + + + + + Whether operations should be logged on boot. + + + + + + + Whether operations that do not modify the configuration or any runtime services should be logged. + + + + + + + Whether audit logging is enabled. + + + + + + + Whether sensitive data in audit log entries should be redacted. + + + + + + + + + References to audit-log-handlers defined in the audit-log-handlers section + + + + + + + + + + + A reference to an audit-log-handler defined in the audit-log-appenders section + + + + + + + + + + + + + + + + + Reference to the SSLContext to use for this management interface. + + + + + + + Where Remoting is accepting incomming connections part of the authentication process + advertises the name of the protocol in use, by default this is 'remote' but this attribute + can be set if an alternative is required. + + + + + + + Where Remoting is accepting incomming connection the initial exchange and the authentication + process both advertise the name of the server, by default this is derived from the address Remoting + is listening on but this attribute can be set to override the name. + + + + + + + + + + + + + HTTP Upgrade configuration on the management interface. + + + + + + + Is HTTP Upgrade to 'remote' enabled. + + + + + + + The SASL authentication policy to secure connections upgraded from HTTP. + + + + + + + + + + + The HTTP authentication factory to use to secure normal HTTP requests. + + + + + + + The maximum number of pending connections on the socket. + + + + + + + The maximum time in milliseconds a connection can be idle without a HTTP request before it is closed. + + + + + + + The maximum number of connections that can be open at any one time. + + Once reached no further connections will be accepted until the count reduces to the connection-low-water level. + + + + + + + The number of connections that the open count must reduce to before the connection-high-water level is reset. + + + + + + + + + + + Definition to HTTP headers to be added to responses based on the path of the request. + + + + + + + + + + + + The name of the header to set. + + Must be a valid HTTP Header name. + + + + + + + The value to set the header to. + + + + + + + + + + The path prefix this header mapping applies to. + + + + + + + + + + + + + + + The SASL server authentication policy to secure connections upgraded from HTTP. + + + + + + + + + + + Configuration of a host's exposed native management interface. + + + + + + + + + + + + + + + Configuration of the socket used by host or standalone server's exposed management interface. + + + + + + Network interface on which the host's socket for + management communication should be opened. + + + + + + + + + Configuration of the socket used by host or standalone server's exposed HTTP management interface. + + + + + + + + Port on which the host's socket for native + management communication should be opened. + + + + + + + + + + + Configuration of a host's exposed HTTP management interface. + + + + + + + + + + + + + + + + Configuration of the socket used by host or standalone server's exposed HTTP management interface. + + + + + + + + Port on which the host's socket for + management communication should be opened. + + If not specified the port will not be opened. + + + + + + + Port on which the host's socket for HTTPS + management communication should be opened. + + If not specified the port will not be opened. + + If specified an ssl-context will be + required to obtain the SSL configuration. + + + + + + + + + + + Configuration of the socket used by host's exposed HTTP management interface. + + + + + + + + Network interface on which the host's socket for + HTTPS management communication should be opened + if a different interface should be used from that + specified by the 'interface' attribute. + + If not specified the interface specified by the 'interface' + attribute will be used. + + Has no effect if the 'secure-port' attribute is not set. + + If specified with a different value from the 'interface' + attribute, redirect of HTTPS requests received on the HTTP + socket to the HTTPS address will not be supported. + + If specified an ssl-context will be + required to obtain the SSL configuration. + + + + + + + + + + + + + + + + + + + Configuration of the socket used by host or standalone server's exposed HTTP management interface. + + + + + + + Configuration of the socket to use for the native management interface is a choice + between a direct configuration of the address and port, or a reference to a socket-binding + configuration in the server's socket-binding-group element. The latter is the recommended + approach as it makes it easier to avoid port conflicts by taking advantage of the + socket-binding-group's port-offset configuration. Direct configuration of the address and + ports is deprecated and is only provided to preserve backward compatibility. + + + + + + + Deprecated. Use 'socket-binding' + + + + + + + + + + + + + Reference to the configuration of the socket to be used by a standalone server's exposed native management interface. + + + + + + Name of a socket-binding configuration declared in the server's socket-binding-group. + + + + + + + + + Configuration of a standalone server's exposed HTTP/HTTPS management interface. + + + + + + + Configuration of the socket to use for the HTTP/HTTPS management interface is a choice + between a direct configuration of the address and ports, or a reference to socket-binding + configurations in the server's socket-binding-group element. The latter is the recommended + approach as it makes it easier to avoid port conflicts by taking advantage of the + socket-binding-group's port-offset configuration. Direct configuration of the address and + ports is deprecated and is only provided to preserve backward compatibility. + + + + + + + Deprecated. Use 'socket-binding' + + + + + + + + + + A space separated list of Origins that will be trusted to send request to the management + API once the user is authenticated. This is used following the Cross-Origin Resource Sharing + recommendation (http://www.w3.org/TR/access-control/). + + + + + + + + + + + Reference to the configurations of the sockets to be used by a standalone server's exposed HTTP and HTTPS management interface. + + + + + + Name of a socket-binding configuration declared in the server's socket-binding-group to use for a HTTP socket. + + + + + + + Name of a socket-binding configuration declared in the server's socket-binding-group to use for a HTTPS socket. + + Note: When specified the interface must also be configured to reference an ssl-context. + + + + + + + + + Makes the native management interface available via the connectors set up in the remoting subsystem, + using the remoting subsystem's endpoint. This should only be used for a server not for a HC/DC. + + + + + + + + + + + + + + + + + + + + + + + + + + The remote domain controller's protocol. If not set, a discovery option must be provided, + or the --cached-dc startup option must be used, or the --admin-only startup option must be used + with the 'admin-only-policy' attribute set to a value other than 'fetch-from-domain-controller'. + + + + + + + The remote domain controller's host name. If not set, a discovery option must be provided, + or the --cached-dc startup option must be used, or the --admin-only startup option must be used + with the 'admin-only-policy' attribute set to a value other than 'fetch-from-domain-controller'. + + + + + + + The remote domain controller's port. If not set, a discovery option must be provided, + or the --cached-dc startup option must be used, or the --admin-only startup option must be used + with the 'admin-only-policy' attribute set to a value other than 'fetch-from-domain-controller'. + + + + + + + Reference to the authentication-context to use when establishing the remote connection. + + + + + + + + When set to true, this instructs the Domain Controller to not forward configuration and + operations for profiles, socket binding groups and server groups which do not affect our servers. + Setting to false will ensure that all of this configuration information is copied. Note that using + the '--backup' startup option on the command line will set this to false if the value is unspecified + in host.xml. If the value is specified in host.xml, then using '--backup' will not override the + specified value (for example: setting ignore-unused-configuration="true" and using --backup will + not override the value of ignore-unused-configuration, which will remain true). If --backup is not + used, this value will be true at runtime. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Provides names of direct child resources of the domain root resource requests for which the + Host Controller should ignore. Only relevant on a secondary Host Controller. Configuring such + "ignored resources" may help allow a Host Controller from an earlier release to function as a + secondary Host Controller to a primary Host Controller running a later release, by letting the secondary + ignore portions of the configuration its version of the software cannot understand. This strategy can + only be successful if the servers managed by the secondary Host Controller do not reference any of the + ignored configuration. + + Supports the following attributes: + + type -- the type of resource (e.g. 'profile' or 'socket-binding-group') certain instances of which + should be ignored. The value corresponds to the 'key' portion of the first element in the + resource's address (e.g. 'profile' in the address /profile=ha/subsystem=web) + + wildcard -- if 'true', all resources of the given type should be ignored. + + Child elements list the names of specific instances of the given type of resource + that should be ignored. Each element in the list corresponds to the 'value' portion of + the first element in the resource's address (e.g. 'ha' in the address /profile=ha/subsystem=web.) + + + + + + + + + + + + + + The name of a specific instances of a particular type of resource that should be ignored. + The 'name' attribute corresponds to the 'value' portion of the first element in the resource's address + (e.g. 'ha' in the address /profile=ha/subsystem=web.) + + + + + + + + + + + + + + + + + + + + The name for this domain controller discovery option. + + + + + + + The fully qualified class name for the DiscoveryOption implementation. + + + + + + + The module from which the DiscoveryOption implementation should be loaded. If not provided, + the DiscoveryOption implementation must be available from the Host Controller's own module. + + + + + + + + + + The name for this domain controller discovery option. + + + + + + + The remote domain controller's protocol. + + + + + + + The remote domain controller's host name. + + + + + + + The remote domain controller's port. + + + + + + + + + + + + + + + + Indicates each server's writable directories should be grouped under the server's name + in the domain/servers directory. This is the default option. + + + + + + + Indicates each server's writable directories should be grouped based on their "type" + (i.e. "data", "log", "tmp") with directories of a given type for all servers appearing + in the domain level directory for that type, e.g. domain/data/servers/server-name. + + + + + + + + + + + + + + + + + + + + + + + Configuration of the SSLContext used for the connection from the application server back to it's host controller. + + + + + + + + + + + Iif the server last status (STARTED or STOPPED) is to be used to define the value of auto-start. + + + + + + + + + + The protocol to initialise the SSLContext, if 'Default' is specified the JVM wide default SSLContext will be used instead. + + + + + + + The algorithm to use when initialising the TrustManagerFactory. + + If not specified the JVM default is used instead. + + + + + + + The type of the trust store. + + If not specified the JVM default is used instead. + + + + + + + The fully qualified path to the truststore. + + If not specified the no file will be used to initialise the truststore. + + + + + + + The password to open the truststore. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Contains a list of extension modules. + + + + + + + + + + A module that extends the standard capabilities of a domain + or a standalone server. + + + + + The name of the module + + + + + + + + + + + + + + + + + + + + + + + + The name of the server group + + + + + + + The name of the profile this server is running. + + + + + + + Set to true to have servers in the group start gracefully, queuing or cleanly rejecting incoming + requests until the server is fully started. If set to false, the server will pass the request to the + appropriate subsystem for processing, irrespective of whether or not that system is ready. + + + + + + + Set to true to have servers belonging to the server group connect back to the host controller using the + endpoint from their remoting subsystem. The subsystem must be preset for this to + work. + + + + + + + + Contains a list of deployments that have been mapped to a server-group. + + + + + + + + + A deployment that has been mapped to a server group. + + + + + + + Whether the deployment deploy automatically when the server starts up. + + + + + + + + + + + Unique identifier of the deployment. Must be unique across all deployments. + + + + + + Name by which the deployment will be known within a running server.of the deployment. + Does not need to be unique across all deployments in the domain, although it must be unique within + an individual server. For example, two different deployments running on different servers in + the domain could both have a 'runtime-name' of 'example.war', with one having a 'name' + of 'example.war_v1' and another with an 'name' of 'example.war_v2'. + + + + + + + + Contains a list of deployments that have been mapped to a server. + + + + + + + + + A deployment that has been mapped to a server. + + + + + + + + + + + + + + + Whether the deployment deploy automatically when the server starts up. + + + + + + + + + + + The checksum of the content + + + + + + + Archived content found on the filesystem + + + + + + + + + + + + + + + + Exploded content found on the filesystem + + + + + + + + + Contains a list of domain-level deployments + + + + + + + + + Deployment represents anything that can be deployed (e.g. an application such as EJB-JAR, + WAR, EAR, + any kind of standard archive such as RAR or JBoss-specific deployment), + which can be enabled or disabled on a domain level. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The domain controller/server bootstrap configuration + + + + + + + + The URI for bootstrapping a domain server + + + + + + Contains a list of profiles available for use in the domain + + + + + + + + + Contains a list of subsystems + + + + + A profile declaration may include configuration + elements from other namespaces for the subsystems that make up the profile. + + + + + + + Name of the profile + + + + + + A profile may include another profile. Overriding of included profiles is not supported. + + + + + + + + Contains a list of subsystems + + + + + + A profile declaration may include configuration + elements from other namespaces for the subsystems that make up the profile. + + + + + + + + + + Contains a list of subsystems that will be run on the host + + + + + + A profile declaration may include configuration + elements from other namespaces for the subsystems that make up the profile. + + + + + + + + + + + Contains a list of socket binding groups + + + + + + + + + Contains a list of socket configurations + + + + + + + + + + Name of an interface that should be used as the interface for + any sockets that do not explicitly declare one. + + + + + + + A profile may include another profile. Overriding of included profiles is not supported. + + + + + + + + Contains a list of socket configurations + + + + + + + + + + Name of an interface that should be used as the interface for + any sockets that do not explicitly declare one. + + + + + + + Increment to apply to the base port values defined in the + socket group to derive the values to use on this + server. + + + + + + + + Contains a list of socket configurations + + + + + + + + + + Name of an interface that should be used as the interface for + any sockets that do not explicitly declare one. + + + + + + + Increment to apply to the base port values defined in the + socket group to derive the values to use on this + server. + + + + + + + + Configuration information for a socket. + + + + + + Specifies zero or more client mappings for this socket binding. + A client connecting to this socket should use the destination address + specified in the mapping that matches its desired outbound interface. + This allows for advanced network topologies that use either network + address translation, or have bindings on multiple network interfaces + to function. + + Each mapping should be evaluated in declared order, with the first successful + match used to determine the destination. + + + + + + + + + Name of the interface to which the socket should be bound, or, for multicast + sockets, the interface on which it should listen. This should + be one of the declared interfaces. + + + + + + + Number of the port to which the socket should be bound. + + + + + + + Whether the port value should remain fixed even if numerically offsets + are applied to the other sockets in the socket group.. + + + + + + + Multicast address on which the socket should receive multicast + traffic. If unspecified, the socket will not be configured + to receive multicast. + + + + + + + Port on which the socket should receive multicast + traffic. Must be configured if 'multicast-address' is configured. + + + + + + + + + Type definition for a client mapping on a socket binding. A client + mapping specifies how external clients should connect to this + socket's port, provided that the client's outbound interface + match the specified source network value. + + + + + + Source network the client connection binds on. This value is in + the form of ip/netmask. A client should match this value against + the desired client host network interface, and if matched the + client should connect to the corresponding destination values. + + If omitted this mapping should match any interface. + + + + + + + The destination address that a client should connect to if the + source-network matches. This value can either be a hostname or + an IP address. + + + + + + + The destination port that a client should connect to if the + source-network matches. + + If omitted this mapping will reuse the effective socket binding + port. + + + + + + + + Configuration information for an outbound socket. + + + + + + + + + + The name of the outbound socket binding + + + + + + + The name of the interface that should be used for setting up the source address of the + outbound socket. This should be one of the declared interfaces. + + + + + + + + The port number that will be used for setting the source address of the outbound socket. If the + source-interface attribute has been specified and the source-port attribute equals 0 or is absent, + then the system uses an ephemeral port while binding the socket to a source address. + + + + + + + Whether the source-port value should remain fixed even if the socket binding group specifies + a port offset + + + + + + + + + + The remote server address to which the outbound socket has to be connect. + The address can be either an IP address of the host server of the hostname of the server + + + + + + + The remote port to which the outbound socket has to connect. + + + + + + + + + + + + + + + + + + + + The reference to a socket binding that has to be used as the destination for the outbound + socket binding. This socket binding name should belong to the same socket binding group + to which this local destination client socket belongs. + + + + + + + + + + The socket group to use for the server group or server. + + + + + + + Increment to apply to the base port values defined in the + referenced socket group to derive the values to use on this + server. + + + + + + + + + + + + + + + + + A list of named network interfaces. The interfaces may or may + not be fully specified (i.e. include criteria on how to determine + their IP address.) + + + + + + + + + + + + A named network interface, but without any criteria + for determining the IP address to associate with that interface. + Acts as a placeholder in the model (e.g. at the domain level) + until a fully specified interface definition is applied at a + lower level (e.g. at the server level, where available addresses + are known.) + + + + + + + + + + A list of fully specified named network interfaces. + + + + + + + + + + + A named network interface, along with required criteria + for determining the IP address to associate with that interface. + + + + + + + + + + A set of criteria that can be used at runtime to determine + what IP address to use for an interface. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Either an IP address in IPv6 or IPv4 dotted decimal notation, + or a hostname that can be resolved to an IP address. + + + + + + + + + + The name of a network interface (e.g. eth0, eth1, lo). + + + + + + + + + + A regular expression against which the names of the network + interfaces available on the machine can be matched to find + an acceptable interface. + + + + + + + + + + A network IP address and the number of bits in the + address' network prefix, written in "slash notation"; + e.g. "192.168.0.0/16". + + + + + + + + + + + + + + + + + + + + + + + + + + + Empty element indicating that part of the selection criteria + for an interface should be whether or not it is a loopback + interface. + + + + + + + + A loopback address that may not actually be configured on the machine's loopback interface. + Differs from inet-addressType in that the given value will be used even if no NIC can + be found that has the IP address associated with it. + + + + + + An IP address in IPv6 or IPv4 dotted decimal notation. + + + + + + + + + Empty element indicating that part of the selection criteria + for an interface should be whether or not it supports + multicast. + + + + + + + + Empty element indicating that part of the selection criteria + for an interface should be whether or not it is a point-to-point + interface. + + + + + + + + Empty element indicating that part of the selection criteria + for an interface should be whether or not it is currently up. + + + + + + + + Empty element indicating that part of the selection criteria + for an interface should be whether or not it is a virtual + interface. + + + + + + + + Empty element indicating that part of the selection criteria + for an interface should be whether or not it has a publicly + routable address. + + + + + + + + Empty element indicating that part of the selection criteria + for an interface should be whether or not an address associated + with it is site-local. + + + + + + + + Empty element indicating that part of the selection criteria + for an interface should be whether or not an address associated + with it is link-local. + + + + + + + + Empty element indicating that sockets using this interface + should be bound to a wildcard address. The IPv6 wildcard + address (::) will be used unless the java.net.preferIpV4Stack + system property is set to true, in which case the IPv4 + wildcard address (0.0.0.0) will be used. If a socket is + bound to an IPv6 anylocal address on a dual-stack machine, + it can accept both IPv6 and IPv4 traffic; if it is bound to + an IPv4 (IPv4-mapped) anylocal address, it can only accept + IPv4 traffic. + + + + + + + Configuration information for a socket. + + + + + + Name of the interface to which the socket should be bound, or, for multicast + sockets, the interface on which it should listen. This should + be one of the declared interfaces. + + + + + + + Number of the port to which the socket should be bound. + + + + + + + Whether the port value should remain fixed even if numerically offsets + are applied to the other sockets in the socket group.. + + + + + + + Multicast address on which the socket should receive multicast + traffic. If unspecified, the socket will not be configured + to receive multicast. + + + + + + + Port on which the socket should receive multicast + traffic. If unspecified, the socket will not be configured + to receive multicast. + + + + + + + + + + A list of named filesystem paths. The paths may or may + not be fully specified (i.e. include the actual paths.) + + + + + + + + + + + A named filesystem path, but without a requirement to specify + the actual path. If no actual path is specified, acts as a + as a placeholder in the model (e.g. at the domain level) + until a fully specified path definition is applied at a + lower level (e.g. at the host level, where available addresses + are known.) + + + + + + + + The name of the path. Cannot be one of the standard fixed paths + provided by the system: + + jboss.home.dir - the root directory of the JBoss AS distribution + user.home - user's home directory + user.dir - user's current working directory + java.home - java installation directory + jboss.server.base.dir - root directory for an individual server + instance + + Note that the system provides other standard paths that can be + overridden by declaring them in the configuration file. See + the 'relative-to' attribute documentation for a complete + list of standard paths. + + + + + + + + + + + + The actual filesystem path. Treated as an absolute path, unless the + 'relative-to' attribute is specified, in which case the value + is treated as relative to that path. + + If treated as an absolute path, the actual runtime pathname specified + by the value of this attribute will be determined as follows: + + If this value is already absolute, then the value is directly + used. Otherwise the runtime pathname is resolved in a + system-dependent way. On UNIX systems, a relative pathname is + made absolute by resolving it against the current user directory. + On Microsoft Windows systems, a relative pathname is made absolute + by resolving it against the current directory of the drive named by the + pathname, if any; if not, it is resolved against the current user + directory. + + + + + + + + + + + + The name of another previously named path, or of one of the + standard paths provided by the system. If 'relative-to' is + provided, the value of the 'path' attribute is treated as + relative to the path specified by this attribute. The standard + paths provided by the system include: + + jboss.home.dir - the root directory of the JBoss AS distribution + user.home - user's home directory + user.dir - user's current working directory + java.home - java installation directory + jboss.server.base.dir - root directory for an individual server + instance + jboss.server.config.dir - directory in which server configuration + files are stored. + jboss.server.data.dir - directory the server will use for persistent + data file storage + jboss.server.log.dir - directory the server will use for + log file storage + jboss.server.temp.dir - directory the server will use for + temporary file storage + jboss.domain.servers.dir - directory under which a host controller + will create the working area for + individual server instances + + + + + + + + + A list of named filesystem paths. + + + + + + + + + + + A named filesystem path. + + + + + + The name of the path. Cannot be one of the standard fixed paths + provided by the system: + + jboss.home.dir - the root directory of the JBoss AS distribution + user.home - user's home directory + user.dir - user's current working directory + java.home - java installation directory + jboss.server.base.dir - root directory for an individual server + instance + + Note that the system provides other standard paths that can be + overridden by declaring them in the configuration file. See + the 'relative-to' attribute documentation for a complete + list of standard paths. + + + + + + + The actual filesystem path. Treated as an absolute path, unless the + 'relative-to' attribute is specified, in which case the value + is treated as relative to that path. + + If treated as an absolute path, the actual runtime pathname specified + by the value of this attribute will be determined as follows: + + If this value is already absolute, then the value is directly + used. Otherwise the runtime pathname is resolved in a + system-dependent way. On UNIX systems, a relative pathname is + made absolute by resolving it against the current user directory. + On Microsoft Windows systems, a relative pathname is made absolute + by resolving it against the current directory of the drive named by the + pathname, if any; if not, it is resolved against the current user + directory. + + Note relative path declarations have to use '/' as file separator. + + + + + + + + + + + + The name of another previously named path, or of one of the + standard paths provided by the system. If 'relative-to' is + provided, the value of the 'path' attribute is treated as + relative to the path specified by this attribute. The standard + paths provided by the system include: + + jboss.home.dir - the root directory of the JBoss AS distribution + user.home - user's home directory + user.dir - user's current working directory + java.home - java installation directory + jboss.server.base.dir - root directory for an individual server + instance + jboss.server.config.dir - directory in which server configuration + files are stored. + jboss.server.data.dir - directory the server will use for persistent + data file storage + jboss.server.log.dir - directory the server will use for + log file storage + jboss.server.temp.dir - directory the server will use for + temporary file storage + jboss.domain.servers.dir - directory under which a host controller + will create the working area for + individual server instances + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Allows the full set of JVM options to be set via the jvm schema elements + + + + + Sets a subset of the JVM options via the jvm schema elements + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Initial JVM heap size + + + + + Maximum JVM heap size + + + + + + + + + + + + + + JVM option value + + + + + + + + JVM agent lib value + + + + + + + + JVM agent path value + + + + + + + + JVM javaagent value + + + + + + + + + + + + + + JBoss Modules option value + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Definition of the security domain to use to obtain the current identity from. + + + + + + Reference to the security domain to use to obtain the current identity. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration of if a classification's read is sensitive + + + + + + + Configuration of if a classification's write is sensitive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration of if a classification's addressability is sensitive + + + + + + + The name of the constraint, must be unique for each name + + + + + + + 'core' or the name of the subsystem defining the constraint + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the constraint, must be unique for each name + + + + + + + 'core' or the name of the subsystem defining the constraint + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . (for example, EAP6.2 or EAP7.3) + * WildFly. (for example WildFly10.0 or WildFly10.1) + + The corresponding kernel management API version is defined in the enum + org.jboss.as.domain.controller.resources.HostExcludeResourceDefinition.KnownRelease. + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 17970228bd600409c3f45b7bcc578ed19aa72e57 Mon Sep 17 00:00:00 2001 From: Yeray Borges Date: Fri, 20 Feb 2026 21:27:38 +0000 Subject: [PATCH 3/6] [WFCORE-7247] Add redactable flag to audit logger and core management configuration changes --- .../controller/AbstractOperationContext.java | 88 +- .../ConfigurationChangesCollector.java | 19 +- .../as/controller/OperationDefinition.java | 37 + .../as/controller/audit/AuditLogger.java | 13 +- .../controller/audit/ManagedAuditLogger.java | 11 +- .../audit/ManagedAuditLoggerImpl.java | 40 +- .../ModelDescriptionConstants.java | 1 + .../as/controller/parsing/Attribute.java | 1 + .../controller/registry/AttributeAccess.java | 7 +- .../as/domain-management/main/module.xml | 1 + ...ConfigurationChangeResourceDefinition.java | 36 +- .../CoreManagementSubsystemSchema.java | 9 +- .../management/LocalDescriptions.properties | 1 + domain-management/pom.xml | 5 + .../AuditLogLoggerResourceDefinition.java | 40 +- .../management/parsing/AuditLogXml.java | 24 +- .../management/parsing/AuditLogXml_6.java | 841 ++++++++++++++++++ .../_private/LocalDescriptions.properties | 1 + .../host/controller/parsing/HostXml_20.java | 4 +- .../as/server/parsing/StandaloneXml_20.java | 2 +- 20 files changed, 1156 insertions(+), 25 deletions(-) create mode 100644 domain-management/src/main/java/org/jboss/as/domain/management/parsing/AuditLogXml_6.java diff --git a/controller/src/main/java/org/jboss/as/controller/AbstractOperationContext.java b/controller/src/main/java/org/jboss/as/controller/AbstractOperationContext.java index c94ff83b8cd..1a15cf4dcd5 100644 --- a/controller/src/main/java/org/jboss/as/controller/AbstractOperationContext.java +++ b/controller/src/main/java/org/jboss/as/controller/AbstractOperationContext.java @@ -5,6 +5,7 @@ package org.jboss.as.controller; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ALLOW_RESOURCE_SERVICE_RESTART; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CANCELLED; @@ -39,6 +40,9 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -47,6 +51,7 @@ import java.util.EnumMap; import java.util.EnumSet; import java.util.HashSet; +import java.util.HexFormat; import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.List; @@ -159,7 +164,7 @@ abstract class AbstractOperationContext implements OperationContext, AutoCloseab ModelNode initialOperation; /** Operations that were added by the controller, before execution started */ - private final List controllerOperations = new ArrayList(2); + private final List controllerOperations = new ArrayList<>(2); private boolean auditLogged; private final AuditLogger auditLogger; private final ModelControllerImpl controller; @@ -453,6 +458,7 @@ public void close() { if (modifiedResourcesForModelValidation != null) { modifiedResourcesForModelValidation.clear(); } + this.controllerOperations.clear(); } /** @@ -475,7 +481,7 @@ ResultAction executeOperation() { } } catch (RuntimeException e) { handleUncaughtException(e); - ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionException(e, controllerOperations); + ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionException(e, filterControllerOperations(true)); } finally { // On failure close any attached response streams if (resultAction != ResultAction.KEEP && !isBooting()) { @@ -667,7 +673,8 @@ void logAuditRecord() { accessContext == null ? null : accessContext.getAccessMechanism(), accessContext == null ? null : accessContext.getRemoteAddress(), getModel(), - controllerOperations); + filterControllerOperations(false), + filterControllerOperations(true)); auditLogged = true; } catch (Exception e) { ControllerLogger.MGMT_OP_LOGGER.failedToUpdateAuditLog(e); @@ -677,10 +684,54 @@ void logAuditRecord() { /** * Record an operation added before execution began (i.e. added by the controller and not by a step) + * * @param operation the operation */ private void recordControllerOperation(ModelNode operation) { - controllerOperations.add(operation.clone()); // clone so we don't log op nodes mutated during execution + ModelNode op = operation.clone(); + ModelNode redactedOp = operation.clone(); + + ImmutableManagementResourceRegistration mrr = getRootResourceRegistration(); + OperationEntry operationEntry = mrr.getOperationEntry(PathAddress.pathAddress(op.get(ADDRESS)), op.get(OP).asString()); + if (operationEntry != null) { + Set redactableNames = operationEntry.getOperationDefinition().getRedactableAttributeNames(); + if (!redactableNames.isEmpty()) { + for (String redactableName : redactableNames) { + redactValues(redactedOp, redactableName); + } + } + } + controllerOperations.add(new RecordedOperation(op, redactedOp)); + } + + private void redactValues(ModelNode node, String redactableName) { + if (!node.isDefined()) { + return; + } + switch (node.getType()) { + case OBJECT -> { + if (node.hasDefined(redactableName)) { + node.get(redactableName).set(sha256(node.get(redactableName).asString())); + } + for (String key : node.keys()) { + redactValues(node.get(key), redactableName); + } + } + case LIST -> { + for (ModelNode element : node.asList()) { + redactValues(element, redactableName); + } + } + case PROPERTY -> { + Property prop = node.asProperty(); + if (prop.getName().equals(redactableName)) { + node.set(prop.getName(), new ModelNode(sha256(prop.getValue().asString()))); + } else { + redactValues(prop.getValue(), redactableName); + } + } + default -> { /* leaf node */ } + } } void recordWriteLock() { @@ -697,7 +748,7 @@ void trackConfigurationChange() { accessContext == null ? null : accessContext.getDomainUuid(), accessContext == null ? null : accessContext.getAccessMechanism(), accessContext == null ? null : accessContext.getRemoteAddress(), - controllerOperations)); + filterControllerOperations(configurationChangesCollector.isRedacted()))); } catch (Exception e) { ControllerLogger.MGMT_OP_LOGGER.failedToUpdateAuditLog(e); } @@ -839,7 +890,7 @@ private boolean tryStageCompleted(Stage currentStage) { if (!initialResponse.hasDefined(FAILURE_DESCRIPTION)) { initialResponse.get(FAILURE_DESCRIPTION).set(ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionFailureDescription(e)); } - ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionException(e, controllerOperations); + ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionException(e, filterControllerOperations(true)); } return result; } @@ -1821,4 +1872,29 @@ public void close() throws IOException { StreamUtils.safeClose(stream); } } + + private record RecordedOperation(ModelNode op, ModelNode redactedOp) { + @Override + public String toString() { + return redactedOp.toString(); + } + } + + private List filterControllerOperations(boolean redacted) { + return controllerOperations.stream() + .map(ro -> redacted ? ro.redactedOp : ro.op) + .toList(); + } + + private String sha256(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] encodedHash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(encodedHash); + } catch (NoSuchAlgorithmException e) { + // SHA-256 should be guaranteed to be available in Java environments, + // but in case of an error, provides a fallback + return "*".repeat(Math.min(input.length(), 6)); + } + } } diff --git a/controller/src/main/java/org/jboss/as/controller/ConfigurationChangesCollector.java b/controller/src/main/java/org/jboss/as/controller/ConfigurationChangesCollector.java index 4380fff2cae..4ca34bd7777 100644 --- a/controller/src/main/java/org/jboss/as/controller/ConfigurationChangesCollector.java +++ b/controller/src/main/java/org/jboss/as/controller/ConfigurationChangesCollector.java @@ -43,10 +43,15 @@ public interface ConfigurationChangesCollector { void deactivate(); - static class ConfigurationChangesCollectorImpl implements ConfigurationChangesCollector { + boolean isRedacted(); + + void setRedacted(boolean redacted); + + class ConfigurationChangesCollectorImpl implements ConfigurationChangesCollector { private final Deque history = new ArrayDeque<>(); private int maxHistory; + private boolean redacted; private ConfigurationChangesCollectorImpl(final int maxHistory) { this.maxHistory = maxHistory; @@ -102,9 +107,19 @@ public void deactivate() { this.history.clear(); } } + + @Override + public void setRedacted(boolean redacted) { + this.redacted = redacted; + } + + @Override + public boolean isRedacted() { + return this.redacted; + } } - static final class ConfigurationChange { + final class ConfigurationChange { private static final DateTimeFormatter DATE_FORMAT = new DateTimeFormatterBuilder().appendInstant(3).toFormatter(Locale.ENGLISH); private final OperationContext.ResultAction resultAction; diff --git a/controller/src/main/java/org/jboss/as/controller/OperationDefinition.java b/controller/src/main/java/org/jboss/as/controller/OperationDefinition.java index 0f0bded08e1..08da5a3a1cc 100644 --- a/controller/src/main/java/org/jboss/as/controller/OperationDefinition.java +++ b/controller/src/main/java/org/jboss/as/controller/OperationDefinition.java @@ -9,10 +9,12 @@ import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; import org.jboss.as.controller.access.management.AccessConstraintDefinition; +import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.descriptions.DescriptionProvider; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.version.Stability; @@ -115,4 +117,39 @@ public AttributeDefinition[] getReplyParameters() { return replyParameters; } + /** + * Returns the names of the attributes that are susceptible to be redacted. This method + * recursively inspects all parameters, including nested attributes within complex types + * ({@link ObjectTypeAttributeDefinition}, {@link ObjectListAttributeDefinition}, + * {@link ObjectMapAttributeDefinition}), collecting the names of those that have the + * {@link AttributeAccess.Flag#REDACTABLE} flag set. + * + * @return a set of attribute names requiring redaction, or an empty set if none + */ + public Set getRedactableAttributeNames() { + Set result = new HashSet<>(); + for (AttributeDefinition parameter : parameters) { + collectRedactableNames(parameter, result); + } + return Collections.unmodifiableSet(result); + } + + private void collectRedactableNames(AttributeDefinition attr, Set result) { + if (attr instanceof ObjectTypeAttributeDefinition objectType) { + for (AttributeDefinition nested : objectType.getValueTypes()) { + collectRedactableNames(nested, result); + } + } else if (attr instanceof ObjectListAttributeDefinition listType) { + for (AttributeDefinition nested : listType.getValueType().getValueTypes()) { + collectRedactableNames(nested, result); + } + } else if (attr instanceof ObjectMapAttributeDefinition mapType) { + for (AttributeDefinition nested : mapType.getValueType().getValueTypes()) { + collectRedactableNames(nested, result); + } + } else if (attr.getFlags().contains(AttributeAccess.Flag.REDACTABLE)) { + result.add(attr.getName()); + } + } + } diff --git a/controller/src/main/java/org/jboss/as/controller/audit/AuditLogger.java b/controller/src/main/java/org/jboss/as/controller/audit/AuditLogger.java index 772f76507d9..8e0eb941ed2 100644 --- a/controller/src/main/java/org/jboss/as/controller/audit/AuditLogger.java +++ b/controller/src/main/java/org/jboss/as/controller/audit/AuditLogger.java @@ -37,7 +37,7 @@ enum Status { } void log(boolean readOnly, OperationContext.ResultAction resultAction, String userId, String domainUUID, - AccessMechanism accessMechanism, InetAddress remoteAddress, final Resource resultantModel, List operations); + AccessMechanism accessMechanism, InetAddress remoteAddress, final Resource resultantModel, List operations, List redactedOperations); void logJmxMethodAccess(final boolean readOnly, final String userId, final String domainUUID, final AccessMechanism accessMechanism, InetAddress remoteAddress, final String methodName, final String[] methodSignature, final Object[] methodParams, final Throwable error); @@ -54,6 +54,15 @@ public boolean isLogReadOnly() { public void setLogReadOnly(boolean logReadOnly) { } + @Override + public boolean isRedacted() { + return false; + } + + @Override + public void setRedacted(boolean maskOperationParameters) { + } + @Override public boolean isLogBoot() { return false; @@ -68,7 +77,7 @@ public Status getLoggerStatus() { } @Override - public void log(boolean readOnly, OperationContext.ResultAction resultAction, String userId, String domainUUID, AccessMechanism accessMechanism, InetAddress remoteAddress, Resource resultantModel, List operations) { + public void log(boolean readOnly, OperationContext.ResultAction resultAction, String userId, String domainUUID, AccessMechanism accessMechanism, InetAddress remoteAddress, Resource resultantModel, List operations, List redactedOperations) { } @Override diff --git a/controller/src/main/java/org/jboss/as/controller/audit/ManagedAuditLogger.java b/controller/src/main/java/org/jboss/as/controller/audit/ManagedAuditLogger.java index 14a73c021cb..27a94e441d7 100644 --- a/controller/src/main/java/org/jboss/as/controller/audit/ManagedAuditLogger.java +++ b/controller/src/main/java/org/jboss/as/controller/audit/ManagedAuditLogger.java @@ -29,10 +29,19 @@ public interface ManagedAuditLogger extends AuditLogger { /** * Set whether to log read-only operations * - * @param logReadOnly wheter to log read-only operations + * @param logReadOnly whether to log read-only operations */ void setLogReadOnly(boolean logReadOnly); + boolean isRedacted(); + + /** + * Set whether to redact the operation parameters that has been flagged as redactable + * + * @param redacted whether to log redact operation parameters + */ + void setRedacted(boolean redacted); + /** * Get whether this audit logger logs operations on boot * diff --git a/controller/src/main/java/org/jboss/as/controller/audit/ManagedAuditLoggerImpl.java b/controller/src/main/java/org/jboss/as/controller/audit/ManagedAuditLoggerImpl.java index 955861c9e2c..38b31f3d6bc 100644 --- a/controller/src/main/java/org/jboss/as/controller/audit/ManagedAuditLoggerImpl.java +++ b/controller/src/main/java/org/jboss/as/controller/audit/ManagedAuditLoggerImpl.java @@ -58,7 +58,7 @@ public class ManagedAuditLoggerImpl implements ManagedAuditLogger, ManagedAuditL public ManagedAuditLoggerImpl(String asVersion, boolean server) { config = new CoreAuditLogConfiguration(asVersion, server); - childImpls = new ArrayList(); + childImpls = new ArrayList<>(); } private ManagedAuditLoggerImpl(ManagedAuditLoggerImpl src, boolean manualCommit) { @@ -69,7 +69,7 @@ private ManagedAuditLoggerImpl(ManagedAuditLoggerImpl src, boolean manualCommit) @Override public void log(boolean readOnly, ResultAction resultAction, String userId, String domainUUID, AccessMechanism accessMechanism, - InetAddress remoteAddress, Resource resultantModel, List operations) { + InetAddress remoteAddress, Resource resultantModel, List operations, List redactedOperations) { if (runDisabledFastPath.get()) return; @@ -78,6 +78,9 @@ public void log(boolean readOnly, ResultAction resultAction, String userId, Stri if (skipLogging(readOnly)) { return; } + if (this.isRedacted()) { + operations = redactedOperations; + } storeLogItem( AuditLogItem.createModelControllerItem(config.getAsVersion(), readOnly, config.isBooting(), resultAction, userId, domainUUID, accessMechanism, remoteAddress, operations)); @@ -149,6 +152,26 @@ public void setLogReadOnly(boolean logReadOnly) { } } + @Override + public boolean isRedacted() { + config.lock(); + try { + return config.isRedacted(); + } finally { + config.unlock(); + } + } + + @Override + public void setRedacted(boolean maskOperationParameters) { + config.lock(); + try { + config.setRedacted(maskOperationParameters); + } finally { + config.unlock(); + } + } + public boolean isLogBoot() { config.lock(); try { @@ -524,6 +547,7 @@ abstract static class ManagedAuditLogConfiguration { private volatile Status status = Status.QUEUEING; private volatile boolean logBoot = true; private volatile boolean logReadOnly; + private volatile boolean redacted; /** Guarded by auditLock - the configured handlers we should use */ @@ -607,6 +631,16 @@ void setLogReadOnly(boolean logReadOnly) { this.logReadOnly = logReadOnly; } + /** Call with lock taken */ + public boolean isRedacted(){ + return this.redacted; + } + + /** Call with lock taken */ + public void setRedacted(boolean redacted) { + this.redacted = redacted; + } + /** Call with lock taken */ boolean isLogBoot() { return logBoot; @@ -677,7 +711,7 @@ void recycleHandler(String name) { /** * The configuration for other audit loggers, e.g. jmx */ - private static class NewAuditLogConfiguration extends ManagedAuditLogConfiguration { + private static class NewAuditLogConfiguration extends ManagedAuditLogConfiguration { NewAuditLogConfiguration(CoreAuditLogConfiguration core, boolean manualCommit) { super(core.sharedConfiguration, false, manualCommit); diff --git a/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java b/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java index 04f524ffb84..8f02ad26d17 100644 --- a/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java +++ b/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java @@ -505,6 +505,7 @@ public class ModelDescriptionConstants { public static final String SUSPEND_TIMEOUT = "suspend-timeout"; public static final String OPERATION_REQUIRES_RELOAD = "operation-requires-reload"; public static final String OPERATION_REQUIRES_RESTART = "operation-requires-restart"; + public static final String REDACTED = "redacted"; public static final String RELOAD_SERVERS = "reload-servers"; public static final String REMOVE_CONTENT = "remove-content"; public static final String RESTART_SERVERS = "restart-servers"; diff --git a/controller/src/main/java/org/jboss/as/controller/parsing/Attribute.java b/controller/src/main/java/org/jboss/as/controller/parsing/Attribute.java index f75676ec590..271b5a9e489 100644 --- a/controller/src/main/java/org/jboss/as/controller/parsing/Attribute.java +++ b/controller/src/main/java/org/jboss/as/controller/parsing/Attribute.java @@ -145,6 +145,7 @@ public enum Attribute { REALM("realm"), RECONNECT_TIMEOUT("reconnect-timeout"), RECURSIVE("recursive"), + REDACTED("redacted"), REF("ref"), REFERRALS("referrals"), REGULAR_EXPRESSION("regular-expression"), diff --git a/controller/src/main/java/org/jboss/as/controller/registry/AttributeAccess.java b/controller/src/main/java/org/jboss/as/controller/registry/AttributeAccess.java index cf1daf31471..ef9ff621e4b 100644 --- a/controller/src/main/java/org/jboss/as/controller/registry/AttributeAccess.java +++ b/controller/src/main/java/org/jboss/as/controller/registry/AttributeAccess.java @@ -155,7 +155,12 @@ public enum Flag implements Predicate { * * {@link #GAUGE_METRIC} and {@link #COUNTER_METRIC} are mutually exclusive. */ - COUNTER_METRIC; + COUNTER_METRIC, + + /** + * The attribute is susceptible to be redacted when it is shown in the management audit log or configuration history. + */ + REDACTABLE; private static final Map, Set> flagSets = new ConcurrentHashMap<>(16); public static Set immutableSetOf(AttributeAccess.Flag... flags) { diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/domain-management/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/domain-management/main/module.xml index 2bc4ef4be51..6abc349c573 100644 --- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/domain-management/main/module.xml +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/domain-management/main/module.xml @@ -38,5 +38,6 @@ + diff --git a/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/ConfigurationChangeResourceDefinition.java b/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/ConfigurationChangeResourceDefinition.java index 96e7680e589..e08f16c4403 100644 --- a/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/ConfigurationChangeResourceDefinition.java +++ b/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/ConfigurationChangeResourceDefinition.java @@ -42,6 +42,7 @@ import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.controller.registry.Resource; +import org.jboss.as.version.Stability; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; @@ -56,6 +57,14 @@ public class ConfigurationChangeResourceDefinition extends PersistentResourceDef ModelDescriptionConstants.MAX_HISTORY, ModelType.INT, true) .setDefaultValue(new ModelNode(10)) .build(); + + public static final SimpleAttributeDefinition REDACTED = SimpleAttributeDefinitionBuilder.create( + ModelDescriptionConstants.REDACTED, ModelType.BOOLEAN, true) + .setAllowExpression(true) + .setStability(Stability.COMMUNITY) + .setDefaultValue(ModelNode.TRUE) + .build(); + public static final PathElement PATH = PathElement.pathElement(SERVICE, CONFIGURATION_CHANGES); public static final String OPERATION_NAME = "list-changes"; @@ -82,6 +91,7 @@ public void registerOperations(ManagementResourceRegistration resourceRegistrati public void registerAttributes(ManagementResourceRegistration resourceRegistration) { super.registerAttributes(resourceRegistration); resourceRegistration.registerReadWriteAttribute(MAX_HISTORY, null, new MaxHistoryWriteHandler(ConfigurationChangesCollector.INSTANCE)); + resourceRegistration.registerReadWriteAttribute(REDACTED, null, new RedactedWriteHandler(ConfigurationChangesCollector.INSTANCE)); } @Override @@ -97,7 +107,9 @@ protected void performRuntime(OperationContext context, ModelNode operation, Res super.performRuntime(context, operation, resource); context.getServiceTarget().addService(CONFIGURATION_CHANGES_CAPABILITY.getCapabilityServiceName()).install(); ModelNode maxHistory = MAX_HISTORY.resolveModelAttribute(context, operation); + ModelNode redacted = REDACTED.resolveModelAttribute(context, operation); ConfigurationChangesCollector.INSTANCE.setMaxHistory(maxHistory.asInt()); + ConfigurationChangesCollector.INSTANCE.setRedacted(redacted.asBoolean()); } @Override @@ -134,17 +146,37 @@ private MaxHistoryWriteHandler(ConfigurationChangesCollector collector) { } @Override - protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode resolvedValue, ModelNode currentValue, HandbackHolder handbackHolder) throws OperationFailedException { + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode resolvedValue, ModelNode currentValue, HandbackHolder handbackHolder) { collector.setMaxHistory(resolvedValue.asInt()); return false; } @Override - protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode valueToRestore, ModelNode valueToRevert, Integer handback) throws OperationFailedException { + protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode valueToRestore, ModelNode valueToRevert, Integer handback) { collector.setMaxHistory(valueToRestore.asInt()); } } + private static class RedactedWriteHandler extends AbstractWriteAttributeHandler { + + private final ConfigurationChangesCollector collector; + + private RedactedWriteHandler(ConfigurationChangesCollector collector) { + this.collector = collector; + } + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode resolvedValue, ModelNode currentValue, HandbackHolder handbackHolder) { + collector.setRedacted(resolvedValue.asBoolean()); + return false; + } + + @Override + protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode valueToRestore, ModelNode valueToRevert, Boolean handback) { + collector.setRedacted(valueToRestore.asBoolean()); + } + } + private static class ConfigurationChangesHandler extends AbstractRuntimeOnlyHandler { private static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME, diff --git a/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/CoreManagementSubsystemSchema.java b/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/CoreManagementSubsystemSchema.java index d2c0241369d..83061204ccb 100644 --- a/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/CoreManagementSubsystemSchema.java +++ b/core-management/core-management-subsystem/src/main/java/org/wildfly/extension/core/management/CoreManagementSubsystemSchema.java @@ -53,10 +53,13 @@ public VersionedNamespace getNamespac public PersistentResourceXMLDescription getXMLDescription() { PersistentResourceXMLDescription.Factory factory = PersistentResourceXMLDescription.factory(this); PersistentResourceXMLDescription.Builder builder = factory.builder(CoreManagementExtension.SUBSYSTEM_PATH); - builder.addChild( + PersistentResourceXMLDescription.Builder configChanges = factory.builder(ConfigurationChangeResourceDefinition.PATH) - .addAttribute(ConfigurationChangeResourceDefinition.MAX_HISTORY) - .build()); + .addAttribute(ConfigurationChangeResourceDefinition.MAX_HISTORY); + if (this.since(VERSION_1_0_COMMUNITY) && !this.equals(VERSION_1_0_PREVIEW)) { + configChanges.addAttribute(ConfigurationChangeResourceDefinition.REDACTED); + } + builder.addChild(configChanges.build()); builder.addChild( factory.builder(UnstableApiAnnotationResourceDefinition.RESOURCE_REGISTRATION) .addAttribute(UnstableApiAnnotationResourceDefinition.LEVEL) diff --git a/core-management/core-management-subsystem/src/main/resources/org/wildfly/extension/core/management/LocalDescriptions.properties b/core-management/core-management-subsystem/src/main/resources/org/wildfly/extension/core/management/LocalDescriptions.properties index 5ed40797c43..6d3aa9202ac 100644 --- a/core-management/core-management-subsystem/src/main/resources/org/wildfly/extension/core/management/LocalDescriptions.properties +++ b/core-management/core-management-subsystem/src/main/resources/org/wildfly/extension/core/management/LocalDescriptions.properties @@ -14,6 +14,7 @@ core-management.configuration-changes.add=Add the history for configuration chan core-management.configuration-changes.deprecated=The configuration changes service at the root of a Domain Controller is deprecated and may be removed or moved in future versions. core-management.configuration-changes.remove=Remove the configuration changes and clear the history. core-management.configuration-changes.max-history=The maximum number of configuration changes stored in history. +core-management.configuration-changes.redacted=Whether sensitive data in configuration changes should be redacted. core-management.configuration-changes.list-changes=List the last configuration changes. core-management.unstable-api-annotations=Service to configure how we deal with finding annotations indicating unstable API in user code. diff --git a/domain-management/pom.xml b/domain-management/pom.xml index 26b82e5263e..6c8a71bc6be 100644 --- a/domain-management/pom.xml +++ b/domain-management/pom.xml @@ -104,6 +104,11 @@ test + + org.wildfly.core + wildfly-version + + org.wildfly.security wildfly-elytron-auth diff --git a/domain-management/src/main/java/org/jboss/as/domain/management/audit/AuditLogLoggerResourceDefinition.java b/domain-management/src/main/java/org/jboss/as/domain/management/audit/AuditLogLoggerResourceDefinition.java index cc40e2a743f..a0882cf19b5 100644 --- a/domain-management/src/main/java/org/jboss/as/domain/management/audit/AuditLogLoggerResourceDefinition.java +++ b/domain-management/src/main/java/org/jboss/as/domain/management/audit/AuditLogLoggerResourceDefinition.java @@ -27,6 +27,7 @@ import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.Resource; import org.jboss.as.domain.management._private.DomainManagementResolver; +import org.jboss.as.version.Stability; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.dmr.Property; @@ -46,6 +47,11 @@ public class AuditLogLoggerResourceDefinition extends SimpleResourceDefinition { .setAllowExpression(true) .setDefaultValue(ModelNode.TRUE).build(); + public static final SimpleAttributeDefinition REDACTED = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.REDACTED, ModelType.BOOLEAN, true) + .setAllowExpression(true) + .setStability(Stability.COMMUNITY) + .setDefaultValue(ModelNode.TRUE) + .build(); public static final SimpleAttributeDefinition LOG_READ_ONLY = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.LOG_READ_ONLY, ModelType.BOOLEAN, true) .setAllowExpression(true) @@ -55,7 +61,7 @@ public class AuditLogLoggerResourceDefinition extends SimpleResourceDefinition { .setAllowExpression(true) .setDefaultValue(ModelNode.TRUE).build(); - static final List ATTRIBUTE_DEFINITIONS = Arrays.asList(LOG_BOOT, LOG_READ_ONLY, ENABLED); + static final List ATTRIBUTE_DEFINITIONS = Arrays.asList(LOG_BOOT, LOG_READ_ONLY, ENABLED, REDACTED); private final ManagedAuditLogger auditLogger; @@ -81,6 +87,7 @@ public void registerAttributes(ManagementResourceRegistration resourceRegistrati resourceRegistration.registerReadWriteAttribute(LOG_READ_ONLY, null, new AuditLogReadOnlyWriteAttributeHandler(auditLogger)); resourceRegistration.registerReadWriteAttribute(ENABLED, null, new AuditLogEnabledWriteAttributeHandler(auditLogger)); + resourceRegistration.registerReadWriteAttribute(REDACTED, null, new AuditLogRedactedWriteAttributeHandler(auditLogger)); } @Override @@ -132,6 +139,7 @@ public void execute(final OperationContext context, final ModelNode operation) t auditLoggerProvider.setLogBoot(AuditLogLoggerResourceDefinition.LOG_BOOT.resolveModelAttribute(context, model).asBoolean()); auditLoggerProvider.setLogReadOnly(AuditLogLoggerResourceDefinition.LOG_READ_ONLY.resolveModelAttribute(context, model).asBoolean()); + auditLoggerProvider.setRedacted(AuditLogLoggerResourceDefinition.REDACTED.resolveModelAttribute(context, model).asBoolean()); boolean enabled = AuditLogLoggerResourceDefinition.ENABLED.resolveModelAttribute(context, model).asBoolean(); final AuditLogger.Status status = enabled ? AuditLogger.Status.LOGGING : AuditLogger.Status.DISABLED; context.completeStep((OperationContext.ResultAction resultAction, OperationContext context1, ModelNode operation1) -> { @@ -247,4 +255,34 @@ protected void revertUpdateToRuntime(OperationContext context, ModelNode operati } } + private static class AuditLogRedactedWriteAttributeHandler extends AbstractWriteAttributeHandler { + + private final ManagedAuditLogger auditLogger; + + AuditLogRedactedWriteAttributeHandler(ManagedAuditLogger auditLogger) { + this.auditLogger = auditLogger; + } + + @Override + protected boolean requiresRuntime(OperationContext context) { + return auditLogger != null; + } + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode resolvedValue, ModelNode currentValue, + HandbackHolder handbackHolder) { + handbackHolder.setHandback(auditLogger.isRedacted()); + auditLogger.setRedacted(resolvedValue.asBoolean()); + return false; + } + + @Override + protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode valueToRestore, ModelNode valueToRevert, Boolean handback) { + auditLogger.setRedacted(handback); + } + } + + } diff --git a/domain-management/src/main/java/org/jboss/as/domain/management/parsing/AuditLogXml.java b/domain-management/src/main/java/org/jboss/as/domain/management/parsing/AuditLogXml.java index d2b8cb2df7f..4032a352317 100644 --- a/domain-management/src/main/java/org/jboss/as/domain/management/parsing/AuditLogXml.java +++ b/domain-management/src/main/java/org/jboss/as/domain/management/parsing/AuditLogXml.java @@ -6,6 +6,7 @@ import java.util.List; import javax.xml.stream.XMLStreamException; +import org.jboss.as.version.Stability; import org.jboss.dmr.ModelNode; import org.jboss.staxmapper.IntVersion; import org.jboss.staxmapper.XMLExtendedStreamReader; @@ -17,6 +18,10 @@ */ public interface AuditLogXml { static AuditLogXml newInstance(IntVersion version, boolean host) { + return newInstance(version, host, Stability.DEFAULT); + } + + static AuditLogXml newInstance(IntVersion version, boolean host, Stability stability) { switch (version.major()) { case 1: case 2: @@ -24,8 +29,25 @@ static AuditLogXml newInstance(IntVersion version, boolean host) { return new AuditLogXml_Legacy(host); case 4: return new AuditLogXml_4(host); + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + return new AuditLogXml_5(host); default: - return new AuditLogXml_5(host); + return new AuditLogXml_6(host, stability); } } diff --git a/domain-management/src/main/java/org/jboss/as/domain/management/parsing/AuditLogXml_6.java b/domain-management/src/main/java/org/jboss/as/domain/management/parsing/AuditLogXml_6.java new file mode 100644 index 00000000000..ae3717a9b97 --- /dev/null +++ b/domain-management/src/main/java/org/jboss/as/domain/management/parsing/AuditLogXml_6.java @@ -0,0 +1,841 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.domain.management.parsing; +import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.AUTHENTICATION; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CLIENT_CERT_STORE; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROTOCOL; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.TRUSTSTORE; +import static org.jboss.as.controller.parsing.ParseUtils.duplicateNamedElement; +import static org.jboss.as.controller.parsing.ParseUtils.isNoNamespaceAttribute; +import static org.jboss.as.controller.parsing.ParseUtils.missingRequired; +import static org.jboss.as.controller.parsing.ParseUtils.requireNamespace; +import static org.jboss.as.controller.parsing.ParseUtils.requireNoAttributes; +import static org.jboss.as.controller.parsing.ParseUtils.requireNoContent; +import static org.jboss.as.controller.parsing.ParseUtils.unexpectedAttribute; +import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement; + +import java.util.Collections; +import java.util.List; + +import javax.xml.stream.XMLStreamException; + +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.controller.operations.common.Util; +import org.jboss.as.controller.parsing.Attribute; +import org.jboss.as.controller.parsing.Element; +import org.jboss.as.domain.management.audit.AccessAuditResourceDefinition; +import org.jboss.as.domain.management.audit.AuditLogLoggerResourceDefinition; +import org.jboss.as.domain.management.audit.FileAuditLogHandlerResourceDefinition; +import org.jboss.as.domain.management.audit.InMemoryAuditLogHandlerResourceDefinition; +import org.jboss.as.domain.management.audit.JsonAuditLogFormatterResourceDefinition; +import org.jboss.as.domain.management.audit.KeystoreAttributes; +import org.jboss.as.domain.management.audit.PeriodicRotatingFileAuditLogHandlerResourceDefinition; +import org.jboss.as.domain.management.audit.SizeRotatingFileAuditLogHandlerResourceDefinition; +import org.jboss.as.domain.management.audit.SyslogAuditLogHandlerResourceDefinition; +import org.jboss.as.domain.management.audit.SyslogAuditLogProtocolResourceDefinition; +import org.jboss.as.domain.management.logging.DomainManagementLogger; +import org.jboss.as.version.Stability; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.Property; +import org.jboss.staxmapper.XMLExtendedStreamReader; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * @author Tomas Hofman (thofman@redhat.com) + */ +final class AuditLogXml_6 implements AuditLogXml { + final boolean host; + final Stability stability; + + AuditLogXml_6(boolean host, Stability stability) { + this.host = host; + this.stability = stability; + } + + private void parseFileAuditLogHandler(final XMLExtendedStreamReader reader, final ModelNode address, final List list) throws XMLStreamException { + // added ROTATE_AT_STARTUP attribute + + final ModelNode add = Util.createAddOperation(); + list.add(add); + final int count = reader.getAttributeCount(); + for (int i = 0; i < count; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case NAME: { + add.get(OP_ADDR).set(address).add(ModelDescriptionConstants.FILE_HANDLER, value); + break; + } + case MAX_FAILURE_COUNT: { + FileAuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.parseAndSetParameter(value, add, reader); + break; + } + case FORMATTER:{ + FileAuditLogHandlerResourceDefinition.FORMATTER.parseAndSetParameter(value, add, reader); + break; + } + case PATH: { + FileAuditLogHandlerResourceDefinition.PATH.parseAndSetParameter(value, add, reader); + break; + } + case RELATIVE_TO: { + FileAuditLogHandlerResourceDefinition.RELATIVE_TO.parseAndSetParameter(value, add, reader); + break; + } + case ROTATE_AT_STARTUP: { + FileAuditLogHandlerResourceDefinition.ROTATE_AT_STARTUP.parseAndSetParameter(value, add, reader); + break; + } + default: { + throw unexpectedAttribute(reader, i); + } + } + } + + requireNoContent(reader); + } + + private void writeFileAuditLogHandler(XMLExtendedStreamWriter writer, ModelNode auditLog, String name) throws XMLStreamException { + // added ROTATE_AT_STARTUP attribute + + if (auditLog.hasDefined(ModelDescriptionConstants.FILE_HANDLER, name)) { + writer.writeStartElement(Element.FILE_HANDLER.getLocalName()); + writer.writeAttribute(Attribute.NAME.getLocalName(), name); + ModelNode handler = auditLog.get(ModelDescriptionConstants.FILE_HANDLER, name); + FileAuditLogHandlerResourceDefinition.FORMATTER.marshallAsAttribute(handler, writer); + FileAuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.marshallAsAttribute(handler, writer); + FileAuditLogHandlerResourceDefinition.PATH.marshallAsAttribute(handler, writer); + FileAuditLogHandlerResourceDefinition.RELATIVE_TO.marshallAsAttribute(handler, writer); + FileAuditLogHandlerResourceDefinition.ROTATE_AT_STARTUP.marshallAsAttribute(handler, writer); + writer.writeEndElement(); + } + } + + @Override + public void parseAuditLog(final XMLExtendedStreamReader reader, final ModelNode address, final String expectedNs, + final List list) throws XMLStreamException { + + requireNamespace(reader, expectedNs); + + final ModelNode auditLogAddress = address.clone().add(AccessAuditResourceDefinition.PATH_ELEMENT.getKey(), AccessAuditResourceDefinition.PATH_ELEMENT.getValue()); + + final ModelNode add = new ModelNode(); + add.get(OP).set(ADD); + add.get(OP_ADDR).set(auditLogAddress); + list.add(add); + + requireNoAttributes(reader); + + + while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { + requireNamespace(reader, expectedNs); + final Element element = Element.forName(reader.getLocalName()); + switch (element) { + case FORMATTERS: + parseAuditLogFormatters(reader, auditLogAddress, expectedNs, list); + break; + case HANDLERS:{ + parseAuditLogHandlers(reader, auditLogAddress, expectedNs, list); + break; + } + case LOGGER:{ + parseAuditLogConfig(reader, auditLogAddress, expectedNs, AuditLogLoggerResourceDefinition.PATH_ELEMENT, list); + break; + } + case SERVER_LOGGER:{ + if (host){ + parseAuditLogConfig(reader, auditLogAddress, expectedNs, AuditLogLoggerResourceDefinition.HOST_SERVER_PATH_ELEMENT, list); + break; + } + //Otherwise fallback to server-logger not recognised in standalone.xml + } + default: + throw unexpectedElement(reader); + } + } + } + + private void parseAuditLogFormatters(final XMLExtendedStreamReader reader, final ModelNode address, final String expectedNs, final List list) throws XMLStreamException { + requireNamespace(reader, expectedNs); + while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { + requireNamespace(reader, expectedNs); + final Element element = Element.forName(reader.getLocalName()); + switch (element) { + case JSON_FORMATTER:{ + parseFileAuditLogFormatter(reader, address, list); + break; + } + default: + throw unexpectedElement(reader); + } + } + } + + private void parseFileAuditLogFormatter(final XMLExtendedStreamReader reader, final ModelNode address, final List list) throws XMLStreamException { + final ModelNode add = Util.createAddOperation(); + list.add(add); + final int count = reader.getAttributeCount(); + for (int i = 0; i < count; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case NAME: { + add.get(OP_ADDR).set(address).add(ModelDescriptionConstants.JSON_FORMATTER, value); + break; + } + case COMPACT:{ + JsonAuditLogFormatterResourceDefinition.COMPACT.parseAndSetParameter(value, add, reader); + break; + } + case DATE_FORMAT:{ + JsonAuditLogFormatterResourceDefinition.DATE_FORMAT.parseAndSetParameter(value, add, reader); + break; + } + case DATE_SEPARATOR:{ + JsonAuditLogFormatterResourceDefinition.DATE_SEPARATOR.parseAndSetParameter(value, add, reader); + break; + } + case ESCAPE_CONTROL_CHARACTERS:{ + JsonAuditLogFormatterResourceDefinition.ESCAPE_CONTROL_CHARACTERS.parseAndSetParameter(value, add, reader); + break; + } + case ESCAPE_NEW_LINE:{ + JsonAuditLogFormatterResourceDefinition.ESCAPE_NEW_LINE.parseAndSetParameter(value, add, reader); + break; + } + case INCLUDE_DATE:{ + JsonAuditLogFormatterResourceDefinition.INCLUDE_DATE.parseAndSetParameter(value, add, reader); + break; + } + default: { + throw unexpectedAttribute(reader, i); + } + } + } + + requireNoContent(reader); + } + + private void parseAuditLogHandlers(final XMLExtendedStreamReader reader, final ModelNode address, final String expectedNs, + final List list) throws XMLStreamException { + + requireNamespace(reader, expectedNs); //FIXME is this needed? what it does? + boolean configurationChangesConfigured = false; + while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { + requireNamespace(reader, expectedNs); + final Element element = Element.forName(reader.getLocalName()); + switch (element) { + case IN_MEMORY_HANDLER: + if(configurationChangesConfigured) { + throw unexpectedElement(reader); + } + parseConfigurationChangesAuditLogHandler(reader, address, list); + configurationChangesConfigured = true; + break; + case FILE_HANDLER: + parseFileAuditLogHandler(reader, address, list); + break; + case PERIODIC_ROTATING_FILE_HANDLER: + parsePeriodicRotatingFileAuditLogHandler(reader, address, list); + break; + case SIZE_ROTATING_FILE_HANDLER: + parseSizeRotatingFileAuditLogHandler(reader, address, list); + break; + case SYSLOG_HANDLER: + parseSyslogAuditLogHandler(reader, address, expectedNs, list); + break; + default: + throw unexpectedElement(reader); + } + } + } + + private void parseConfigurationChangesAuditLogHandler(final XMLExtendedStreamReader reader, final ModelNode address, final List list) throws XMLStreamException { + final ModelNode add = Util.createAddOperation(); + list.add(add); + final int count = reader.getAttributeCount(); + for (int i = 0; i < count; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case NAME: { + add.get(OP_ADDR).set(address).add(ModelDescriptionConstants.IN_MEMORY_HANDLER, value); + break; + } + case MAX_HISTORY: { + InMemoryAuditLogHandlerResourceDefinition.MAX_OPERATION_COUNT.parseAndSetParameter(value, add, reader); + break; + } + default: { + throw unexpectedAttribute(reader, i); + } + } + } + requireNoContent(reader); + } + + private void parseSizeRotatingFileAuditLogHandler(final XMLExtendedStreamReader reader, final ModelNode address, final List list) throws XMLStreamException { + final ModelNode add = Util.createAddOperation(); + list.add(add); + final int count = reader.getAttributeCount(); + for (int i = 0; i < count; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case NAME: + add.get(OP_ADDR).set(address).add(ModelDescriptionConstants.SIZE_ROTATING_FILE_HANDLER, value); + break; + case MAX_FAILURE_COUNT: + SizeRotatingFileAuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.parseAndSetParameter(value, add, reader); + break; + case FORMATTER: + SizeRotatingFileAuditLogHandlerResourceDefinition.FORMATTER.parseAndSetParameter(value, add, reader); + break; + case PATH: + SizeRotatingFileAuditLogHandlerResourceDefinition.PATH.parseAndSetParameter(value, add, reader); + break; + case RELATIVE_TO: + SizeRotatingFileAuditLogHandlerResourceDefinition.RELATIVE_TO.parseAndSetParameter(value, add, reader); + break; + case ROTATE_SIZE: + SizeRotatingFileAuditLogHandlerResourceDefinition.ROTATE_SIZE.parseAndSetParameter(value, add, reader); + break; + case MAX_BACKUP_INDEX: + SizeRotatingFileAuditLogHandlerResourceDefinition.MAX_BACKUP_INDEX.parseAndSetParameter(value, add, reader); + break; + default: + throw unexpectedAttribute(reader, i); + } + } + + requireNoContent(reader); + } + + private void parsePeriodicRotatingFileAuditLogHandler(final XMLExtendedStreamReader reader, final ModelNode address, final List list) throws XMLStreamException { + final ModelNode add = Util.createAddOperation(); + list.add(add); + final int count = reader.getAttributeCount(); + for (int i = 0; i < count; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case NAME: + add.get(OP_ADDR).set(address).add(ModelDescriptionConstants.PERIODIC_ROTATING_FILE_HANDLER, value); + break; + case MAX_FAILURE_COUNT: + PeriodicRotatingFileAuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.parseAndSetParameter(value, add, reader); + break; + case FORMATTER: + PeriodicRotatingFileAuditLogHandlerResourceDefinition.FORMATTER.parseAndSetParameter(value, add, reader); + break; + case PATH: + PeriodicRotatingFileAuditLogHandlerResourceDefinition.PATH.parseAndSetParameter(value, add, reader); + break; + case RELATIVE_TO: + PeriodicRotatingFileAuditLogHandlerResourceDefinition.RELATIVE_TO.parseAndSetParameter(value, add, reader); + break; + case SUFFIX: + PeriodicRotatingFileAuditLogHandlerResourceDefinition.SUFFIX.parseAndSetParameter(value, add, reader); + break; + default: + throw unexpectedAttribute(reader, i); + } + } + + requireNoContent(reader); + } + + private void parseSyslogAuditLogHandlerAttributes(final XMLExtendedStreamReader reader, final ModelNode address, final ModelNode addOp) throws XMLStreamException { + final int count = reader.getAttributeCount(); + for (int i = 0; i < count; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case NAME: { + addOp.get(OP_ADDR).set(address).add(ModelDescriptionConstants.SYSLOG_HANDLER, value); + break; + } + case MAX_FAILURE_COUNT: { + SyslogAuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.parseAndSetParameter(value, addOp, reader); + break; + } + case FORMATTER:{ + SyslogAuditLogHandlerResourceDefinition.FORMATTER.parseAndSetParameter(value, addOp, reader); + break; + } + case MAX_LENGTH: { + SyslogAuditLogHandlerResourceDefinition.MAX_LENGTH.parseAndSetParameter(value, addOp, reader); + break; + } + case TRUNCATE: { + SyslogAuditLogHandlerResourceDefinition.TRUNCATE.parseAndSetParameter(value, addOp, reader); + break; + } + case FACILITY: { + SyslogAuditLogHandlerResourceDefinition.FACILITY.parseAndSetParameter(value, addOp, reader); + break; + } + case APP_NAME: { + SyslogAuditLogHandlerResourceDefinition.APP_NAME.parseAndSetParameter(value, addOp, reader); + break; + } + case SYSLOG_FORMAT: { + SyslogAuditLogHandlerResourceDefinition.SYSLOG_FORMAT.parseAndSetParameter(value, addOp, reader); + break; + } + default: { + throw unexpectedAttribute(reader, i); + } + } + } + } + + private void parseSyslogAuditLogHandler(final XMLExtendedStreamReader reader, final ModelNode address, final String expectedNs, + final List list) throws XMLStreamException { + final ModelNode add = Util.createAddOperation(); + list.add(add); + + parseSyslogAuditLogHandlerAttributes(reader, address, add); + + if (!add.get(OP_ADDR).isDefined()) { + throw missingRequired(reader, Collections.singleton(Attribute.NAME)); + } + + boolean protocolSet = false; + while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { + requireNamespace(reader, expectedNs); + final Element element = Element.forName(reader.getLocalName()); + + //Check there is only one protocol + if (protocolSet) { + throw DomainManagementLogger.ROOT_LOGGER.onlyOneSyslogHandlerProtocol(reader.getLocation()); + } + protocolSet = true; + + switch (element) { + case UDP: + case TCP: + case TLS: { + parseSyslogAuditLogHandlerProtocol(reader, add.get(OP_ADDR), expectedNs, list, element); + break; + } + default: + throw unexpectedElement(reader); + } + } + } + + private void parseSyslogAuditLogHandlerProtocol(final XMLExtendedStreamReader reader, final ModelNode address, final String expectedNs, + final List list, final Element protocolElement) throws XMLStreamException { + PathAddress protocolAddress = PathAddress.pathAddress(address.clone().add(PROTOCOL, protocolElement.getLocalName())); + ModelNode add = Util.createAddOperation(protocolAddress); + list.add(add); + final int tcpCount = reader.getAttributeCount(); + for (int i = 0; i < tcpCount; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case HOST: { + SyslogAuditLogProtocolResourceDefinition.Udp.HOST.parseAndSetParameter(value, add, reader); + break; + } + case PORT: { + SyslogAuditLogProtocolResourceDefinition.Udp.PORT.parseAndSetParameter(value, add, reader); + break; + } + case MESSAGE_TRANSFER : { + if (protocolElement != Element.UDP) { + SyslogAuditLogProtocolResourceDefinition.Tcp.MESSAGE_TRANSFER.parseAndSetParameter(value, add, reader); + break; + } + } + case RECONNECT_TIMEOUT: + if (protocolElement != Element.UDP) { + SyslogAuditLogProtocolResourceDefinition.Tcp.RECONNECT_TIMEOUT.parseAndSetParameter(value, add, reader); + break; + } + default: { + throw unexpectedAttribute(reader, i); + } + } + } + + if (protocolElement != Element.TLS) { + requireNoContent(reader); + } else { + boolean seenTrustStore = false; + boolean seenClientCertStore = false; + while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { + requireNamespace(reader, expectedNs); + final Element element = Element.forName(reader.getLocalName()); + switch (element) { + case TRUSTSTORE:{ + if (seenTrustStore) { + throw duplicateNamedElement(reader, Element.TRUSTSTORE.getLocalName()); + } + seenTrustStore = true; + parseSyslogTlsKeystore(reader, protocolAddress, expectedNs, list, SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.TRUSTSTORE_ELEMENT, false); + break; + } + case CLIENT_CERT_STORE : { + if (seenClientCertStore) { + throw duplicateNamedElement(reader, Element.CLIENT_CERT_STORE.getLocalName()); + } + seenClientCertStore = true; + parseSyslogTlsKeystore(reader, protocolAddress, expectedNs, list, SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.CLIENT_CERT_ELEMENT, true); + break; + } + default: + throw unexpectedElement(reader); + } + } + } + } + private void parseSyslogTlsKeystore(final XMLExtendedStreamReader reader, final PathAddress address, final String expectedNs, + final List list, final PathElement storeAddress, final boolean hasKeyPassword) throws XMLStreamException { + ModelNode add = Util.createAddOperation(address.append(storeAddress)); + list.add(add); + final int tcpCount = reader.getAttributeCount(); + for (int i = 0; i < tcpCount; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case KEYSTORE_PASSWORD: { + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEYSTORE_PASSWORD.parseAndSetParameter(value, add, reader); + break; + } + case PATH: { + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEYSTORE_PATH.parseAndSetParameter(value, add, reader); + break; + } + case RELATIVE_TO : { + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEYSTORE_RELATIVE_TO.parseAndSetParameter(value, add, reader); + break; + } + case KEY_PASSWORD: { + if (hasKeyPassword){ + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEY_PASSWORD.parseAndSetParameter(value, add, reader); + break; + } + } + default: { + throw unexpectedAttribute(reader, i); + } + } + } + while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { + requireNamespace(reader, expectedNs); + final Element element = Element.forName(reader.getLocalName()); + switch (element) { + case KEYSTORE_PASSWORD_CREDENTIAL_REFERENCE: { + KeystoreAttributes.KEYSTORE_PASSWORD_CREDENTIAL_REFERENCE.getParser().parseElement(KeystoreAttributes.KEYSTORE_PASSWORD_CREDENTIAL_REFERENCE, reader, add); + break; + } + case KEY_PASSWORD_CREDENTIAL_REFERENCE: { + KeystoreAttributes.KEY_PASSWORD_CREDENTIAL_REFERENCE.getParser().parseElement(KeystoreAttributes.KEY_PASSWORD_CREDENTIAL_REFERENCE, reader, add); + break; + } + default: { + throw unexpectedElement(reader); + } + } + } + } + + private void parseAuditLogConfig(final XMLExtendedStreamReader reader, final ModelNode address, final String expectedNs, + final PathElement pathElement, final List list) throws XMLStreamException { + + requireNamespace(reader, expectedNs); + + final ModelNode configAddress = address.clone().add(pathElement.getKey(), pathElement.getValue()); + + final ModelNode add = new ModelNode(); + add.get(OP).set(ADD); + add.get(OP_ADDR).set(configAddress); + + list.add(add); + + final int count = reader.getAttributeCount(); + for (int i = 0; i < count; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case LOG_READ_ONLY: { + AuditLogLoggerResourceDefinition.LOG_READ_ONLY.parseAndSetParameter(value, add, reader); + break; + } + case LOG_BOOT: { + AuditLogLoggerResourceDefinition.LOG_BOOT.parseAndSetParameter(value, add, reader); + break; + } + case ENABLED: { + AuditLogLoggerResourceDefinition.ENABLED.parseAndSetParameter(value, add, reader); + break; + } + case REDACTED: { + if (stability.enables(Stability.COMMUNITY)) { + AuditLogLoggerResourceDefinition.REDACTED.parseAndSetParameter(value, add, reader); + } else { + throw unexpectedAttribute(reader, i); + } + break; + } + default: { + throw unexpectedAttribute(reader, i); + } + } + } + + while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { + requireNamespace(reader, expectedNs); + final Element element = Element.forName(reader.getLocalName()); + switch (element) { + case HANDLERS:{ + parseAuditLogHandlersReference(reader, configAddress, expectedNs, list); + break; + } + default: + throw unexpectedElement(reader); + } + } + } + + private void parseAuditLogHandlersReference(final XMLExtendedStreamReader reader, final ModelNode address, final String expectedNs, + final List list) throws XMLStreamException { + requireNamespace(reader, expectedNs); + + while (reader.hasNext() && reader.nextTag() != END_ELEMENT) { + requireNamespace(reader, expectedNs); + final Element element = Element.forName(reader.getLocalName()); + switch (element) { + case HANDLER:{ + requireNamespace(reader, expectedNs); + final ModelNode add = new ModelNode(); + add.get(OP).set(ADD); + list.add(add); + + final int count = reader.getAttributeCount(); + for (int i = 0; i < count; i++) { + final String value = reader.getAttributeValue(i); + if (!isNoNamespaceAttribute(reader, i)) { + throw unexpectedAttribute(reader, i); + } + final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i)); + switch (attribute) { + case NAME: { + add.get(OP_ADDR).set(address).add(ModelDescriptionConstants.HANDLER, value); + break; + } + default: { + throw unexpectedAttribute(reader, i); + } + } + requireNoContent(reader); + } + break; + } + default: + throw unexpectedElement(reader); + } + } + } + + @Override + public void writeAuditLog(XMLExtendedStreamWriter writer, ModelNode auditLog) throws XMLStreamException { + writer.writeStartElement(Element.AUDIT_LOG.getLocalName()); + + if (auditLog.hasDefined(ModelDescriptionConstants.JSON_FORMATTER) && !auditLog.get(ModelDescriptionConstants.JSON_FORMATTER).keys().isEmpty()) { + writer.writeStartElement(Element.FORMATTERS.getLocalName()); + for (Property prop : auditLog.get(ModelDescriptionConstants.JSON_FORMATTER).asPropertyList()) { + writer.writeStartElement(Element.JSON_FORMATTER.getLocalName()); + writer.writeAttribute(Attribute.NAME.getLocalName(), prop.getName()); + JsonAuditLogFormatterResourceDefinition.COMPACT.marshallAsAttribute(prop.getValue(), writer); + JsonAuditLogFormatterResourceDefinition.DATE_FORMAT.marshallAsAttribute(prop.getValue(), writer); + JsonAuditLogFormatterResourceDefinition.DATE_SEPARATOR.marshallAsAttribute(prop.getValue(), writer); + JsonAuditLogFormatterResourceDefinition.ESCAPE_CONTROL_CHARACTERS.marshallAsAttribute(prop.getValue(), writer); + JsonAuditLogFormatterResourceDefinition.ESCAPE_NEW_LINE.marshallAsAttribute(prop.getValue(), writer); + JsonAuditLogFormatterResourceDefinition.INCLUDE_DATE.marshallAsAttribute(prop.getValue(), writer); + writer.writeEndElement(); + } + writer.writeEndElement(); + } + + + if ((auditLog.hasDefined(ModelDescriptionConstants.FILE_HANDLER) && !auditLog.get(ModelDescriptionConstants.FILE_HANDLER).keys().isEmpty()) || + (auditLog.hasDefined(ModelDescriptionConstants.SYSLOG_HANDLER) && !auditLog.get(ModelDescriptionConstants.SYSLOG_HANDLER).keys().isEmpty()) || + (auditLog.hasDefined(ModelDescriptionConstants.SIZE_ROTATING_FILE_HANDLER) && !auditLog.get(ModelDescriptionConstants.SIZE_ROTATING_FILE_HANDLER).keys().isEmpty()) || + (auditLog.hasDefined(ModelDescriptionConstants.PERIODIC_ROTATING_FILE_HANDLER) && !auditLog.get(ModelDescriptionConstants.PERIODIC_ROTATING_FILE_HANDLER).keys().isEmpty()) || + (auditLog.hasDefined(ModelDescriptionConstants.IN_MEMORY_HANDLER) && !auditLog.get(ModelDescriptionConstants.IN_MEMORY_HANDLER).keys().isEmpty())) { + writer.writeStartElement(Element.HANDLERS.getLocalName()); + if (auditLog.hasDefined(ModelDescriptionConstants.FILE_HANDLER)) { + for (String name : auditLog.get(ModelDescriptionConstants.FILE_HANDLER).keys()) { + writeFileAuditLogHandler(writer, auditLog, name); + } + } + if (auditLog.hasDefined(ModelDescriptionConstants.PERIODIC_ROTATING_FILE_HANDLER)) { + for (String name : auditLog.get(ModelDescriptionConstants.PERIODIC_ROTATING_FILE_HANDLER).keys()) { + writer.writeStartElement(Element.PERIODIC_ROTATING_FILE_HANDLER.getLocalName()); + writer.writeAttribute(Attribute.NAME.getLocalName(), name); + ModelNode handler = auditLog.get(ModelDescriptionConstants.PERIODIC_ROTATING_FILE_HANDLER, name); + PeriodicRotatingFileAuditLogHandlerResourceDefinition.FORMATTER.marshallAsAttribute(handler, writer); + PeriodicRotatingFileAuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.marshallAsAttribute(handler, writer); + PeriodicRotatingFileAuditLogHandlerResourceDefinition.PATH.marshallAsAttribute(handler, writer); + PeriodicRotatingFileAuditLogHandlerResourceDefinition.RELATIVE_TO.marshallAsAttribute(handler, writer); + PeriodicRotatingFileAuditLogHandlerResourceDefinition.SUFFIX.marshallAsAttribute(handler, writer); + writer.writeEndElement(); + } + } + if (auditLog.hasDefined(ModelDescriptionConstants.SIZE_ROTATING_FILE_HANDLER)) { + for (String name : auditLog.get(ModelDescriptionConstants.SIZE_ROTATING_FILE_HANDLER).keys()) { + writer.writeStartElement(Element.SIZE_ROTATING_FILE_HANDLER.getLocalName()); + writer.writeAttribute(Attribute.NAME.getLocalName(), name); + ModelNode handler = auditLog.get(ModelDescriptionConstants.SIZE_ROTATING_FILE_HANDLER, name); + SizeRotatingFileAuditLogHandlerResourceDefinition.FORMATTER.marshallAsAttribute(handler, writer); + SizeRotatingFileAuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.marshallAsAttribute(handler, writer); + SizeRotatingFileAuditLogHandlerResourceDefinition.PATH.marshallAsAttribute(handler, writer); + SizeRotatingFileAuditLogHandlerResourceDefinition.RELATIVE_TO.marshallAsAttribute(handler, writer); + SizeRotatingFileAuditLogHandlerResourceDefinition.ROTATE_SIZE.marshallAsAttribute(handler, writer); + SizeRotatingFileAuditLogHandlerResourceDefinition.MAX_BACKUP_INDEX.marshallAsAttribute(handler, writer); + writer.writeEndElement(); + } + } + if (auditLog.hasDefined(ModelDescriptionConstants.SYSLOG_HANDLER)) { + for (String name : auditLog.get(ModelDescriptionConstants.SYSLOG_HANDLER).keys()) { + writer.writeStartElement(Element.SYSLOG_HANDLER.getLocalName()); + writer.writeAttribute(Attribute.NAME.getLocalName(), name); + ModelNode handler = auditLog.get(ModelDescriptionConstants.SYSLOG_HANDLER, name); + SyslogAuditLogHandlerResourceDefinition.FORMATTER.marshallAsAttribute(handler, writer); + SyslogAuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.marshallAsAttribute(handler, writer); + SyslogAuditLogHandlerResourceDefinition.SYSLOG_FORMAT.marshallAsAttribute(handler, writer); + SyslogAuditLogHandlerResourceDefinition.MAX_LENGTH.marshallAsAttribute(handler, writer); + SyslogAuditLogHandlerResourceDefinition.TRUNCATE.marshallAsAttribute(handler, writer); + SyslogAuditLogHandlerResourceDefinition.FACILITY.marshallAsAttribute(handler, writer); + SyslogAuditLogHandlerResourceDefinition.APP_NAME.marshallAsAttribute(handler, writer); + if (handler.hasDefined(PROTOCOL)) { + writeAuditLogSyslogProtocol(writer, handler.get(PROTOCOL)); + } + writer.writeEndElement(); + } + } + if (auditLog.hasDefined(ModelDescriptionConstants.IN_MEMORY_HANDLER)) { + for (String name : auditLog.get(ModelDescriptionConstants.IN_MEMORY_HANDLER).keys()) { + writer.writeStartElement(Element.IN_MEMORY_HANDLER.getLocalName()); + writer.writeAttribute(Attribute.NAME.getLocalName(), name); + ModelNode handler = auditLog.get(ModelDescriptionConstants.IN_MEMORY_HANDLER, name); + InMemoryAuditLogHandlerResourceDefinition.MAX_OPERATION_COUNT.marshallAsAttribute(handler, writer); + writer.writeEndElement(); + } + } + writer.writeEndElement(); + } + writeAuditLogger(writer, auditLog, Element.LOGGER.getLocalName()); + writeAuditLogger(writer, auditLog, Element.SERVER_LOGGER.getLocalName()); + writer.writeEndElement(); + } + + private void writeAuditLogger(XMLExtendedStreamWriter writer, ModelNode auditLog, String element) throws XMLStreamException { + if (auditLog.hasDefined(element) && auditLog.get(element).hasDefined(ModelDescriptionConstants.AUDIT_LOG)){ + ModelNode config = auditLog.get(element, ModelDescriptionConstants.AUDIT_LOG); + writer.writeStartElement(element); + AuditLogLoggerResourceDefinition.LOG_BOOT.marshallAsAttribute(config, writer); + AuditLogLoggerResourceDefinition.LOG_READ_ONLY.marshallAsAttribute(config, writer); + AuditLogLoggerResourceDefinition.ENABLED.marshallAsAttribute(config, writer); + if (stability.enables(Stability.COMMUNITY)) { + AuditLogLoggerResourceDefinition.REDACTED.marshallAsAttribute(config, writer); + } + if (config.hasDefined(ModelDescriptionConstants.HANDLER) && !config.get(ModelDescriptionConstants.HANDLER).keys().isEmpty()) { + writer.writeStartElement(Element.HANDLERS.getLocalName()); + for (String name : config.get(ModelDescriptionConstants.HANDLER).keys()) { + writer.writeStartElement(Element.HANDLER.getLocalName()); + writer.writeAttribute(Attribute.NAME.getLocalName(), name); + writer.writeEndElement(); + } + writer.writeEndElement(); + } + + writer.writeEndElement(); + } + } + + + private void writeAuditLogSyslogProtocol(XMLExtendedStreamWriter writer, ModelNode protocol) throws XMLStreamException { + String type = protocol.keys().iterator().next(); + ModelNode protocolContents = protocol.get(type); + if (type.equals(ModelDescriptionConstants.UDP)) { + writer.writeStartElement(type); + SyslogAuditLogProtocolResourceDefinition.Udp.HOST.marshallAsAttribute(protocolContents, writer); + SyslogAuditLogProtocolResourceDefinition.Udp.PORT.marshallAsAttribute(protocolContents, writer); + writer.writeEndElement(); + } else if (type.equals(ModelDescriptionConstants.TCP)) { + writer.writeStartElement(type); + SyslogAuditLogProtocolResourceDefinition.Tcp.HOST.marshallAsAttribute(protocolContents, writer); + SyslogAuditLogProtocolResourceDefinition.Tcp.PORT.marshallAsAttribute(protocolContents, writer); + SyslogAuditLogProtocolResourceDefinition.Tcp.MESSAGE_TRANSFER.marshallAsAttribute(protocolContents, writer); + SyslogAuditLogProtocolResourceDefinition.Tcp.RECONNECT_TIMEOUT.marshallAsAttribute(protocolContents, writer); + writer.writeEndElement(); + } else if (type.equals(ModelDescriptionConstants.TLS)) { + writer.writeStartElement(type); + SyslogAuditLogProtocolResourceDefinition.Tls.HOST.marshallAsAttribute(protocolContents, writer); + SyslogAuditLogProtocolResourceDefinition.Tls.PORT.marshallAsAttribute(protocolContents, writer); + SyslogAuditLogProtocolResourceDefinition.Tls.MESSAGE_TRANSFER.marshallAsAttribute(protocolContents, writer); + SyslogAuditLogProtocolResourceDefinition.Tcp.RECONNECT_TIMEOUT.marshallAsAttribute(protocolContents, writer); + + if (protocolContents.hasDefined(AUTHENTICATION)) { + writeAuditLogSyslogTlsProtocolKeyStore(writer, protocolContents.get(AUTHENTICATION), TRUSTSTORE); + writeAuditLogSyslogTlsProtocolKeyStore(writer, protocolContents.get(AUTHENTICATION), CLIENT_CERT_STORE); + } + + writer.writeEndElement(); + } + } + + private void writeAuditLogSyslogTlsProtocolKeyStore(XMLExtendedStreamWriter writer, ModelNode keystoreParent, String name) throws XMLStreamException { + if (keystoreParent.hasDefined(name)) { + ModelNode keystore = keystoreParent.get(name); + writer.writeStartElement(name); + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEYSTORE_PATH.marshallAsAttribute(keystore, writer); + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEYSTORE_RELATIVE_TO.marshallAsAttribute(keystore, writer); + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEYSTORE_PASSWORD.marshallAsAttribute(keystore, writer); + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEY_PASSWORD.marshallAsAttribute(keystore, writer); + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEYSTORE_PASSWORD_CREDENTIAL_REFERENCE.marshallAsElement(keystore, writer); + SyslogAuditLogProtocolResourceDefinition.Tls.TlsKeyStore.KEY_PASSWORD_CREDENTIAL_REFERENCE.marshallAsElement(keystore, writer); + writer.writeEndElement(); + } + } +} diff --git a/domain-management/src/main/resources/org/jboss/as/domain/management/_private/LocalDescriptions.properties b/domain-management/src/main/resources/org/jboss/as/domain/management/_private/LocalDescriptions.properties index 7ee297d62db..322308b55d8 100644 --- a/domain-management/src/main/resources/org/jboss/as/domain/management/_private/LocalDescriptions.properties +++ b/domain-management/src/main/resources/org/jboss/as/domain/management/_private/LocalDescriptions.properties @@ -123,6 +123,7 @@ core.management.audit-log.handler-reference.remove=Removes a reference to a file core.management.audit-log.json-formatter=Formatters for formatting the audit log messages using json. core.management.audit-log.file-handler=File handlers for use with the management audit logging service. core.management.audit-log.in-memory-handler=In memory handlers for use with the management audit logging service. +core.management.audit-log.redacted=Whether sensitive operation parameters should be redacted on the audit log trace or configuration change history. core.management.audit-log.periodic-rotating-file-handler=Periodic-rotating file handlers for use with the management audit logging service. core.management.audit-log.size-rotating-file-handler=Size-rotating file handlers for use with the management audit logging service. core.management.audit-log.syslog-handler=Syslog handlers for use with the management audit logging service. diff --git a/host-controller/src/main/java/org/jboss/as/host/controller/parsing/HostXml_20.java b/host-controller/src/main/java/org/jboss/as/host/controller/parsing/HostXml_20.java index e14c8ca16a1..3983b80e707 100644 --- a/host-controller/src/main/java/org/jboss/as/host/controller/parsing/HostXml_20.java +++ b/host-controller/src/main/java/org/jboss/as/host/controller/parsing/HostXml_20.java @@ -137,12 +137,12 @@ final class HostXml_20 extends CommonXml implements ManagementXmlDelegate { super(new SocketBindingsXml.HostSocketBindingsXml()); this.namespace = namespace.getUri(); this.version = namespace.getVersion(); - this.auditLogDelegate = AuditLogXml.newInstance(version, true); + this.stability = namespace.getStability(); + this.auditLogDelegate = AuditLogXml.newInstance(version, true, this.stability); this.defaultHostControllerName = defaultHostControllerName; this.runningMode = runningMode; this.isCachedDc = isCachedDC; this.extensionRegistry = extensionRegistry; - this.stability = namespace.getStability(); this.extensionXml = extensionXml; } diff --git a/server/src/main/java/org/jboss/as/server/parsing/StandaloneXml_20.java b/server/src/main/java/org/jboss/as/server/parsing/StandaloneXml_20.java index 21d6c78600a..02c5c3958d1 100644 --- a/server/src/main/java/org/jboss/as/server/parsing/StandaloneXml_20.java +++ b/server/src/main/java/org/jboss/as/server/parsing/StandaloneXml_20.java @@ -115,7 +115,7 @@ final class StandaloneXml_20 extends CommonXml implements ManagementXmlDelegate this.namespace = namespace.getUri(); this.stability = namespace.getStability(); this.accessControlXml = AccessControlXml.newInstance(this.namespace); - this.auditLogDelegate = AuditLogXml.newInstance(version, false); + this.auditLogDelegate = AuditLogXml.newInstance(version, false, this.stability); this.deferredExtensionContext = deferredExtensionContext; this.parsingOptions = options; } From 8900746da15b5e3e8b798cafae15b0751baeefe6 Mon Sep 17 00:00:00 2001 From: Yeray Borges Date: Fri, 20 Feb 2026 21:45:19 +0000 Subject: [PATCH 4/6] [WFCORE-7247] Add redactable flag to relevant attributes --- .../jboss/as/controller/security/CredentialReference.java | 2 ++ .../jboss/as/domain/management/audit/KeystoreAttributes.java | 5 +++-- .../elytron/AbstractCredentialStoreResourceDefinition.java | 2 ++ .../extension/elytron/CredentialStoreResourceDefinition.java | 2 ++ .../elytron/ExpressionResolverResourceDefinition.java | 3 +++ .../wildfly/extension/elytron/FileSystemRealmDefinition.java | 1 + .../wildfly/extension/elytron/ModifiableRealmDecorator.java | 2 ++ .../org/wildfly/extension/elytron/TokenRealmDefinition.java | 2 +- .../controller/resources/SslLoopbackResourceDefinition.java | 2 ++ 9 files changed, 18 insertions(+), 3 deletions(-) diff --git a/controller/src/main/java/org/jboss/as/controller/security/CredentialReference.java b/controller/src/main/java/org/jboss/as/controller/security/CredentialReference.java index 27a99cb261e..1e4dc2a1a84 100644 --- a/controller/src/main/java/org/jboss/as/controller/security/CredentialReference.java +++ b/controller/src/main/java/org/jboss/as/controller/security/CredentialReference.java @@ -32,6 +32,7 @@ import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.operations.validation.ParameterValidator; +import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.Resource; import org.jboss.as.controller.transform.TransformationContext; import org.jboss.as.controller.transform.description.RejectAttributeChecker; @@ -138,6 +139,7 @@ public final class CredentialReference { clearTextAttribute = new SimpleAttributeDefinitionBuilder(CLEAR_TEXT, ModelType.STRING, true) .setXmlName(CLEAR_TEXT) .setAllowExpression(true) + .setFlags(AttributeAccess.Flag.REDACTABLE) .build(); credentialReferenceAD = getAttributeBuilder(CREDENTIAL_REFERENCE, CREDENTIAL_REFERENCE, false, false) .setRestartAllServices() diff --git a/domain-management/src/main/java/org/jboss/as/domain/management/audit/KeystoreAttributes.java b/domain-management/src/main/java/org/jboss/as/domain/management/audit/KeystoreAttributes.java index 73eebd9a94e..f0fa0249d5b 100644 --- a/domain-management/src/main/java/org/jboss/as/domain/management/audit/KeystoreAttributes.java +++ b/domain-management/src/main/java/org/jboss/as/domain/management/audit/KeystoreAttributes.java @@ -40,19 +40,20 @@ public class KeystoreAttributes { .setXmlName(ModelDescriptionConstants.KEY_PASSWORD) .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) .setAllowExpression(true) - .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES) + .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES, AttributeAccess.Flag.REDACTABLE) .setAlternatives(KEY_PASSWORD_CREDENTIAL_REFERENCE_NAME) .build(); public static final ObjectTypeAttributeDefinition KEYSTORE_PASSWORD_CREDENTIAL_REFERENCE = CredentialReference.getAttributeBuilder(KEYSTORE_PASSWORD_CREDENTIAL_REFERENCE_NAME, KEYSTORE_PASSWORD_CREDENTIAL_REFERENCE_NAME, true, false) .setAlternatives(ModelDescriptionConstants.KEYSTORE_PASSWORD) + .setFlags(AttributeAccess.Flag.REDACTABLE) .build(); public static final SimpleAttributeDefinition KEYSTORE_PASSWORD = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.KEYSTORE_PASSWORD, ModelType.STRING, true) .setXmlName(ModelDescriptionConstants.KEYSTORE_PASSWORD) .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true)) .setAllowExpression(true) - .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES) + .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES, AttributeAccess.Flag.REDACTABLE) .setAlternatives(KEYSTORE_PASSWORD_CREDENTIAL_REFERENCE_NAME) .build(); diff --git a/elytron/src/main/java/org/wildfly/extension/elytron/AbstractCredentialStoreResourceDefinition.java b/elytron/src/main/java/org/wildfly/extension/elytron/AbstractCredentialStoreResourceDefinition.java index f03e01579ef..19f9bdb1990 100644 --- a/elytron/src/main/java/org/wildfly/extension/elytron/AbstractCredentialStoreResourceDefinition.java +++ b/elytron/src/main/java/org/wildfly/extension/elytron/AbstractCredentialStoreResourceDefinition.java @@ -38,6 +38,7 @@ import org.jboss.as.controller.SimpleResourceDefinition; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver; +import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; @@ -83,6 +84,7 @@ protected ServiceUtil getCredentialStoreUtil() { .build(); static final SimpleAttributeDefinition KEY = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.KEY, ModelType.STRING, false) + .setFlags(AttributeAccess.Flag.REDACTABLE) .setMinSize(1) .build(); diff --git a/elytron/src/main/java/org/wildfly/extension/elytron/CredentialStoreResourceDefinition.java b/elytron/src/main/java/org/wildfly/extension/elytron/CredentialStoreResourceDefinition.java index 45d3dad77c5..51f9162c41f 100644 --- a/elytron/src/main/java/org/wildfly/extension/elytron/CredentialStoreResourceDefinition.java +++ b/elytron/src/main/java/org/wildfly/extension/elytron/CredentialStoreResourceDefinition.java @@ -51,6 +51,7 @@ import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver; +import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.controller.registry.Resource; @@ -191,6 +192,7 @@ final class CredentialStoreResourceDefinition extends AbstractCredentialStoreRes } static final SimpleAttributeDefinition SECRET_VALUE = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.SECRET_VALUE, ModelType.STRING, true) + .setFlags(AttributeAccess.Flag.REDACTABLE) .setMinSize(0) .build(); diff --git a/elytron/src/main/java/org/wildfly/extension/elytron/ExpressionResolverResourceDefinition.java b/elytron/src/main/java/org/wildfly/extension/elytron/ExpressionResolverResourceDefinition.java index 27ec42cb618..f3127a6136b 100644 --- a/elytron/src/main/java/org/wildfly/extension/elytron/ExpressionResolverResourceDefinition.java +++ b/elytron/src/main/java/org/wildfly/extension/elytron/ExpressionResolverResourceDefinition.java @@ -35,6 +35,7 @@ import org.jboss.as.controller.SimpleResourceDefinition; import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver; +import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.as.controller.registry.Resource; @@ -79,6 +80,7 @@ class ExpressionResolverResourceDefinition extends SimpleResourceDefinition { .setAllowExpression(false) .setMinSize(1) .setRestartAllServices() + .setFlags(AttributeAccess.Flag.REDACTABLE) .build(); private static final ObjectTypeAttributeDefinition RESOLVER = new ObjectTypeAttributeDefinition.Builder(ElytronDescriptionConstants.RESOLVER, NAME, CREDENTIAL_STORE, SECRET_KEY) @@ -116,6 +118,7 @@ class ExpressionResolverResourceDefinition extends SimpleResourceDefinition { static final SimpleAttributeDefinition CLEAR_TEXT = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.CLEAR_TEXT, ModelType.STRING, false) .setMinSize(1) + .setFlags(AttributeAccess.Flag.REDACTABLE) .build(); static final SimpleOperationDefinition CREATE_EXPRESSION = new SimpleOperationDefinitionBuilder(ElytronDescriptionConstants.CREATE_EXPRESSION, RESOURCE_RESOLVER) diff --git a/elytron/src/main/java/org/wildfly/extension/elytron/FileSystemRealmDefinition.java b/elytron/src/main/java/org/wildfly/extension/elytron/FileSystemRealmDefinition.java index ea85695e8bd..c916f89874a 100644 --- a/elytron/src/main/java/org/wildfly/extension/elytron/FileSystemRealmDefinition.java +++ b/elytron/src/main/java/org/wildfly/extension/elytron/FileSystemRealmDefinition.java @@ -151,6 +151,7 @@ class FileSystemRealmDefinition extends SimpleResourceDefinition { .setRequires(ElytronDescriptionConstants.CREDENTIAL_STORE) .setMinSize(1) .setRestartAllServices() + .setFlags(AttributeAccess.Flag.REDACTABLE) .build(); static final SimpleAttributeDefinition KEY_STORE = diff --git a/elytron/src/main/java/org/wildfly/extension/elytron/ModifiableRealmDecorator.java b/elytron/src/main/java/org/wildfly/extension/elytron/ModifiableRealmDecorator.java index 29d7bc725c8..0b2a9176a12 100644 --- a/elytron/src/main/java/org/wildfly/extension/elytron/ModifiableRealmDecorator.java +++ b/elytron/src/main/java/org/wildfly/extension/elytron/ModifiableRealmDecorator.java @@ -19,6 +19,7 @@ import org.jboss.as.controller.capability.RuntimeCapability; import org.jboss.as.controller.descriptions.ResourceDescriptionResolver; import org.jboss.as.controller.operations.validation.StringAllowedValuesValidator; +import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; @@ -325,6 +326,7 @@ protected void executeRuntimeStep(final OperationContext context, final ModelNod static class SetPasswordHandler extends ElytronRuntimeOnlyHandler { static final SimpleAttributeDefinition PASSWORD = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.PASSWORD, ModelType.STRING, false) + .setFlags(AttributeAccess.Flag.REDACTABLE) .build(); static class Bcrypt { diff --git a/elytron/src/main/java/org/wildfly/extension/elytron/TokenRealmDefinition.java b/elytron/src/main/java/org/wildfly/extension/elytron/TokenRealmDefinition.java index f891632397a..763661c4a69 100644 --- a/elytron/src/main/java/org/wildfly/extension/elytron/TokenRealmDefinition.java +++ b/elytron/src/main/java/org/wildfly/extension/elytron/TokenRealmDefinition.java @@ -186,7 +186,7 @@ static class OAuth2IntrospectionValidatorAttributes { static final SimpleAttributeDefinition CLIENT_SECRET = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.CLIENT_SECRET, ModelType.STRING, false) .setAllowExpression(true) .setMinSize(1) - .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES) + .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES, AttributeAccess.Flag.REDACTABLE) .build(); static final SimpleAttributeDefinition INTROSPECTION_URL = new SimpleAttributeDefinitionBuilder(ElytronDescriptionConstants.INTROSPECTION_URL, ModelType.STRING, false) diff --git a/host-controller/src/main/java/org/jboss/as/host/controller/resources/SslLoopbackResourceDefinition.java b/host-controller/src/main/java/org/jboss/as/host/controller/resources/SslLoopbackResourceDefinition.java index f0082c7f5c7..9c6a25bb5b4 100644 --- a/host-controller/src/main/java/org/jboss/as/host/controller/resources/SslLoopbackResourceDefinition.java +++ b/host-controller/src/main/java/org/jboss/as/host/controller/resources/SslLoopbackResourceDefinition.java @@ -23,6 +23,7 @@ import org.jboss.as.controller.access.management.AccessConstraintDefinition; import org.jboss.as.controller.access.management.SensitiveTargetAccessConstraintDefinition; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.controller.registry.AttributeAccess; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.host.controller.descriptions.HostResolver; import org.jboss.dmr.ModelNode; @@ -61,6 +62,7 @@ public class SslLoopbackResourceDefinition extends SimpleResourceDefinition { .build(); public static final SimpleAttributeDefinition TRUSTSTORE_PASSWORD = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.TRUSTSTORE_PASSWORD, ModelType.STRING, true) + .setFlags(AttributeAccess.Flag.REDACTABLE) .setAllowExpression(true) .build(); From ee6e4349aff4d70c4878f15ff9ee9ec70a575b88 Mon Sep 17 00:00:00 2001 From: Yeray Borges Date: Wed, 25 Feb 2026 23:41:55 +0000 Subject: [PATCH 5/6] [WFCORE-7247] Test configuration changes Signed-off-by: Yeray Borges --- ...rationChangesRedactableValuesTestCase.java | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 testsuite/standalone/src/test/java/org/wildfly/core/test/standalone/mgmt/ConfigurationChangesRedactableValuesTestCase.java diff --git a/testsuite/standalone/src/test/java/org/wildfly/core/test/standalone/mgmt/ConfigurationChangesRedactableValuesTestCase.java b/testsuite/standalone/src/test/java/org/wildfly/core/test/standalone/mgmt/ConfigurationChangesRedactableValuesTestCase.java new file mode 100644 index 00000000000..41189dd07e8 --- /dev/null +++ b/testsuite/standalone/src/test/java/org/wildfly/core/test/standalone/mgmt/ConfigurationChangesRedactableValuesTestCase.java @@ -0,0 +1,159 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.core.test.standalone.mgmt; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATIONS; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REDACTED; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; +import java.util.List; + +import jakarta.inject.Inject; + +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.operations.common.Util; +import org.jboss.as.test.integration.management.util.ServerReload; +import org.jboss.dmr.ModelNode; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.wildfly.core.testrunner.ManagementClient; +import org.wildfly.core.testrunner.ServerSetup; +import org.wildfly.core.testrunner.UnsuccessfulOperationException; +import org.wildfly.core.testrunner.WildFlyRunner; +import org.wildfly.test.stability.StabilityServerSetupSnapshotRestoreTasks; + +@RunWith(WildFlyRunner.class) +@ServerSetup({StabilityServerSetupSnapshotRestoreTasks.Community.class}) +public class ConfigurationChangesRedactableValuesTestCase { + + private static final int MAX_HISTORY_SIZE = 8; + + private static final PathAddress CONFIGURATION_CHANGES_ADDRESS = PathAddress.pathAddress() + .append(PathElement.pathElement(SUBSYSTEM, "core-management")) + .append(PathElement.pathElement("service", "configuration-changes")); + + private static final PathAddress CREDENTIAL_STORE_ADDRESS = PathAddress.pathAddress() + .append(PathElement.pathElement(SUBSYSTEM, "elytron")) + .append(PathElement.pathElement("credential-store", "test-cs")); + + @Inject + protected ManagementClient client; + + + @Before + public void createConfigurationChanges() throws Exception { + final ModelNode add = Util.createAddOperation(CONFIGURATION_CHANGES_ADDRESS); + add.get("max-history").set(MAX_HISTORY_SIZE); + client.executeForResult(add); + } + + @After + public void clearConfigurationChanges() throws UnsuccessfulOperationException { + final ModelNode remove = Util.createRemoveOperation(CONFIGURATION_CHANGES_ADDRESS); + client.executeForResult(remove); + } + + @Test + public void testRedactableAttribute() throws Exception { + String storePassword = "storeSecretPassword"; + String expectedHash = sha256(storePassword); + + ModelNode addOp = Util.createAddOperation(CREDENTIAL_STORE_ADDRESS); + addOp.get("relative-to").set("jboss.server.data.dir"); + addOp.get("location").set("test-redact.store"); + addOp.get("create").set(true); + ModelNode credRef = new ModelNode(); + credRef.get("clear-text").set(storePassword); + addOp.get("credential-reference").set(credRef); + client.executeForResult(addOp); + + try { + List changes = getConfigurationChanges(); + assertFalse("Expected at least one configuration change", changes.isEmpty()); + + // The latest change + ModelNode latestChange = changes.get(0); + assertEquals(SUCCESS, latestChange.get(OUTCOME).asString()); + ModelNode recordedOp = latestChange.get(OPERATIONS).asList().get(0); + assertEquals(ADD, recordedOp.get(OP).asString()); + + assertTrue("credential-reference should be present in the recorded operation", recordedOp.hasDefined("credential-reference")); + ModelNode recordedCredRef = recordedOp.get("credential-reference"); + + assertTrue("clear-text should be present inside credential-reference", recordedCredRef.hasDefined("clear-text")); + String recordedClearText = recordedCredRef.get("clear-text").asString(); + assertNotEquals("clear-text should not contain the original value", storePassword, recordedClearText); + assertEquals("clear-text should be replaced with SHA-256 hash of the original value", expectedHash, recordedClearText); + } finally { + client.executeForResult(Util.createRemoveOperation(CREDENTIAL_STORE_ADDRESS)); + } + + ServerReload.executeReloadAndWaitForCompletion(client.getControllerClient()); + + // repeat the test but now with redaction disabled + ModelNode writeOp = Util.getWriteAttributeOperation(CONFIGURATION_CHANGES_ADDRESS, REDACTED, false); + client.executeForResult(writeOp); + + addOp = Util.createAddOperation(CREDENTIAL_STORE_ADDRESS); + addOp.get("relative-to").set("jboss.server.data.dir"); + addOp.get("location").set("test-redact.store"); + credRef = new ModelNode(); + credRef.get("clear-text").set(storePassword); + addOp.get("credential-reference").set(credRef); + client.executeForResult(addOp); + + try { + List changes = getConfigurationChanges(); + assertFalse("Expected at least one configuration change", changes.isEmpty()); + + // The latest change + ModelNode latestChange = changes.get(0); + assertEquals(SUCCESS, latestChange.get(OUTCOME).asString()); + ModelNode recordedOp = latestChange.get(OPERATIONS).asList().get(0); + assertEquals(ADD, recordedOp.get(OP).asString()); + + assertTrue("credential-reference should be present in the recorded operation", recordedOp.hasDefined("credential-reference")); + ModelNode recordedCredRef = recordedOp.get("credential-reference"); + + assertTrue("clear-text should be present inside credential-reference", recordedCredRef.hasDefined("clear-text")); + String recordedClearText = recordedCredRef.get("clear-text").asString(); + assertEquals("clear-text should contain the original value", storePassword, recordedClearText); + } finally { + client.executeForResult(Util.createRemoveOperation(CREDENTIAL_STORE_ADDRESS)); + } + } + + private List getConfigurationChanges() throws UnsuccessfulOperationException { + ModelNode listChanges = Util.createOperation("list-changes", CONFIGURATION_CHANGES_ADDRESS); + ModelNode result = client.executeForResult(listChanges); + return result.asList(); + } + + private static String sha256(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] encodedHash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(encodedHash); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not available", e); + } + } +} From c1857a07b6a4f712e832a13e96299a1c81b5650d Mon Sep 17 00:00:00 2001 From: Yeray Borges Date: Thu, 12 Mar 2026 11:58:41 +0000 Subject: [PATCH 6/6] [WFCORE-7247] Test audit log readaction using Elytron scret-value --- .../auditlog/AuditLogRedactedTestCase.java | 237 ++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 testsuite/standalone/src/test/java/org/jboss/as/test/integration/auditlog/AuditLogRedactedTestCase.java diff --git a/testsuite/standalone/src/test/java/org/jboss/as/test/integration/auditlog/AuditLogRedactedTestCase.java b/testsuite/standalone/src/test/java/org/jboss/as/test/integration/auditlog/AuditLogRedactedTestCase.java new file mode 100644 index 00000000000..a5a795ab6fe --- /dev/null +++ b/testsuite/standalone/src/test/java/org/jboss/as/test/integration/auditlog/AuditLogRedactedTestCase.java @@ -0,0 +1,237 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.test.integration.auditlog; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HexFormat; +import java.util.List; +import java.util.regex.Pattern; + +import jakarta.inject.Inject; + +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.controller.operations.common.Util; +import org.jboss.as.domain.management.CoreManagementResourceDefinition; +import org.jboss.as.domain.management.audit.AccessAuditResourceDefinition; +import org.jboss.as.domain.management.audit.AuditLogLoggerResourceDefinition; +import org.jboss.dmr.ModelNode; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.wildfly.core.testrunner.ManagementClient; +import org.wildfly.core.testrunner.ServerSetup; +import org.wildfly.core.testrunner.WildFlyRunner; +import org.wildfly.test.stability.StabilityServerSetupSnapshotRestoreTasks; + +@RunWith(WildFlyRunner.class) +@ServerSetup({StabilityServerSetupSnapshotRestoreTasks.Community.class}) +public class AuditLogRedactedTestCase { + + @Inject + public ManagementClient managementClient; + + public Path logDir = new File(System.getProperty("jboss.home")).toPath() + .resolve("standalone") + .resolve("data") + .resolve("audit-log.log") + .toAbsolutePath(); + + public PathAddress auditLogConfigAddress = PathAddress.pathAddress( + CoreManagementResourceDefinition.PATH_ELEMENT, + AccessAuditResourceDefinition.PATH_ELEMENT, + AuditLogLoggerResourceDefinition.PATH_ELEMENT); + + private static final PathAddress CREDENTIAL_STORE_ADDRESS = PathAddress.pathAddress( + PathElement.pathElement("subsystem", "elytron"), + PathElement.pathElement("credential-store", "test-audit-cs")); + + @Before + public void before() throws Exception { + Files.deleteIfExists(logDir); + + ModelNode op = Util.getWriteAttributeOperation( + auditLogConfigAddress, + AuditLogLoggerResourceDefinition.ENABLED.getName(), + ModelNode.TRUE); + + managementClient.executeForResult(op); + + op = Util.getWriteAttributeOperation( + auditLogConfigAddress, + AuditLogLoggerResourceDefinition.LOG_READ_ONLY.getName(), + ModelNode.TRUE); + + managementClient.executeForResult(op); + + Assert.assertTrue(Files.exists(logDir)); + } + + @After + public void after() throws Exception { + removeCredentialStore(); + setAuditLogRedacted(true); + + ModelNode op = Util.getWriteAttributeOperation( + auditLogConfigAddress, + AuditLogLoggerResourceDefinition.LOG_READ_ONLY.getName(), + ModelNode.FALSE); + managementClient.executeForResult(op); + + op = Util.getWriteAttributeOperation( + auditLogConfigAddress, + AuditLogLoggerResourceDefinition.ENABLED.getName(), + ModelNode.FALSE); + + managementClient.executeForResult(op); + Assert.assertTrue(Files.exists(logDir)); + + Files.delete(logDir); + } + + @Test + public void testEnableAndDisableCoreAuditLog() throws Exception { + String storePassword = "storeSecretPassword"; + String alias = "audit-alias"; + String secondAlias = "audit-alias-2"; + String secretValue = "auditSecretValue"; + String expectedHash = sha256(secretValue); + + createCredentialStore(storePassword); + + try { + Files.delete(logDir); + + ModelNode addAlias = Util.createOperation("add-alias", CREDENTIAL_STORE_ADDRESS); + addAlias.get("alias").set(alias); + addAlias.get("secret-value").set(secretValue); + managementClient.executeForResult(addAlias); + + List records = readFile(logDir.toFile(), 1); + List ops = records.get(0).get("ops").asList(); + Assert.assertEquals(1, ops.size()); + ModelNode op = ops.get(0); + Assert.assertEquals("add-alias", op.get(ModelDescriptionConstants.OP).asString()); + Assert.assertEquals(expectedHash, op.get("secret-value").asString()); + Assert.assertNotEquals(secretValue, op.get("secret-value").asString()); + + ModelNode removeAlias = Util.createOperation("remove-alias", CREDENTIAL_STORE_ADDRESS); + removeAlias.get("alias").set(alias); + managementClient.executeForResult(removeAlias); + + setAuditLogRedacted(false); + Files.deleteIfExists(logDir); + + addAlias = Util.createOperation("add-alias", CREDENTIAL_STORE_ADDRESS); + addAlias.get("alias").set(secondAlias); + addAlias.get("secret-value").set(secretValue); + managementClient.executeForResult(addAlias); + + records = readFile(logDir.toFile(), 1); + ops = records.get(0).get("ops").asList(); + Assert.assertEquals(1, ops.size()); + op = ops.get(0); + Assert.assertEquals("add-alias", op.get(ModelDescriptionConstants.OP).asString()); + Assert.assertEquals(secretValue, op.get("secret-value").asString()); + + removeAlias = Util.createOperation("remove-alias", CREDENTIAL_STORE_ADDRESS); + removeAlias.get("alias").set(secondAlias); + managementClient.executeForResult(removeAlias); + } finally { + setAuditLogRedacted(true); + removeCredentialStore(); + } + } + + private void createCredentialStore(String storePassword) throws Exception { + ModelNode addOp = Util.createAddOperation(CREDENTIAL_STORE_ADDRESS); + addOp.get("relative-to").set("jboss.server.data.dir"); + addOp.get("location").set("test-audit.store"); + addOp.get("create").set(true); + ModelNode credRef = new ModelNode(); + credRef.get("clear-text").set(storePassword); + addOp.get("credential-reference").set(credRef); + managementClient.executeForResult(addOp); + } + + private void removeCredentialStore() throws Exception { + try { + ModelNode remove = Util.createRemoveOperation(CREDENTIAL_STORE_ADDRESS); + managementClient.executeForResult(remove); + + // Clean up the file created by the credential store + Path storePath = new File(System.getProperty("jboss.home")).toPath() + .resolve("standalone") + .resolve("data") + .resolve("test-audit.store") + .toAbsolutePath(); + Files.deleteIfExists(storePath); + } catch (Exception ignored) { + // best-effort cleanup + } + } + + private void setAuditLogRedacted(boolean redacted) throws Exception { + ModelNode op = Util.getWriteAttributeOperation( + auditLogConfigAddress, + AuditLogLoggerResourceDefinition.REDACTED.getName(), + redacted ? ModelNode.TRUE : ModelNode.FALSE); + managementClient.executeForResult(op); + } + + + private final Pattern DATE_STAMP_PATTERN = Pattern.compile("\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d - \\{"); + + private List readFile(File file, int expectedRecords) throws IOException { + List list = new ArrayList<>(); + + try (final BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { + StringWriter writer = null; + String line = reader.readLine(); + while (line != null) { + if (DATE_STAMP_PATTERN.matcher(line).matches()) { + if (writer != null) { + list.add(ModelNode.fromJSONString(writer.getBuffer().toString())); + } + writer = new StringWriter(); + writer.append("{"); + } else { + Assert.assertNotNull(writer); + writer.append("\n"); + writer.append(line); + } + line = reader.readLine(); + } + if (writer != null) { + list.add(ModelNode.fromJSONString(writer.getBuffer().toString())); + } + } + Assert.assertEquals(list.toString(), expectedRecords, list.size()); + return list; + } + + private static String sha256(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] encodedHash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + return HexFormat.of().formatHex(encodedHash); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not available", e); + } + } +}