mirror of https://github.com/apache/cassandra
Merge branch 'cassandra-5.0' into trunk
This commit is contained in:
commit
63fb8741b8
|
|
@ -288,6 +288,7 @@ Merged from 5.0:
|
|||
* Prioritize built indexes in IndexStatusManager (CASSANDRA-19400)
|
||||
* Add java.base/java.lang.reflect among opens for jvm11-client.options (CASSANDRA-19780)
|
||||
Merged from 4.1:
|
||||
* Redact security-sensitive information in system_views.settings (CASSANDRA-20856)
|
||||
* Improve CommitLogSegmentReader to skip SyncBlocks correctly in case of CRC errors (CASSANDRA-20664)
|
||||
* Do not crash on first boot with data_disk_usage_max_disk_size set when data directory is not created yet (CASSANDRA-20787)
|
||||
* Rework / simplification of nodetool get/setguardrailsconfig commands (CASSANDRA-20778)
|
||||
|
|
|
|||
|
|
@ -119,15 +119,21 @@ public abstract class EncryptionOptions<T extends EncryptionOptions<T>>
|
|||
* truststore_passwords configurations as they are in plaintext format.
|
||||
*/
|
||||
public final ParameterizedClass ssl_context_factory;
|
||||
@Redacted
|
||||
public final String keystore;
|
||||
@Nullable
|
||||
@Redacted
|
||||
public final String keystore_password;
|
||||
@Nullable
|
||||
@Redacted
|
||||
public final String keystore_password_file;
|
||||
@Redacted
|
||||
public final String truststore;
|
||||
@Nullable
|
||||
@Redacted
|
||||
public final String truststore_password;
|
||||
@Nullable
|
||||
@Redacted
|
||||
public final String truststore_password_file;
|
||||
public final List<String> cipher_suites;
|
||||
protected String protocol;
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ public class JMXServerOptions
|
|||
public final String login_config_file;
|
||||
|
||||
// location for credentials file if using JVM's file-based authentication
|
||||
@Redacted
|
||||
public final String password_file;
|
||||
// location of standard access file, if using JVM's file-based access control
|
||||
public final String access_file;
|
||||
|
|
@ -132,7 +133,7 @@ public class JMXServerOptions
|
|||
", jmx_port=" + jmx_port +
|
||||
", rmi_port=" + rmi_port +
|
||||
", authenticate=" + authenticate +
|
||||
", jmx_encryption_options=" + jmx_encryption_options +
|
||||
", jmx_encryption_options=" + jmxOptionsString +
|
||||
", login_config_name='" + login_config_name + '\'' +
|
||||
", login_config_file='" + login_config_file + '\'' +
|
||||
", password_file='" + password_file + '\'' +
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
package org.apache.cassandra.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
|
|
@ -210,5 +211,25 @@ public final class Properties
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If there is a hierarchy of settings, like
|
||||
* </p>
|
||||
* {@code a.b.c.{d,e,f,g,h}}
|
||||
* </p>
|
||||
* and we put e.g. {@link Redacted} on {@code c},
|
||||
* then all {@code d,e,f,g,h} will be redacted as well automatically.
|
||||
* This is handy for cases when we want to redact whole family of properties by one shot.
|
||||
*
|
||||
* @param aClass annotation to get
|
||||
* @return found annotation of given type on root or on leaf, null when not present.
|
||||
* @param <A> type of annotation
|
||||
*/
|
||||
@Override
|
||||
public <A extends Annotation> A getAnnotation(Class<A> aClass)
|
||||
{
|
||||
A annotation = root.getAnnotation(aClass);
|
||||
return annotation != null ? annotation : leaf.getAnnotation(aClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.cassandra.config;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation supposed to be placed on any field in Config (and its subproperties in embedded configuration classes)
|
||||
* which will be redacted e.g. password or similar.
|
||||
* <p>
|
||||
* User querying system_views.settings will be shown redacted values on such fields.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.FIELD })
|
||||
public @interface Redacted
|
||||
{
|
||||
String REDACTED_STRING = "<REDACTED>";
|
||||
|
||||
/**
|
||||
* @return redacted value, defaults to {@code <REDACTED>}.
|
||||
*/
|
||||
String redactedValue() default REDACTED_STRING;
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import com.google.common.annotations.VisibleForTesting;
|
|||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import org.apache.cassandra.config.Config;
|
||||
import org.apache.cassandra.config.Redacted;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.config.Loader;
|
||||
import org.apache.cassandra.config.Properties;
|
||||
|
|
@ -89,6 +90,10 @@ final class SettingsTable extends AbstractVirtualTable
|
|||
@VisibleForTesting
|
||||
String getValue(Property prop)
|
||||
{
|
||||
Redacted maybeCredential = prop.getAnnotation(Redacted.class);
|
||||
if (maybeCredential != null)
|
||||
return maybeCredential.redactedValue();
|
||||
|
||||
Object value = prop.get(config);
|
||||
if (value == null)
|
||||
return null;
|
||||
|
|
@ -96,6 +101,23 @@ final class SettingsTable extends AbstractVirtualTable
|
|||
if (value.getClass().isArray())
|
||||
return Arrays.asList((Object[]) value).toString();
|
||||
|
||||
if (value instanceof Map)
|
||||
{
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>) value).entrySet())
|
||||
{
|
||||
// this is done on best-effort basis as we do not have names in parameters
|
||||
// inherently under control as this is what a user is responsible for
|
||||
// when dealing with custom implementations
|
||||
if (entry.getKey().endsWith("_password") || entry.getKey().equals("password"))
|
||||
map.put(entry.getKey(), Redacted.REDACTED_STRING);
|
||||
else
|
||||
map.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,11 @@
|
|||
|
||||
package org.apache.cassandra.db.virtual;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
|
@ -30,16 +33,20 @@ import org.junit.Test;
|
|||
import com.datastax.driver.core.ResultSet;
|
||||
import com.datastax.driver.core.Row;
|
||||
import org.apache.cassandra.config.Config;
|
||||
import org.apache.cassandra.config.Redacted;
|
||||
import org.apache.cassandra.config.DefaultLoader;
|
||||
import org.apache.cassandra.config.DurationSpec;
|
||||
import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.Builder;
|
||||
import org.apache.cassandra.config.EncryptionOptions.ServerEncryptionOptions.InternodeEncryption;
|
||||
import org.apache.cassandra.config.JMXServerOptions;
|
||||
import org.apache.cassandra.config.ParameterizedClass;
|
||||
import org.apache.cassandra.config.TransparentDataEncryptionOptions;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.security.SSLFactory;
|
||||
import org.yaml.snakeyaml.introspector.Property;
|
||||
|
||||
import static org.apache.cassandra.config.EncryptionOptions.ClientEncryptionOptions.ClientAuth.REQUIRED;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
public class SettingsTableTest extends CQLTester
|
||||
{
|
||||
|
|
@ -62,6 +69,16 @@ public class SettingsTableTest extends CQLTester
|
|||
config.commitlog_sync_group_window = new DurationSpec.IntMillisecondsBound(0);
|
||||
config.credentials_update_interval = null;
|
||||
config.data_file_directories = new String[] {"/my/data/directory", "/another/data/directory"};
|
||||
|
||||
Map<String, String> params = new LinkedHashMap<>();
|
||||
params.put("keystore_password", "password");
|
||||
params.put("key_password", "password");
|
||||
params.put("keystore", "conf/.keystore");
|
||||
config.transparent_data_encryption_options = new TransparentDataEncryptionOptions(false,
|
||||
"AES/CBC/PKCS5Padding",
|
||||
"alias",
|
||||
new ParameterizedClass("SomeClass",
|
||||
params));
|
||||
table = new SettingsTable(KS_NAME, config);
|
||||
VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table)));
|
||||
disablePreparedReuseForTest();
|
||||
|
|
@ -299,4 +316,40 @@ public class SettingsTableTest extends CQLTester
|
|||
config.transparent_data_encryption_options.iv_length = 7;
|
||||
check(pre + "iv_length", "7");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedaction()
|
||||
{
|
||||
assertValue("transparent_data_encryption_options.key_provider.parameters",
|
||||
String.format("{keystore_password=%s, keystore=conf/.keystore, key_password=%s}",
|
||||
Redacted.REDACTED_STRING,
|
||||
Redacted.REDACTED_STRING));
|
||||
|
||||
Set<Map.Entry<String, Property>> entries = new DefaultLoader().flatten(Config.class)
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(e -> e.getValue().getAnnotation(Redacted.class) != null)
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (e, r) -> e, TreeMap::new))
|
||||
.entrySet();
|
||||
|
||||
Assert.assertFalse(entries.isEmpty());
|
||||
|
||||
for (Map.Entry<String, Property> entry : entries)
|
||||
{
|
||||
logger.info("redacted {}", entry.getKey());
|
||||
assertValue(entry.getKey(), entry.getValue().getAnnotation(Redacted.class).redactedValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertValue(String settingName, String expectedValue)
|
||||
{
|
||||
List<Row> all = executeNet(String.format("SELECT * from vts.settings WHERE name = '%s'", settingName)).all();
|
||||
Assert.assertFalse(all.isEmpty());
|
||||
Row row = all.get(0);
|
||||
String name = row.getString("name");
|
||||
String value = row.getString("value");
|
||||
|
||||
Assert.assertEquals(settingName, name);
|
||||
Assert.assertEquals(expectedValue, value);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue