Skip to content

Commit 63fb874

Browse files
committed
Merge branch 'cassandra-5.0' into trunk
2 parents deadaea + e45a330 commit 63fb874

File tree

7 files changed

+147
-1
lines changed

7 files changed

+147
-1
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ Merged from 5.0:
288288
* Prioritize built indexes in IndexStatusManager (CASSANDRA-19400)
289289
* Add java.base/java.lang.reflect among opens for jvm11-client.options (CASSANDRA-19780)
290290
Merged from 4.1:
291+
* Redact security-sensitive information in system_views.settings (CASSANDRA-20856)
291292
* Improve CommitLogSegmentReader to skip SyncBlocks correctly in case of CRC errors (CASSANDRA-20664)
292293
* Do not crash on first boot with data_disk_usage_max_disk_size set when data directory is not created yet (CASSANDRA-20787)
293294
* Rework / simplification of nodetool get/setguardrailsconfig commands (CASSANDRA-20778)

src/java/org/apache/cassandra/config/EncryptionOptions.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,21 @@ static Set<String> asSet()
119119
* truststore_passwords configurations as they are in plaintext format.
120120
*/
121121
public final ParameterizedClass ssl_context_factory;
122+
@Redacted
122123
public final String keystore;
123124
@Nullable
125+
@Redacted
124126
public final String keystore_password;
125127
@Nullable
128+
@Redacted
126129
public final String keystore_password_file;
130+
@Redacted
127131
public final String truststore;
128132
@Nullable
133+
@Redacted
129134
public final String truststore_password;
130135
@Nullable
136+
@Redacted
131137
public final String truststore_password_file;
132138
public final List<String> cipher_suites;
133139
protected String protocol;

src/java/org/apache/cassandra/config/JMXServerOptions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public class JMXServerOptions
5959
public final String login_config_file;
6060

6161
// location for credentials file if using JVM's file-based authentication
62+
@Redacted
6263
public final String password_file;
6364
// location of standard access file, if using JVM's file-based access control
6465
public final String access_file;
@@ -132,7 +133,7 @@ public String toString()
132133
", jmx_port=" + jmx_port +
133134
", rmi_port=" + rmi_port +
134135
", authenticate=" + authenticate +
135-
", jmx_encryption_options=" + jmx_encryption_options +
136+
", jmx_encryption_options=" + jmxOptionsString +
136137
", login_config_name='" + login_config_name + '\'' +
137138
", login_config_file='" + login_config_file + '\'' +
138139
", password_file='" + password_file + '\'' +

src/java/org/apache/cassandra/config/Properties.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818
package org.apache.cassandra.config;
1919

20+
import java.lang.annotation.Annotation;
2021
import java.lang.reflect.Constructor;
2122
import java.util.ArrayDeque;
2223
import java.util.Collection;
@@ -210,5 +211,25 @@ public Object get(Object object)
210211
throw e;
211212
}
212213
}
214+
215+
/**
216+
* If there is a hierarchy of settings, like
217+
* </p>
218+
* {@code a.b.c.{d,e,f,g,h}}
219+
* </p>
220+
* and we put e.g. {@link Redacted} on {@code c},
221+
* then all {@code d,e,f,g,h} will be redacted as well automatically.
222+
* This is handy for cases when we want to redact whole family of properties by one shot.
223+
*
224+
* @param aClass annotation to get
225+
* @return found annotation of given type on root or on leaf, null when not present.
226+
* @param <A> type of annotation
227+
*/
228+
@Override
229+
public <A extends Annotation> A getAnnotation(Class<A> aClass)
230+
{
231+
A annotation = root.getAnnotation(aClass);
232+
return annotation != null ? annotation : leaf.getAnnotation(aClass);
233+
}
213234
}
214235
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.cassandra.config;
20+
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
/**
27+
* Annotation supposed to be placed on any field in Config (and its subproperties in embedded configuration classes)
28+
* which will be redacted e.g. password or similar.
29+
* <p>
30+
* User querying system_views.settings will be shown redacted values on such fields.
31+
*/
32+
@Retention(RetentionPolicy.RUNTIME)
33+
@Target({ ElementType.FIELD })
34+
public @interface Redacted
35+
{
36+
String REDACTED_STRING = "<REDACTED>";
37+
38+
/**
39+
* @return redacted value, defaults to {@code <REDACTED>}.
40+
*/
41+
String redactedValue() default REDACTED_STRING;
42+
}

src/java/org/apache/cassandra/db/virtual/SettingsTable.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.common.collect.ImmutableMap;
2727

2828
import org.apache.cassandra.config.Config;
29+
import org.apache.cassandra.config.Redacted;
2930
import org.apache.cassandra.config.DatabaseDescriptor;
3031
import org.apache.cassandra.config.Loader;
3132
import org.apache.cassandra.config.Properties;
@@ -89,13 +90,34 @@ public DataSet data()
8990
@VisibleForTesting
9091
String getValue(Property prop)
9192
{
93+
Redacted maybeCredential = prop.getAnnotation(Redacted.class);
94+
if (maybeCredential != null)
95+
return maybeCredential.redactedValue();
96+
9297
Object value = prop.get(config);
9398
if (value == null)
9499
return null;
95100

96101
if (value.getClass().isArray())
97102
return Arrays.asList((Object[]) value).toString();
98103

104+
if (value instanceof Map)
105+
{
106+
Map<String, Object> map = new HashMap<>();
107+
for (Map.Entry<String, Object> entry : ((Map<String, Object>) value).entrySet())
108+
{
109+
// this is done on best-effort basis as we do not have names in parameters
110+
// inherently under control as this is what a user is responsible for
111+
// when dealing with custom implementations
112+
if (entry.getKey().endsWith("_password") || entry.getKey().equals("password"))
113+
map.put(entry.getKey(), Redacted.REDACTED_STRING);
114+
else
115+
map.put(entry.getKey(), entry.getValue());
116+
}
117+
118+
return map.toString();
119+
}
120+
99121
return value.toString();
100122
}
101123

test/unit/org/apache/cassandra/db/virtual/SettingsTableTest.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818

1919
package org.apache.cassandra.db.virtual;
2020

21+
import java.util.LinkedHashMap;
2122
import java.util.List;
2223
import java.util.Map;
24+
import java.util.Set;
25+
import java.util.TreeMap;
2326
import java.util.stream.Collectors;
2427

2528
import com.google.common.collect.ImmutableList;
@@ -30,16 +33,20 @@
3033
import com.datastax.driver.core.ResultSet;
3134
import com.datastax.driver.core.Row;
3235
import org.apache.cassandra.config.Config;
36+
import org.apache.cassandra.config.Redacted;
37+
import org.apache.cassandra.config.DefaultLoader;
3338
import org.apache.cassandra.config.DurationSpec;
3439
import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.Builder;
3540
import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption;
3641
import org.apache.cassandra.config.JMXServerOptions;
3742
import org.apache.cassandra.config.ParameterizedClass;
43+
import org.apache.cassandra.config.TransparentDataEncryptionOptions;
3844
import org.apache.cassandra.cql3.CQLTester;
3945
import org.apache.cassandra.security.SSLFactory;
4046
import org.yaml.snakeyaml.introspector.Property;
4147

4248
import static org.apache.cassandra.config.EncryptionOptions.ClientEncryptionOptions.ClientAuth.REQUIRED;
49+
import static java.util.stream.Collectors.toMap;
4350

4451
public class SettingsTableTest extends CQLTester
4552
{
@@ -62,6 +69,16 @@ public void config()
6269
config.commitlog_sync_group_window = new DurationSpec.IntMillisecondsBound(0);
6370
config.credentials_update_interval = null;
6471
config.data_file_directories = new String[] {"/my/data/directory", "/another/data/directory"};
72+
73+
Map<String, String> params = new LinkedHashMap<>();
74+
params.put("keystore_password", "password");
75+
params.put("key_password", "password");
76+
params.put("keystore", "conf/.keystore");
77+
config.transparent_data_encryption_options = new TransparentDataEncryptionOptions(false,
78+
"AES/CBC/PKCS5Padding",
79+
"alias",
80+
new ParameterizedClass("SomeClass",
81+
params));
6582
table = new SettingsTable(KS_NAME, config);
6683
VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table)));
6784
disablePreparedReuseForTest();
@@ -299,4 +316,40 @@ public void testTransparentEncryptionOptionsOverride() throws Throwable
299316
config.transparent_data_encryption_options.iv_length = 7;
300317
check(pre + "iv_length", "7");
301318
}
319+
320+
@Test
321+
public void testRedaction()
322+
{
323+
assertValue("transparent_data_encryption_options.key_provider.parameters",
324+
String.format("{keystore_password=%s, keystore=conf/.keystore, key_password=%s}",
325+
Redacted.REDACTED_STRING,
326+
Redacted.REDACTED_STRING));
327+
328+
Set<Map.Entry<String, Property>> entries = new DefaultLoader().flatten(Config.class)
329+
.entrySet()
330+
.stream()
331+
.filter(e -> e.getValue().getAnnotation(Redacted.class) != null)
332+
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e, r) -> e, TreeMap::new))
333+
.entrySet();
334+
335+
Assert.assertFalse(entries.isEmpty());
336+
337+
for (Map.Entry<String, Property> entry : entries)
338+
{
339+
logger.info("redacted {}", entry.getKey());
340+
assertValue(entry.getKey(), entry.getValue().getAnnotation(Redacted.class).redactedValue());
341+
}
342+
}
343+
344+
private void assertValue(String settingName, String expectedValue)
345+
{
346+
List<Row> all = executeNet(String.format("SELECT * from vts.settings WHERE name = '%s'", settingName)).all();
347+
Assert.assertFalse(all.isEmpty());
348+
Row row = all.get(0);
349+
String name = row.getString("name");
350+
String value = row.getString("value");
351+
352+
Assert.assertEquals(settingName, name);
353+
Assert.assertEquals(expectedValue, value);
354+
}
302355
}

0 commit comments

Comments
 (0)