// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.zookeeper.client; import com.yahoo.security.tls.MixedMode; import com.yahoo.security.tls.TlsContext; import com.yahoo.security.tls.TransportSecurityUtils; import org.apache.zookeeper.client.ZKClientConfig; import org.apache.zookeeper.server.quorum.QuorumPeerConfig; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; /** * Builder for ZK client configuration * * @author bjorncs */ public class ZkClientConfigBuilder { public static final String CLIENT_SECURE_PROPERTY = "zookeeper.client.secure"; public static final String SSL_CONTEXT_SUPPLIER_CLASS_PROPERTY = "zookeeper.ssl.context.supplier.class"; public static final String SSL_ENABLED_PROTOCOLS_PROPERTY = "zookeeper.ssl.enabledProtocols"; public static final String SSL_ENABLED_CIPHERSUITES_PROPERTY = "zookeeper.ssl.ciphersuites"; public static final String SSL_CLIENTAUTH_PROPERTY = "zookeeper.ssl.clientAuth"; public static final String CLIENT_CONNECTION_SOCKET = "zookeeper.clientCnxnSocket"; private static final TlsContext defaultTlsContext = getTlsContext().orElse(null); private final TlsContext tlsContext; public ZkClientConfigBuilder() { this(defaultTlsContext); } ZkClientConfigBuilder(TlsContext tlsContext) { this.tlsContext = tlsContext; } public ZKClientConfig toConfig(Path configFile) throws IOException, QuorumPeerConfig.ConfigException { String configString = toConfigString(); Files.createDirectories(configFile.getParent()); Path tempFile = Files.createTempFile(configFile.toAbsolutePath().getParent(), "." + configFile.getFileName(), ".tmp"); Files.writeString(tempFile, configString); Files.move(tempFile, configFile, StandardCopyOption.ATOMIC_MOVE); return new ZKClientConfig(configFile.toString()); } public ZKClientConfig toConfig() { ZKClientConfig config = new ZKClientConfig(); Map configProperties = toConfigProperties(); configProperties.forEach(config::setProperty); return config; } public String toConfigString() { StringBuilder builder = new StringBuilder(); Map properties = toConfigProperties(); properties.forEach((key, value) -> builder.append(key).append('=').append(value).append('\n')); return builder.toString(); } public Map toConfigProperties() { Map builder = new HashMap<>(); builder.put(CLIENT_SECURE_PROPERTY, Boolean.toString(tlsContext != null)); builder.put(CLIENT_CONNECTION_SOCKET, "org.apache.zookeeper.ClientCnxnSocketNetty"); if (tlsContext != null) { builder.put(SSL_CONTEXT_SUPPLIER_CLASS_PROPERTY, VespaSslContextProvider.class.getName()); String protocolsConfigValue = Arrays.stream(tlsContext.parameters().getProtocols()).sorted().collect(Collectors.joining(",")); builder.put(SSL_ENABLED_PROTOCOLS_PROPERTY, protocolsConfigValue); String ciphersConfigValue = Arrays.stream(tlsContext.parameters().getCipherSuites()).sorted().collect(Collectors.joining(",")); builder.put(SSL_ENABLED_CIPHERSUITES_PROPERTY, ciphersConfigValue); builder.put(SSL_CLIENTAUTH_PROPERTY, "NEED"); } return Map.copyOf(builder); } private static Optional getTlsContext() { if (TransportSecurityUtils.getInsecureMixedMode() == MixedMode.PLAINTEXT_CLIENT_MIXED_SERVER) return Optional.empty(); return TransportSecurityUtils.getSystemTlsContext(); } }