Skip to content

Commit 5015013

Browse files
committed
fixup! Logging and cleanup
1 parent 2fc6bfc commit 5015013

File tree

2 files changed

+68
-27
lines changed

2 files changed

+68
-27
lines changed

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/CrossClusterApiKeySigner.java

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
import java.util.Collection;
3333
import java.util.Map;
3434
import java.util.Objects;
35+
import java.util.Optional;
3536
import java.util.Set;
3637
import java.util.concurrent.ConcurrentHashMap;
3738
import java.util.stream.Collectors;
38-
import java.util.stream.Stream;
3939

4040
import javax.net.ssl.X509KeyManager;
4141

@@ -45,6 +45,7 @@
4545
public class CrossClusterApiKeySigner {
4646
private final Logger logger = LogManager.getLogger(getClass());
4747
private final Environment environment;
48+
private static final Map<String, String> SIGNATURE_ALGORITHM_BY_TYPE = Map.of("RSA", "SHA256withRSA", "EC", "SHA256withECDSA");
4849

4950
private final Map<String, SigningConfig> signingConfigByClusterAlias = new ConcurrentHashMap<>();
5051

@@ -69,6 +70,8 @@ public void loadSigningConfig(String clusterAlias, @Nullable Settings settings,
6970
updateSecureSettings
7071
);
7172

73+
logger.trace("Loading signing config for [{}] with settings [{}]", clusterAlias, effectiveSettings);
74+
7275
SigningConfig signingConfig = new SigningConfig(null, null, effectiveSettings);
7376
if (effectiveSettings.getByPrefix(SETTINGS_PART_SIGNING).isEmpty() == false) {
7477
try {
@@ -80,16 +83,20 @@ public void loadSigningConfig(String clusterAlias, @Nullable Settings settings,
8083
);
8184
if (keyConfig.hasKeyMaterial()) {
8285
String alias = effectiveSettings.get(SETTINGS_PART_SIGNING + "." + KEYSTORE_ALIAS_SUFFIX);
83-
var keyPair = Strings.isNullOrEmpty(alias)
84-
? buildKeyPairForCluster(keyConfig)
85-
: buildKeyPairForCluster(keyConfig, alias);
86+
var keyPair = Strings.isNullOrEmpty(alias) ? buildKeyPair(keyConfig) : buildKeyPair(keyConfig, alias);
8687
if (keyPair != null) {
88+
logger.trace("Key pair [{}] found for [{}]", keyPair, clusterAlias);
8789
signingConfig = new SigningConfig(keyPair, keyConfig.getDependentFiles(), effectiveSettings);
8890
}
91+
} else {
92+
logger.error(Strings.format("No signing credentials found in signing config for cluster [%s]", clusterAlias));
8993
}
9094
} catch (Exception e) {
95+
// Since this can be called by the settings applier we don't want to surface an error here
9196
logger.error(Strings.format("Failed to load signing config for cluster [%s]", clusterAlias), e);
9297
}
98+
} else {
99+
logger.trace("No signing settings found for [{}]", clusterAlias);
93100
}
94101
return signingConfig;
95102
});
@@ -98,6 +105,7 @@ public void loadSigningConfig(String clusterAlias, @Nullable Settings settings,
98105
public X509CertificateSignature sign(String clusterAlias, String... headers) {
99106
SigningConfig signingConfig = signingConfigByClusterAlias.get(clusterAlias);
100107
if (signingConfig == null || signingConfig.keyPair() == null) {
108+
logger.trace("No signing config found for [{}] returning empty signature", clusterAlias);
101109
return null;
102110
}
103111
var keyPair = signingConfig.keyPair();
@@ -110,7 +118,10 @@ public X509CertificateSignature sign(String clusterAlias, String... headers) {
110118
final byte[] sigBytes = signature.sign();
111119
return new X509CertificateSignature(keyPair.certificate, algorithm, new BytesArray(sigBytes));
112120
} catch (GeneralSecurityException e) {
113-
throw new ElasticsearchSecurityException("Failed to sign cross cluster headers", e);
121+
throw new ElasticsearchSecurityException(
122+
Strings.format("Failed to sign cross cluster headers for cluster [%s]", clusterAlias),
123+
e
124+
);
114125
}
115126
}
116127

@@ -153,13 +164,14 @@ void reloadSigningConfigs(Set<String> clusterAliases) {
153164
clusterAliases.forEach(alias -> loadSigningConfig(alias, null, true));
154165
}
155166

156-
private X509KeyPair buildKeyPairForCluster(SslKeyConfig keyConfig) {
167+
private X509KeyPair buildKeyPair(SslKeyConfig keyConfig) {
157168
final X509KeyManager keyManager = keyConfig.createKeyManager();
158169
if (keyManager == null) {
159170
return null;
160171
}
161172

162-
final Set<String> aliases = Stream.of("RSA", "EC")
173+
final Set<String> aliases = SIGNATURE_ALGORITHM_BY_TYPE.keySet()
174+
.stream()
163175
.map(keyType -> keyManager.getServerAliases(keyType, null))
164176
.filter(Objects::nonNull)
165177
.flatMap(Arrays::stream)
@@ -182,12 +194,24 @@ private X509KeyPair buildKeyPairForCluster(SslKeyConfig keyConfig) {
182194
};
183195
}
184196

185-
private X509KeyPair buildKeyPairForCluster(SslKeyConfig keyConfig, String alias) {
197+
private X509KeyPair buildKeyPair(SslKeyConfig keyConfig, String alias) {
186198
final X509KeyManager keyManager = keyConfig.createKeyManager();
187199
if (keyManager == null) {
188200
return null;
189201
}
190202

203+
final String keyType = keyManager.getPrivateKey(alias).getAlgorithm();
204+
if (SIGNATURE_ALGORITHM_BY_TYPE.containsKey(keyType) == false) {
205+
throw new IllegalStateException(
206+
Strings.format(
207+
"The key associated with alias [%s] uses unsupported key algorithm type [%s], only %s is supported",
208+
alias,
209+
keyType,
210+
SIGNATURE_ALGORITHM_BY_TYPE.keySet()
211+
)
212+
);
213+
}
214+
191215
final X509Certificate[] chain = keyManager.getCertificateChain(alias);
192216
logger.trace("KeyConfig [{}] has entry for alias: [{}] [{}]", keyConfig, alias, chain != null);
193217

@@ -208,16 +232,20 @@ private static String calculateFingerprint(X509Certificate certificate) {
208232

209233
private record X509KeyPair(X509Certificate certificate, PrivateKey privateKey, String signatureAlgorithm, String fingerprint) {
210234
X509KeyPair(X509Certificate certificate, PrivateKey privateKey) {
211-
this(certificate, privateKey, switch (privateKey.getAlgorithm()) {
212-
case "RSA" -> "SHA256withRSA";
213-
case "EC" -> "SHA256withECDSA";
214-
default -> throw new IllegalArgumentException(
215-
"Unsupported Key Type [" + privateKey.getAlgorithm() + "] for [" + privateKey + "]"
216-
);
217-
}, calculateFingerprint(certificate));
235+
this(
236+
certificate,
237+
privateKey,
238+
Optional.ofNullable(SIGNATURE_ALGORITHM_BY_TYPE.get(privateKey.getAlgorithm()))
239+
.orElseThrow(
240+
() -> new IllegalArgumentException(
241+
"Unsupported Key Type [" + privateKey.getAlgorithm() + "] for [" + privateKey + "]"
242+
)
243+
),
244+
calculateFingerprint(certificate)
245+
);
218246
}
219247
}
220248

221-
private record SigningConfig(@Nullable X509KeyPair keyPair, Collection<Path> dependentFiles, @Nullable Settings settings) {}
249+
private record SigningConfig(@Nullable X509KeyPair keyPair, @Nullable Collection<Path> dependentFiles, @Nullable Settings settings) {}
222250

223251
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/CrossClusterApiKeySignerReloader.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.elasticsearch.common.settings.SecureString;
1515
import org.elasticsearch.common.settings.Setting;
1616
import org.elasticsearch.common.settings.Settings;
17+
import org.elasticsearch.core.Strings;
1718
import org.elasticsearch.transport.RemoteClusterSettings;
1819
import org.elasticsearch.watcher.FileChangesListener;
1920
import org.elasticsearch.watcher.FileWatcher;
@@ -26,7 +27,6 @@
2627
import java.nio.file.Path;
2728
import java.security.GeneralSecurityException;
2829
import java.util.HashMap;
29-
import java.util.HashSet;
3030
import java.util.List;
3131
import java.util.Map;
3232
import java.util.Set;
@@ -35,11 +35,17 @@
3535
import static org.elasticsearch.xpack.security.transport.CrossClusterApiKeySignerSettings.getDynamicSettings;
3636
import static org.elasticsearch.xpack.security.transport.CrossClusterApiKeySignerSettings.getSecureSettings;
3737

38+
/**
39+
* Responsible for reloading a provided {@link CrossClusterApiKeySigner} when updates are received from the following sources:
40+
* - Dynamic cluster settings
41+
* - Reloadable secure settings
42+
* - File content changes in any of the files pointed to by the cluster settings
43+
*/
3844
public final class CrossClusterApiKeySignerReloader implements ReloadableSecurityComponent {
3945

4046
private static final Logger logger = LogManager.getLogger(CrossClusterApiKeySignerReloader.class);
4147
private final CrossClusterApiKeySigner apiKeySigner;
42-
private final Set<Path> monitoredPaths = new HashSet<>();
48+
private final Map<Path, ChangeListener> monitoredPathToChangeListener = new HashMap<>();
4349

4450
public CrossClusterApiKeySignerReloader(
4551
ResourceWatcherService resourceWatcherService,
@@ -48,8 +54,8 @@ public CrossClusterApiKeySignerReloader(
4854
) {
4955
this.apiKeySigner = apiKeySigner;
5056
clusterSettings.addAffixGroupUpdateConsumer(getDynamicSettings(), (key, val) -> {
51-
logger.info("Updating cross cluster signing configuration for key [{}] with settings [{}]", key, val);
5257
apiKeySigner.loadSigningConfig(key, val.getByPrefix(RemoteClusterSettings.REMOTE_CLUSTER_SETTINGS_PREFIX + key + "."), false);
58+
logger.info("Updated signing configuration for [{}] due to update of cluster settings [{}]", key, val);
5359
watchDependentFilesForClusterAliases(
5460
apiKeySigner::reloadSigningConfigs,
5561
resourceWatcherService,
@@ -70,18 +76,23 @@ private void watchDependentFilesForClusterAliases(
7076
Map<Path, Set<String>> dependentFilesToClusterAliases
7177
) {
7278
dependentFilesToClusterAliases.forEach((path, clusterAliases) -> {
73-
if (monitoredPaths.contains(path)) {
79+
var existingChangeListener = monitoredPathToChangeListener.get(path);
80+
if (existingChangeListener != null) {
81+
logger.trace("Found existing listener for file [{}], adding clusterAliases {}", path, clusterAliases);
82+
existingChangeListener.addClusterAliases(clusterAliases);
7483
return;
7584
}
7685

86+
logger.trace("Adding listener for file [{}] for clusters {}", path, clusterAliases);
87+
7788
ChangeListener changeListener = new ChangeListener(clusterAliases, path, reloadConsumer);
7889
FileWatcher fileWatcher = new FileWatcher(path);
7990
fileWatcher.addListener(changeListener);
8091
try {
8192
resourceWatcherService.add(fileWatcher, Frequency.HIGH);
82-
monitoredPaths.add(path);
93+
monitoredPathToChangeListener.put(path, changeListener);
8394
} catch (IOException | SecurityException e) {
84-
logger.error("failed to start watching file [{}] - {}", path, e);
95+
logger.error(Strings.format("failed to start watching file [%s]", path), e);
8596
}
8697
});
8798
}
@@ -90,6 +101,10 @@ private record ChangeListener(Set<String> clusterAliases, Path file, Consumer<Se
90101
implements
91102
FileChangesListener {
92103

104+
public void addClusterAliases(Set<String> clusterAliases) {
105+
this.clusterAliases.addAll(clusterAliases);
106+
}
107+
93108
@Override
94109
public void onFileCreated(Path file) {
95110
onFileChanged(file);
@@ -104,7 +119,7 @@ public void onFileDeleted(Path file) {
104119
public void onFileChanged(Path file) {
105120
if (this.file.equals(file)) {
106121
reloadConsumer.accept(this.clusterAliases);
107-
logger.info("Updated signing configuration for [{}] config(s) using file [{}]", clusterAliases.size(), file);
122+
logger.info("Updated signing configuration for [{}] config(s) due to update of file [{}]", clusterAliases.size(), file);
108123
}
109124
}
110125
}
@@ -121,10 +136,11 @@ public void reload(Settings settings) {
121136
// Only update signing config if settings were found, since empty config means config deletion
122137
if (settingsForCluster.isEmpty() == false) {
123138
apiKeySigner.loadSigningConfig(clusterAlias, settingsForCluster, true);
139+
logger.info("Updated signing configuration for [{}] due to reload of secure settings", clusterAlias);
124140
}
125141
});
126142
} catch (GeneralSecurityException e) {
127-
logger.error("Keystore exception while reloading CrossClusterApiKeySigner", e);
143+
logger.error("Keystore exception while reloading signing configuration after reload of secure settings", e);
128144
}
129145
}
130146

@@ -194,9 +210,6 @@ public void writeTo(StreamOutput out) throws IOException {
194210
};
195211
}
196212

197-
/**
198-
* A single-purpose record for the internal implementation of extractSecureSettings
199-
*/
200213
private record SecureSettingValue(SecureString value, byte[] sha256Digest) {}
201214

202215
}

0 commit comments

Comments
 (0)