This is an automated email from the ASF dual-hosted git repository.
mridulpathak pushed a commit to branch release24.09
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
The following commit(s) were added to refs/heads/release24.09 by this push:
new bcb6a58c2a Improved: added a rotateSecretKeys Gradle task to
unconditionally replace an already-set JWT/password secret key
bcb6a58c2a is described below
commit bcb6a58c2a6ee11176685e354b1ddff01788602a
Author: Mridul Pathak <[email protected]>
AuthorDate: Fri Sep 25 14:26:56 2026 +0530
Improved: added a rotateSecretKeys Gradle task to unconditionally replace
an already-set JWT/password secret key
generateSecretKeys only fills in security.token.key and
login.secret_key_string when they are missing or blank, and leaves an
already-set value untouched. There was previously no supported way to replace
an existing value other than editing security.properties by hand. Added
rotateSecretKeys, which unconditionally regenerates both keys, and pointed the
security.properties comments at it. generateSecretKeys itself is unchanged in
behavior.
---
build.gradle | 57 +++++++++++++++++----------
framework/security/config/security.properties | 8 +++-
2 files changed, 43 insertions(+), 22 deletions(-)
diff --git a/build.gradle b/build.gradle
index 57465f6893..41d54f0c53 100644
--- a/build.gradle
+++ b/build.gradle
@@ -740,29 +740,31 @@ task gitInfoFooter(group: sysadminGroup, description:
'Update the Git Branch-rev
}
}
+// Writes a cryptographically secure random key for propertyName into
propertiesFile. Unless force is
+// true, a property that is already set (uncommented, non-blank) is left
untouched and the call is a no-op.
+def generateAndWriteKey(File propertiesFile, String propertyName, boolean
force = false) {
+ def content = propertiesFile.text
+ def escapedName = propertyName.replace('.', '\\.')
+ if (!force && content =~ /(?m)^${escapedName}=.+$/) {
+ println "Secret key '${propertyName}' is already set, skipping."
+ return
+ }
+ def keyBytes = new byte[48] // 48 bytes * 4/3 = 64 Base64 chars (no
padding needed)
+ new java.security.SecureRandom().nextBytes(keyBytes)
+ def key = java.util.Base64.getEncoder().encodeToString(keyBytes)
+ if (content =~ /(?m)^#?${escapedName}=.*$/) {
+ content = content.replaceAll(/(?m)^#?${escapedName}=.*$/,
"${propertyName}=${key}")
+ } else {
+ content += "\n${propertyName}=${key}\n"
+ }
+ propertiesFile.text = content
+}
+
task generateSecretKeys(group: sysadminGroup,
description: 'Generate cryptographically secure secret keys for JWT
token signing, password encryption, '
- + 'and AdminServer authentication, and write them to their
properties files') {
+ + 'and AdminServer authentication, and write them to their
properties files. A property that is '
+ + 'already set is left untouched; use rotateSecretKeys to
replace an existing JWT/password key.') {
doLast {
- def generateAndWriteKey = { File propertiesFile, String propertyName ->
- def content = propertiesFile.text
- def escapedName = propertyName.replace('.', '\\.')
- // Only generate a key if the property is missing or commented out
- if (content =~ /(?m)^${escapedName}=.+$/) {
- println "Secret key '${propertyName}' is already set,
skipping."
- return
- }
- def keyBytes = new byte[48] // 48 bytes * 4/3 = 64 Base64 chars
(no padding needed)
- new java.security.SecureRandom().nextBytes(keyBytes)
- def key = java.util.Base64.getEncoder().encodeToString(keyBytes)
- if (content =~ /(?m)^#?${escapedName}=.*$/) {
- content = content.replaceAll(/(?m)^#?${escapedName}=.*$/,
"${propertyName}=${key}")
- } else {
- content += "\n${propertyName}=${key}\n"
- }
- propertiesFile.text = content
- }
-
def securityPropertiesFile =
file('framework/security/config/security.properties')
generateAndWriteKey(securityPropertiesFile, 'login.secret_key_string')
generateAndWriteKey(securityPropertiesFile, 'security.token.key')
@@ -774,6 +776,21 @@ task generateSecretKeys(group: sysadminGroup,
}
}
+task rotateSecretKeys(group: sysadminGroup,
+ description: 'Unconditionally regenerate security.token.key (JWT
signing) and login.secret_key_string '
+ + '(password encryption) in security.properties, overwriting
any existing value. Unlike '
+ + 'generateSecretKeys, this always overwrites: existing JWT
tokens will stop validating and any '
+ + 'in-flight forgot-password link encrypted with the old
login.secret_key_string will stop '
+ + 'decrypting.') {
+ doLast {
+ def securityPropertiesFile =
file('framework/security/config/security.properties')
+ generateAndWriteKey(securityPropertiesFile, 'login.secret_key_string',
true)
+ generateAndWriteKey(securityPropertiesFile, 'security.token.key', true)
+
+ println "Secret keys rotated. Keep the new keys secret and do not
commit them to version control."
+ }
+}
+
// ========== OFBiz Plugin Management ==========
task createPlugin(group: ofbizPlugin, description: 'create a new plugin
component based on specified templates') {
doLast {
diff --git a/framework/security/config/security.properties
b/framework/security/config/security.properties
index a53645b4d1..08cc1973a2 100644
--- a/framework/security/config/security.properties
+++ b/framework/security/config/security.properties
@@ -144,7 +144,9 @@ security.login.externalLoginKey.enabled=true
# -- Security key used to encrypt and decrypt the autogenerated password in
forgot password functionality.
# Read Passwords and JWT (JSON Web Tokens) usage documentation to choose
the way you want to store this key
# The key must be 512 bits (ie 64 chars) as we use HMAC512 to create the
token, cf. OFBIZ-12724
-# Run './gradlew generateSecretKeys' to generate a cryptographically secure
random key.
+# Run './gradlew generateSecretKeys' to generate a cryptographically secure
random key; this only fills in
+# a missing/blank value and leaves an existing one untouched. To replace an
already-set value, run
+# './gradlew rotateSecretKeys' instead.
login.secret_key_string=
# -- Time To Live of the token send to the external server in seconds
@@ -157,7 +159,9 @@ security.internal.sso.enabled=false
# -- The secret key for the JWT token signature.
# Read Passwords and JWT (JSON Web Tokens) usage documentation to choose
the way you want to store this key
# The key must be 512 bits (ie 64 chars) as we use HMAC512 to create the
token, cf. OFBIZ-12724
-# Run './gradlew generateSecretKeys' to generate a cryptographically secure
random key.
+# Run './gradlew generateSecretKeys' to generate a cryptographically secure
random key; this only fills in
+# a missing/blank value and leaves an existing one untouched. To replace an
already-set value, run
+# './gradlew rotateSecretKeys' instead.
security.token.key=
# -- Allowed subdirectories (relative to ofbiz.home) for the OFBIZ_FILE /
OFBIZ_FILE_BIN data resource types.