-
Notifications
You must be signed in to change notification settings - Fork 704
Pluggable Secret Managers support in Apache OFBiz trunk #1367
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ashishvijaywargiya
wants to merge
47
commits into
apache:trunk
Choose a base branch
from
ashishvijaywargiya:various-secret-manager
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 26 commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
b1611dc
Adding support of Secret Manager framework support in Apache OFBiz. F…
ashishvijaywargiya 348d346
Adding the support of AES Encryption based password. Also added the g…
ashishvijaywargiya dc09410
Testing with aws secret manager has been completed. I tested it with …
ashishvijaywargiya 653ee97
Changing -Pvalue to -PdbPassword for clarity.
ashishvijaywargiya 2d6aa95
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya fd38a3c
Added the fallback support in the code base. If remote services of se…
ashishvijaywargiya b9d4683
Adding the fallback option for all the plugins. And also improved con…
ashishvijaywargiya 77e264e
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya 3a75ccf
Improved: Use Shiro's AesCipherService for ConfigCryptoUtil encryption
ashishvijaywargiya f9520a5
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya 4de4287
Adding a very important feature which I envisioned before I started t…
ashishvijaywargiya bdc41a1
Adding the two files SecretValueResolver.java and SecretValueResolver…
ashishvijaywargiya a490fd7
Committing the missed build.gradle file in which I added a new gradle…
ashishvijaywargiya 229d62b
I renamed the gradle task and also renamed the master key to OFBIZ_MA…
ashishvijaywargiya 63d4f7d
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya 400b9e1
Add masked stdin prompts for generateDBPassword and generateEncrypted…
ashishvijaywargiya b4d6060
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya 723ca7f
Adding a support of Secret Manager screen to add encrypted secrets an…
ashishvijaywargiya 50ab873
The secret-value marker (default: LOOKUP, was SECRET) is now configur…
ashishvijaywargiya 7fcf87a
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya dfcf653
Fixing the following points:
ashishvijaywargiya 4949fca
Fix EncryptValue screen 500 error and usage-stats rendering
ashishvijaywargiya 5477136
Fixing formatting issues.
ashishvijaywargiya 9d21423
Fixing formatting issues.
ashishvijaywargiya 5717a3f
Fixing console errors for FTL file.
ashishvijaywargiya a6962d6
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya abc1cb0
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya 6da2979
Add automatic secret-rotation sync for SystemProperty-backed secrets
ashishvijaywargiya faea278
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya d9152f0
Add per-key alias overrides and shared helpers for secret-provider pl…
ashishvijaywargiya c6af8db
Add SecretAuditLog entity and async audit trail for secret access events
ashishvijaywargiya 86ee418
Fixing the build failure related to code formatting.
ashishvijaywargiya 4f48017
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya 4f69f62
Add DENIED outcome audit events for all SECRET_MAINT and SECRET_AUDIT…
ashishvijaywargiya 2a919c7
Add ROTATION_POLL summary audit event at start/end of each scheduled …
ashishvijaywargiya e6622ad
Wire SecretAuditContainer to pass the configured delegator name to Se…
ashishvijaywargiya 73a58ba
Add provider health-status indicator and Secret Usage Statistics screen
ashishvijaywargiya d8cb9e5
Following the best practices in Groovy file.
ashishvijaywargiya 42cfc85
Add reEncryptAllSecrets Gradle task for master-key rotation
ashishvijaywargiya efcfd18
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya 285dc92
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya 1642e1d
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya b5decc1
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya c0e82ad
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya 4b54144
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya ef38c78
Removing the mysql driver that I mistakenly committed.
ashishvijaywargiya eeb9df1
Merge branch 'apache:trunk' into various-secret-manager
ashishvijaywargiya File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
180 changes: 180 additions & 0 deletions
180
framework/base/src/main/java/org/apache/ofbiz/base/crypto/ConfigCryptoUtil.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,180 @@ | ||
| /******************************************************************************* | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, | ||
| * software distributed under the License is distributed on an | ||
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| * KIND, either express or implied. See the License for the | ||
| * specific language governing permissions and limitations | ||
| * under the License. | ||
| *******************************************************************************/ | ||
| package org.apache.ofbiz.base.crypto; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
| import java.security.GeneralSecurityException; | ||
| import java.util.Base64; | ||
| import java.util.Properties; | ||
|
|
||
| import javax.crypto.SecretKeyFactory; | ||
| import javax.crypto.spec.PBEKeySpec; | ||
|
|
||
| import org.apache.ofbiz.base.util.GeneralException; | ||
| import org.apache.ofbiz.base.util.UtilProperties; | ||
| import org.apache.shiro.crypto.CryptoException; | ||
| import org.apache.shiro.crypto.cipher.AesCipherService; | ||
| import org.apache.shiro.lang.util.ByteSource; | ||
|
|
||
| /** | ||
| * AES-256-GCM encryption/decryption for configuration values such as the | ||
| * database passwords stored in {@code passwords.properties} as {@code ENC(...)}. | ||
| * | ||
| * <p>The AES key is derived from a master key (e.g. the {@code OFBIZ_MASTER_KEY} | ||
| * environment variable) via PBKDF2WithHmacSHA256, so the same master key always | ||
| * yields the same AES key. The actual cipher operations are delegated to Shiro's | ||
| * {@link AesCipherService} (the same library used by | ||
| * {@link org.apache.ofbiz.entity.util.EntityCrypto}), which defaults to GCM mode | ||
| * and prepends a random IV to the ciphertext.</p> | ||
| */ | ||
| public final class ConfigCryptoUtil { | ||
|
|
||
| private static final String KEY_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA256"; | ||
| // Static salt: the master key is the actual secret; the salt only needs to defeat | ||
| // precomputed rainbow tables, not provide per-installation uniqueness. | ||
| private static final byte[] SALT = "OFBizConfigCryptoUtilSalt".getBytes(StandardCharsets.UTF_8); | ||
| private static final int KEY_LENGTH_BITS = 256; | ||
|
|
||
| // Note: AesCipherService derives the GCM tag length from getKeySize(), which must stay at | ||
| // its default of 128 (a valid GCM tag length). The 256-bit AES key below is passed directly | ||
| // to encrypt/decrypt and does not depend on this setting. | ||
| private static final AesCipherService CIPHER_SERVICE = new AesCipherService(); | ||
|
|
||
| // Read config once at class-load time using raw Properties to avoid re-entrancy via | ||
| // UtilProperties.getPropertyValue() → SecretValueResolver → ConfigCryptoUtil. | ||
| private static final Properties SECURITY_PROPERTIES = UtilProperties.getProperties("security"); | ||
|
|
||
| /** Name of the environment variable holding the AES master key; configurable via secret.master.key.env.var. */ | ||
| public static final String MASTER_KEY_ENV_VAR = SECURITY_PROPERTIES != null | ||
| ? SECURITY_PROPERTIES.getProperty("secret.master.key.env.var", "OFBIZ_MASTER_KEY").trim() | ||
| : "OFBIZ_MASTER_KEY"; | ||
|
|
||
| // PBKDF2 iteration count. Raising this requires re-encrypting all existing ENC(...) values | ||
| // because the derived AES key changes when the iteration count changes. | ||
| static final int ITERATIONS = readIterations(); | ||
|
|
||
| private static int readIterations() { | ||
| if (SECURITY_PROPERTIES == null) { | ||
| return 310000; | ||
| } | ||
| try { | ||
| int v = Integer.parseInt( | ||
| SECURITY_PROPERTIES.getProperty("secret.pbkdf2.iterations", "310000").trim()); | ||
| return v > 0 ? v : 310000; | ||
| } catch (NumberFormatException e) { | ||
| return 310000; | ||
| } | ||
| } | ||
|
|
||
| private static byte[] deriveKey(String masterKey) throws GeneralSecurityException { | ||
| SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_ALGORITHM); | ||
| PBEKeySpec spec = new PBEKeySpec(masterKey.toCharArray(), SALT, ITERATIONS, KEY_LENGTH_BITS); | ||
| try { | ||
| return factory.generateSecret(spec).getEncoded(); | ||
| } finally { | ||
| spec.clearPassword(); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Encrypts {@code plainText} with a key derived from {@code masterKey}. | ||
| * @return Base64 string containing the random IV followed by the ciphertext (with GCM auth tag) | ||
| */ | ||
| public static String encrypt(String plainText, String masterKey) throws GeneralException { | ||
| if (masterKey == null || masterKey.isEmpty()) { | ||
| throw new GeneralException("masterKey must not be null or empty"); | ||
| } | ||
| try { | ||
| byte[] key = deriveKey(masterKey); | ||
| ByteSource encrypted = CIPHER_SERVICE.encrypt(plainText.getBytes(StandardCharsets.UTF_8), key); | ||
| return encrypted.toBase64(); | ||
| } catch (GeneralSecurityException | CryptoException e) { | ||
| throw new GeneralException("Unable to encrypt value", e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Reverses {@link #encrypt(String, String)}: derives the AES key from {@code masterKey} | ||
| * and lets {@link AesCipherService} split the IV from the ciphertext and decrypt. | ||
| */ | ||
| public static String decrypt(String encryptedBase64, String masterKey) throws GeneralException { | ||
| if (masterKey == null || masterKey.isEmpty()) { | ||
| throw new GeneralException("masterKey must not be null or empty"); | ||
| } | ||
| try { | ||
| byte[] key = deriveKey(masterKey); | ||
| byte[] payload = Base64.getDecoder().decode(encryptedBase64); | ||
| byte[] decrypted = CIPHER_SERVICE.decrypt(payload, key).getClonedBytes(); | ||
| return new String(decrypted, StandardCharsets.UTF_8); | ||
| } catch (GeneralSecurityException | CryptoException | IllegalArgumentException e) { | ||
| throw new GeneralException("Unable to decrypt value: verify the master key is correct", e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Returns the plaintext of {@code rawValue} if it is wrapped in {@code ENC(...)}, | ||
| * otherwise returns it unchanged. | ||
| * | ||
| * <p>All {@link org.apache.ofbiz.base.secret.SecretProvider} implementations should | ||
| * call this after fetching a value from their remote backend so that operators can | ||
| * optionally add a client-side AES-256-GCM layer on top of whatever the vault already | ||
| * provides. The master key is read from the {@code OFBIZ_MASTER_KEY} environment variable.</p> | ||
| * | ||
| * @param rawValue the value as returned by the secret backend (plaintext or {@code ENC(...)}) | ||
| * @param secretName used only in error messages to identify which secret failed | ||
| * @return the plaintext value — either the original string or the decrypted one | ||
| * @throws GeneralException if the value is encrypted but {@code OFBIZ_MASTER_KEY} is missing, | ||
| * or if decryption fails (e.g. wrong key) | ||
| */ | ||
| public static String decryptIfEncrypted(String rawValue, String secretName) throws GeneralException { | ||
| if (!rawValue.startsWith("ENC(") || !rawValue.endsWith(")")) { | ||
| return rawValue; | ||
| } | ||
| String masterKey = System.getenv(MASTER_KEY_ENV_VAR); | ||
| if (masterKey == null || masterKey.isEmpty()) { | ||
| throw new GeneralException("Secret '" + secretName + "' is encrypted (ENC(...)) but the " | ||
| + MASTER_KEY_ENV_VAR + " environment variable is not set"); | ||
| } | ||
| String base64 = rawValue.substring("ENC(".length(), rawValue.length() - 1); | ||
| try { | ||
| return decrypt(base64, masterKey); | ||
| } catch (GeneralException e) { | ||
| throw new GeneralException("Failed to decrypt secret '" + secretName | ||
| + "': verify OFBIZ_MASTER_KEY matches the key used to encrypt", e); | ||
| } | ||
| } | ||
|
|
||
| private ConfigCryptoUtil() { } | ||
|
|
||
| /** | ||
| * Command-line helper that prints {@code ENC(<base64>)} for a given master key and plaintext | ||
| * value. Invoked via the {@code generateDBPassword} and {@code generateEncryptedSecret} | ||
| * Gradle tasks to produce values for {@code passwords.properties} | ||
| * ({@code jdbc-password.<lookup-key>=ENC(...)}) and {@code SystemProperty.systemPropertyValue} | ||
| * respectively. | ||
| */ | ||
| public static void main(String[] args) throws GeneralException { | ||
| if (args.length != 2) { | ||
| System.err.println("Usage: ConfigCryptoUtil <masterKey> <plainTextValue>"); | ||
| System.exit(1); | ||
| return; | ||
| } | ||
| System.out.println("ENC(" + encrypt(args[1], args[0]) + ")"); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.