001/* 002 * (C) Copyright 2013-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Florent Guillaume 018 */ 019package org.nuxeo.ecm.core.redis; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.nio.file.Files; 024import java.nio.file.Paths; 025import java.security.GeneralSecurityException; 026import java.security.KeyStore; 027 028import javax.net.ssl.SSLContext; 029import javax.net.ssl.SSLSocketFactory; 030 031import org.apache.commons.lang3.StringUtils; 032import org.apache.http.ssl.SSLContextBuilder; 033import org.apache.http.ssl.SSLContexts; 034import org.nuxeo.common.xmap.annotation.XNode; 035import org.nuxeo.common.xmap.annotation.XObject; 036 037import redis.clients.jedis.Jedis; 038import redis.clients.jedis.JedisPool; 039import redis.clients.jedis.JedisPoolConfig; 040import redis.clients.jedis.Protocol; 041 042/** 043 * Descriptor for a Redis configuration. 044 * 045 * @since 5.8 046 */ 047@XObject("server") 048public class RedisServerDescriptor extends RedisPoolDescriptor { 049 050 @XNode("host") 051 public String host; 052 053 @XNode("port") 054 public int port = Protocol.DEFAULT_PORT; 055 056 /** @since 10.3 */ 057 @XNode("ssl") 058 public boolean ssl; 059 060 /** @since 10.3 */ 061 @XNode("trustStorePath") 062 public String trustStorePath; 063 064 /** @since 10.3 */ 065 @XNode("trustStorePassword") 066 public String trustStorePassword; 067 068 /** @since 10.3 */ 069 @XNode("trustStoreType") 070 public String trustStoreType; 071 072 /** @since 10.3 */ 073 @XNode("keyStorePath") 074 public String keyStorePath; 075 076 /** @since 10.3 */ 077 @XNode("keyStorePassword") 078 public String keyStorePassword; 079 080 /** @since 10.3 */ 081 @XNode("keyStoreType") 082 public String keyStoreType; 083 084 @XNode("failoverTimeout") 085 public int failoverTimeout = 300; 086 087 @SuppressWarnings("resource") // JedisPool closed by RedisComponent.stop 088 @Override 089 public RedisExecutor newExecutor() { 090 SSLContext sslContext = getSSLContext(); 091 boolean useSSL; 092 SSLSocketFactory sslSocketFactory; 093 if (sslContext == null) { 094 useSSL = ssl; 095 sslSocketFactory = null; 096 } else { 097 useSSL = true; 098 sslSocketFactory = sslContext.getSocketFactory(); 099 } 100 try (Jedis jedis = new Jedis(host, port, useSSL, sslSocketFactory, null, null)) { 101 if (StringUtils.isNotBlank(password)) { 102 jedis.auth(password); 103 } 104 String pong = jedis.ping(); 105 if (!"PONG".equals(pong)) { 106 throw new RuntimeException("Cannot connect to Redis host: " + host + ":" + port); 107 } 108 } 109 110 JedisPoolConfig conf = new JedisPoolConfig(); 111 conf.setMaxTotal(maxTotal); 112 conf.setMaxIdle(maxIdle); 113 RedisExecutor base = new RedisPoolExecutor(new JedisPool(conf, host, port, timeout, 114 StringUtils.defaultIfBlank(password, null), database, useSSL, sslSocketFactory, null, null)); 115 return new RedisFailoverExecutor(failoverTimeout, base); 116 } 117 118 protected SSLContext getSSLContext() { 119 try { 120 KeyStore trustStore = loadKeyStore(trustStorePath, trustStorePassword, trustStoreType); 121 KeyStore keyStore = loadKeyStore(keyStorePath, keyStorePassword, keyStoreType); 122 if (trustStore == null && keyStore == null) { 123 return null; 124 } 125 SSLContextBuilder sslContextBuilder = SSLContexts.custom(); 126 if (trustStore != null) { 127 sslContextBuilder.loadTrustMaterial(trustStore, null); 128 } 129 if (keyStore != null) { 130 sslContextBuilder.loadKeyMaterial(keyStore, null); 131 } 132 return sslContextBuilder.build(); 133 } catch (GeneralSecurityException | IOException e) { 134 throw new RuntimeException("Cannot setup SSL context", e); 135 } 136 } 137 138 protected KeyStore loadKeyStore(String path, String password, String type) 139 throws GeneralSecurityException, IOException { 140 if (StringUtils.isBlank(path)) { 141 return null; 142 } 143 String keyStoreType = StringUtils.defaultIfBlank(type, KeyStore.getDefaultType()); 144 KeyStore keyStore = KeyStore.getInstance(keyStoreType); 145 char[] passwordChars = StringUtils.isBlank(password) ? null : password.toCharArray(); 146 try (InputStream is = Files.newInputStream(Paths.get(path))) { 147 keyStore.load(is, passwordChars); 148 } 149 return keyStore; 150 } 151 152}