| /* |
| * 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.accumulo.core.conf; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Base64; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Properties; |
| |
| import org.apache.accumulo.core.Constants; |
| import org.apache.accumulo.core.client.admin.TableOperations.ImportMappingOptions; |
| import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; |
| import org.apache.accumulo.core.client.security.tokens.CredentialProviderToken; |
| import org.apache.accumulo.core.client.security.tokens.DelegationToken; |
| import org.apache.accumulo.core.client.security.tokens.KerberosToken; |
| import org.apache.accumulo.core.client.security.tokens.PasswordToken; |
| |
| import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; |
| |
| public enum ClientProperty { |
| |
| // Instance |
| INSTANCE_NAME("instance.name", "", PropertyType.STRING, |
| "Name of Accumulo instance to " + "connect to", "2.0.0", true), |
| INSTANCE_ZOOKEEPERS("instance.zookeepers", "localhost:2181", PropertyType.HOSTLIST, |
| "Zookeeper connection information for Accumulo instance", "2.0.0", true), |
| INSTANCE_ZOOKEEPERS_TIMEOUT("instance.zookeepers.timeout", "30s", PropertyType.TIMEDURATION, |
| "Zookeeper session timeout", "2.0.0", false), |
| |
| // Authentication |
| AUTH_TYPE("auth.type", "password", PropertyType.STRING, |
| "Authentication method (i.e password, kerberos, PasswordToken, KerberosToken, etc)", "2.0.0", |
| true), |
| AUTH_PRINCIPAL("auth.principal", "", PropertyType.STRING, |
| "Accumulo principal/username for chosen authentication method", "2.0.0", true), |
| AUTH_TOKEN("auth.token", "", PropertyType.STRING, |
| "Authentication token (ex. mypassword, /path/to/keytab)", "2.0.0", true), |
| |
| // BatchWriter |
| BATCH_WRITER_MEMORY_MAX("batch.writer.memory.max", "50M", PropertyType.BYTES, |
| "Max memory (in bytes) to batch before writing", "2.0.0", false), |
| BATCH_WRITER_LATENCY_MAX("batch.writer.latency.max", "120s", PropertyType.TIMEDURATION, |
| "Max amount of time (in seconds) to hold data in memory before flushing it", "2.0.0", false), |
| BATCH_WRITER_TIMEOUT_MAX("batch.writer.timeout.max", "0", PropertyType.TIMEDURATION, |
| "Max amount of time (in seconds) an unresponsive server will be re-tried. An" |
| + " exception is thrown when this timeout is exceeded. Set to zero for no timeout.", |
| "2.0.0", false), |
| BATCH_WRITER_THREADS_MAX("batch.writer.threads.max", "3", PropertyType.COUNT, |
| "Maximum number of threads to use for writing data to tablet servers.", "2.0.0", false), |
| BATCH_WRITER_DURABILITY("batch.writer.durability", "default", PropertyType.DURABILITY, |
| Property.TABLE_DURABILITY.getDescription() + " Setting this property will " |
| + "change the durability for the BatchWriter session. A value of \"default\" will" |
| + " use the table's durability setting. ", |
| "2.0.0", false), |
| |
| // Scanner |
| SCANNER_BATCH_SIZE("scanner.batch.size", "1000", PropertyType.COUNT, |
| "Number of key/value pairs that will be fetched at time from tablet server", "2.0.0", false), |
| |
| // BatchScanner |
| BATCH_SCANNER_NUM_QUERY_THREADS("batch.scanner.num.query.threads", "3", PropertyType.COUNT, |
| "Number of concurrent query threads to spawn for querying", "2.0.0", false), |
| |
| // Bulk load |
| BULK_LOAD_THREADS("bulk.threads", ImportMappingOptions.BULK_LOAD_THREADS_DEFAULT, |
| PropertyType.COUNT, |
| "The number of threads used to inspect bulk load files to determine where files go. " |
| + "If the value ends with C, then it will be multiplied by the number of cores on the " |
| + "system. This property is only used by the bulk import API introduced in 2.0.0.", |
| "2.0.0", false), |
| |
| // SSL |
| SSL_ENABLED("ssl.enabled", "false", "Enable SSL for client RPC"), |
| SSL_KEYSTORE_PASSWORD("ssl.keystore.password", "", "Password used to encrypt keystore"), |
| SSL_KEYSTORE_PATH("ssl.keystore.path", "", PropertyType.PATH, "Path to SSL keystore file", |
| "2.0.0", false), |
| SSL_KEYSTORE_TYPE("ssl.keystore.type", "jks", "Type of SSL keystore"), |
| SSL_TRUSTSTORE_PASSWORD("ssl.truststore.password", "", "Password used to encrypt truststore"), |
| SSL_TRUSTSTORE_PATH("ssl.truststore.path", "", PropertyType.PATH, "Path to SSL truststore file", |
| "2.0.0", false), |
| SSL_TRUSTSTORE_TYPE("ssl.truststore.type", "jks", "Type of SSL truststore"), |
| SSL_USE_JSSE("ssl.use.jsse", "false", "Use JSSE system properties to configure SSL"), |
| |
| // SASL |
| SASL_ENABLED("sasl.enabled", "false", "Enable SASL for client RPC"), |
| SASL_QOP("sasl.qop", "auth", |
| "SASL quality of protection. Valid values are 'auth', 'auth-int', and 'auth-conf'"), |
| SASL_KERBEROS_SERVER_PRIMARY("sasl.kerberos.server.primary", "accumulo", |
| "Kerberos principal/primary that Accumulo servers use to login"), |
| |
| // Trace |
| TRACE_SPAN_RECEIVERS("trace.span.receivers", "org.apache.accumulo.tracer.ZooTraceClient", |
| "A list of span receiver classes to send trace spans"), |
| TRACE_ZOOKEEPER_PATH("trace.zookeeper.path", Constants.ZTRACERS, PropertyType.PATH, |
| "The zookeeper node where tracers are registered", "2.0.0", false); |
| |
| public static final String TRACE_SPAN_RECEIVER_PREFIX = "trace.span.receiver"; |
| |
| private String key; |
| private String defaultValue; |
| private PropertyType type; |
| private String description; |
| private String since; |
| private boolean required; |
| |
| ClientProperty(String key, String defaultValue, PropertyType type, String description, |
| String since, boolean required) { |
| this.key = Objects.requireNonNull(key); |
| this.defaultValue = Objects.requireNonNull(defaultValue); |
| this.type = Objects.requireNonNull(type); |
| this.description = Objects.requireNonNull(description); |
| this.since = Objects.requireNonNull(since); |
| this.required = required; |
| } |
| |
| ClientProperty(String key, String defaultValue, String description) { |
| this(key, defaultValue, PropertyType.STRING, description, "", false); |
| } |
| |
| public String getKey() { |
| return key; |
| } |
| |
| public String getDefaultValue() { |
| return defaultValue; |
| } |
| |
| public PropertyType getType() { |
| return type; |
| } |
| |
| public String getDescription() { |
| return description; |
| } |
| |
| public String getSince() { |
| return since; |
| } |
| |
| public boolean isRequired() { |
| return required; |
| } |
| |
| public String getValue(Properties properties) { |
| Objects.requireNonNull(properties); |
| String value = properties.getProperty(getKey()); |
| if (value == null || value.isEmpty()) { |
| value = getDefaultValue(); |
| } |
| Objects.requireNonNull(value); |
| if (isRequired() && value.isEmpty()) { |
| throw new IllegalArgumentException(getKey() + " must be set!"); |
| } |
| if (!type.isValidFormat(value)) { |
| throw new IllegalArgumentException( |
| "Invalid format for type \"" + type + "\" for provided value: " + value); |
| } |
| return value; |
| } |
| |
| public boolean isEmpty(Properties properties) { |
| Objects.requireNonNull(properties); |
| String value = properties.getProperty(getKey()); |
| return (value == null || value.isEmpty()); |
| } |
| |
| public Long getBytes(Properties properties) { |
| String value = getValue(properties); |
| if (value.isEmpty()) { |
| return null; |
| } |
| checkState(getType() == PropertyType.BYTES, |
| "Invalid type getting bytes. Type must be " + PropertyType.BYTES + ", not " + getType()); |
| return ConfigurationTypeHelper.getMemoryAsBytes(value); |
| } |
| |
| public Long getTimeInMillis(Properties properties) { |
| String value = getValue(properties); |
| if (value.isEmpty()) { |
| return null; |
| } |
| checkState(getType() == PropertyType.TIMEDURATION, "Invalid type getting time. Type must be " |
| + PropertyType.TIMEDURATION + ", not " + getType()); |
| return ConfigurationTypeHelper.getTimeInMillis(value); |
| } |
| |
| public Integer getInteger(Properties properties) { |
| String value = getValue(properties); |
| if (value.isEmpty()) { |
| return null; |
| } |
| return Integer.parseInt(value); |
| } |
| |
| public boolean getBoolean(Properties properties) { |
| String value = getValue(properties); |
| if (value.isEmpty()) { |
| return false; |
| } |
| return Boolean.valueOf(value); |
| } |
| |
| public void setBytes(Properties properties, Long bytes) { |
| checkState(getType() == PropertyType.BYTES, "Invalid type setting " + "bytes. Type must be " |
| + PropertyType.BYTES + ", not " + getType()); |
| properties.setProperty(getKey(), bytes.toString()); |
| } |
| |
| public void setTimeInMillis(Properties properties, Long milliseconds) { |
| checkState(getType() == PropertyType.TIMEDURATION, "Invalid type setting " |
| + "time. Type must be " + PropertyType.TIMEDURATION + ", not " + getType()); |
| properties.setProperty(getKey(), milliseconds + "ms"); |
| } |
| |
| public static Properties getPrefix(Properties properties, String prefix) { |
| Properties props = new Properties(); |
| for (Object keyObj : properties.keySet()) { |
| String key = (String) keyObj; |
| if (key.startsWith(prefix)) { |
| props.put(key, properties.getProperty(key)); |
| } |
| } |
| return props; |
| } |
| |
| public static Map<String,String> toMap(Properties properties) { |
| Map<String,String> propMap = new HashMap<>(); |
| for (Object obj : properties.keySet()) { |
| propMap.put((String) obj, properties.getProperty((String) obj)); |
| } |
| return propMap; |
| } |
| |
| public static String encodeToken(AuthenticationToken token) { |
| return Base64.getEncoder() |
| .encodeToString(AuthenticationToken.AuthenticationTokenSerializer.serialize(token)); |
| } |
| |
| public static AuthenticationToken decodeToken(String className, String tokenString) { |
| return AuthenticationToken.AuthenticationTokenSerializer.deserialize(className, |
| Base64.getDecoder().decode(tokenString)); |
| } |
| |
| public static void setPassword(Properties properties, CharSequence password) { |
| properties.setProperty(ClientProperty.AUTH_TYPE.getKey(), "password"); |
| properties.setProperty(ClientProperty.AUTH_TOKEN.getKey(), password.toString()); |
| } |
| |
| public static void setKerberosKeytab(Properties properties, String keytabPath) { |
| properties.setProperty(ClientProperty.AUTH_TYPE.getKey(), "kerberos"); |
| properties.setProperty(ClientProperty.AUTH_TOKEN.getKey(), keytabPath); |
| } |
| |
| @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", |
| justification = "code runs in same security context as user who providing the token file") |
| public static AuthenticationToken getAuthenticationToken(Properties properties) { |
| String authType = ClientProperty.AUTH_TYPE.getValue(properties); |
| String token = ClientProperty.AUTH_TOKEN.getValue(properties); |
| switch (authType) { |
| case "password": |
| return new PasswordToken(token); |
| case "PasswordToken": |
| return decodeToken(PasswordToken.class.getName(), token); |
| case "kerberos": |
| try { |
| String principal = ClientProperty.AUTH_PRINCIPAL.getValue(properties); |
| return new KerberosToken(principal, new File(token)); |
| } catch (IOException e) { |
| throw new IllegalArgumentException(e); |
| } |
| case "KerberosToken": |
| return decodeToken(KerberosToken.class.getName(), token); |
| case "CredentialProviderToken": |
| return decodeToken(CredentialProviderToken.class.getName(), token); |
| case "DelegationToken": |
| return decodeToken(DelegationToken.class.getName(), token); |
| default: |
| return decodeToken(authType, token); |
| } |
| } |
| |
| public static void setAuthenticationToken(Properties properties, AuthenticationToken token) { |
| properties.setProperty(ClientProperty.AUTH_TYPE.getKey(), token.getClass().getName()); |
| properties.setProperty(ClientProperty.AUTH_TOKEN.getKey(), encodeToken(token)); |
| } |
| |
| public static void validateProperty(Properties properties, ClientProperty prop) { |
| if (!properties.containsKey(prop.getKey()) || prop.getValue(properties).isEmpty()) { |
| throw new IllegalArgumentException(prop.getKey() + " is not set"); |
| } |
| } |
| |
| public static void validate(Properties properties, boolean validateToken) { |
| validateProperty(properties, ClientProperty.INSTANCE_NAME); |
| validateProperty(properties, ClientProperty.INSTANCE_ZOOKEEPERS); |
| validateProperty(properties, ClientProperty.AUTH_TYPE); |
| validateProperty(properties, ClientProperty.AUTH_PRINCIPAL); |
| if (validateToken) { |
| validateProperty(properties, ClientProperty.AUTH_TOKEN); |
| } |
| } |
| |
| /** |
| * @throws IllegalArgumentException |
| * if Properties does not contain all required |
| */ |
| public static void validate(Properties properties) { |
| validate(properties, true); |
| } |
| } |