001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.crypto.key.kms; 020 021import java.io.IOException; 022import java.security.GeneralSecurityException; 023import java.security.NoSuchAlgorithmException; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.List; 027import java.util.concurrent.atomic.AtomicInteger; 028 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.crypto.key.KeyProvider; 031import org.apache.hadoop.crypto.key.KeyProviderCryptoExtension.CryptoExtension; 032import org.apache.hadoop.crypto.key.KeyProviderCryptoExtension.EncryptedKeyVersion; 033import org.apache.hadoop.crypto.key.KeyProviderDelegationTokenExtension; 034import org.apache.hadoop.security.Credentials; 035import org.apache.hadoop.security.token.Token; 036import org.apache.hadoop.util.Time; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040import com.google.common.annotations.VisibleForTesting; 041 042/** 043 * A simple LoadBalancing KMSClientProvider that round-robins requests 044 * across a provided array of KMSClientProviders. It also retries failed 045 * requests on the next available provider in the load balancer group. It 046 * only retries failed requests that result in an IOException, sending back 047 * all other Exceptions to the caller without retry. 048 */ 049public class LoadBalancingKMSClientProvider extends KeyProvider implements 050 CryptoExtension, 051 KeyProviderDelegationTokenExtension.DelegationTokenExtension { 052 053 public static Logger LOG = 054 LoggerFactory.getLogger(LoadBalancingKMSClientProvider.class); 055 056 static interface ProviderCallable<T> { 057 public T call(KMSClientProvider provider) throws IOException, Exception; 058 } 059 060 @SuppressWarnings("serial") 061 static class WrapperException extends RuntimeException { 062 public WrapperException(Throwable cause) { 063 super(cause); 064 } 065 } 066 067 private final KMSClientProvider[] providers; 068 private final AtomicInteger currentIdx; 069 070 public LoadBalancingKMSClientProvider(KMSClientProvider[] providers, 071 Configuration conf) { 072 this(shuffle(providers), Time.monotonicNow(), conf); 073 } 074 075 @VisibleForTesting 076 LoadBalancingKMSClientProvider(KMSClientProvider[] providers, long seed, 077 Configuration conf) { 078 super(conf); 079 this.providers = providers; 080 this.currentIdx = new AtomicInteger((int)(seed % providers.length)); 081 } 082 083 @VisibleForTesting 084 KMSClientProvider[] getProviders() { 085 return providers; 086 } 087 088 private <T> T doOp(ProviderCallable<T> op, int currPos) 089 throws IOException { 090 IOException ex = null; 091 for (int i = 0; i < providers.length; i++) { 092 KMSClientProvider provider = providers[(currPos + i) % providers.length]; 093 try { 094 return op.call(provider); 095 } catch (IOException ioe) { 096 LOG.warn("KMS provider at [{}] threw an IOException [{}]!!", 097 provider.getKMSUrl(), ioe.getMessage()); 098 ex = ioe; 099 } catch (Exception e) { 100 if (e instanceof RuntimeException) { 101 throw (RuntimeException)e; 102 } else { 103 throw new WrapperException(e); 104 } 105 } 106 } 107 if (ex != null) { 108 LOG.warn("Aborting since the Request has failed with all KMS" 109 + " providers in the group. !!"); 110 throw ex; 111 } 112 throw new IOException("No providers configured !!"); 113 } 114 115 private int nextIdx() { 116 while (true) { 117 int current = currentIdx.get(); 118 int next = (current + 1) % providers.length; 119 if (currentIdx.compareAndSet(current, next)) { 120 return current; 121 } 122 } 123 } 124 125 @Override 126 public Token<?>[] 127 addDelegationTokens(final String renewer, final Credentials credentials) 128 throws IOException { 129 return doOp(new ProviderCallable<Token<?>[]>() { 130 @Override 131 public Token<?>[] call(KMSClientProvider provider) throws IOException { 132 return provider.addDelegationTokens(renewer, credentials); 133 } 134 }, nextIdx()); 135 } 136 137 // This request is sent to all providers in the load-balancing group 138 @Override 139 public void warmUpEncryptedKeys(String... keyNames) throws IOException { 140 for (KMSClientProvider provider : providers) { 141 try { 142 provider.warmUpEncryptedKeys(keyNames); 143 } catch (IOException ioe) { 144 LOG.error( 145 "Error warming up keys for provider with url" 146 + "[" + provider.getKMSUrl() + "]"); 147 } 148 } 149 } 150 151 // This request is sent to all providers in the load-balancing group 152 @Override 153 public void drain(String keyName) { 154 for (KMSClientProvider provider : providers) { 155 provider.drain(keyName); 156 } 157 } 158 159 @Override 160 public EncryptedKeyVersion 161 generateEncryptedKey(final String encryptionKeyName) 162 throws IOException, GeneralSecurityException { 163 try { 164 return doOp(new ProviderCallable<EncryptedKeyVersion>() { 165 @Override 166 public EncryptedKeyVersion call(KMSClientProvider provider) 167 throws IOException, GeneralSecurityException { 168 return provider.generateEncryptedKey(encryptionKeyName); 169 } 170 }, nextIdx()); 171 } catch (WrapperException we) { 172 throw (GeneralSecurityException) we.getCause(); 173 } 174 } 175 176 @Override 177 public KeyVersion 178 decryptEncryptedKey(final EncryptedKeyVersion encryptedKeyVersion) 179 throws IOException, GeneralSecurityException { 180 try { 181 return doOp(new ProviderCallable<KeyVersion>() { 182 @Override 183 public KeyVersion call(KMSClientProvider provider) 184 throws IOException, GeneralSecurityException { 185 return provider.decryptEncryptedKey(encryptedKeyVersion); 186 } 187 }, nextIdx()); 188 } catch (WrapperException we) { 189 throw (GeneralSecurityException)we.getCause(); 190 } 191 } 192 193 @Override 194 public KeyVersion getKeyVersion(final String versionName) throws IOException { 195 return doOp(new ProviderCallable<KeyVersion>() { 196 @Override 197 public KeyVersion call(KMSClientProvider provider) throws IOException { 198 return provider.getKeyVersion(versionName); 199 } 200 }, nextIdx()); 201 } 202 203 @Override 204 public List<String> getKeys() throws IOException { 205 return doOp(new ProviderCallable<List<String>>() { 206 @Override 207 public List<String> call(KMSClientProvider provider) throws IOException { 208 return provider.getKeys(); 209 } 210 }, nextIdx()); 211 } 212 213 @Override 214 public Metadata[] getKeysMetadata(final String... names) throws IOException { 215 return doOp(new ProviderCallable<Metadata[]>() { 216 @Override 217 public Metadata[] call(KMSClientProvider provider) throws IOException { 218 return provider.getKeysMetadata(names); 219 } 220 }, nextIdx()); 221 } 222 223 @Override 224 public List<KeyVersion> getKeyVersions(final String name) throws IOException { 225 return doOp(new ProviderCallable<List<KeyVersion>>() { 226 @Override 227 public List<KeyVersion> call(KMSClientProvider provider) 228 throws IOException { 229 return provider.getKeyVersions(name); 230 } 231 }, nextIdx()); 232 } 233 234 @Override 235 public KeyVersion getCurrentKey(final String name) throws IOException { 236 return doOp(new ProviderCallable<KeyVersion>() { 237 @Override 238 public KeyVersion call(KMSClientProvider provider) throws IOException { 239 return provider.getCurrentKey(name); 240 } 241 }, nextIdx()); 242 } 243 @Override 244 public Metadata getMetadata(final String name) throws IOException { 245 return doOp(new ProviderCallable<Metadata>() { 246 @Override 247 public Metadata call(KMSClientProvider provider) throws IOException { 248 return provider.getMetadata(name); 249 } 250 }, nextIdx()); 251 } 252 253 @Override 254 public KeyVersion createKey(final String name, final byte[] material, 255 final Options options) throws IOException { 256 return doOp(new ProviderCallable<KeyVersion>() { 257 @Override 258 public KeyVersion call(KMSClientProvider provider) throws IOException { 259 return provider.createKey(name, material, options); 260 } 261 }, nextIdx()); 262 } 263 264 @Override 265 public KeyVersion createKey(final String name, final Options options) 266 throws NoSuchAlgorithmException, IOException { 267 try { 268 return doOp(new ProviderCallable<KeyVersion>() { 269 @Override 270 public KeyVersion call(KMSClientProvider provider) throws IOException, 271 NoSuchAlgorithmException { 272 return provider.createKey(name, options); 273 } 274 }, nextIdx()); 275 } catch (WrapperException e) { 276 throw (NoSuchAlgorithmException)e.getCause(); 277 } 278 } 279 @Override 280 public void deleteKey(final String name) throws IOException { 281 doOp(new ProviderCallable<Void>() { 282 @Override 283 public Void call(KMSClientProvider provider) throws IOException { 284 provider.deleteKey(name); 285 return null; 286 } 287 }, nextIdx()); 288 } 289 @Override 290 public KeyVersion rollNewVersion(final String name, final byte[] material) 291 throws IOException { 292 return doOp(new ProviderCallable<KeyVersion>() { 293 @Override 294 public KeyVersion call(KMSClientProvider provider) throws IOException { 295 return provider.rollNewVersion(name, material); 296 } 297 }, nextIdx()); 298 } 299 300 @Override 301 public KeyVersion rollNewVersion(final String name) 302 throws NoSuchAlgorithmException, IOException { 303 try { 304 return doOp(new ProviderCallable<KeyVersion>() { 305 @Override 306 public KeyVersion call(KMSClientProvider provider) throws IOException, 307 NoSuchAlgorithmException { 308 return provider.rollNewVersion(name); 309 } 310 }, nextIdx()); 311 } catch (WrapperException e) { 312 throw (NoSuchAlgorithmException)e.getCause(); 313 } 314 } 315 316 // Close all providers in the LB group 317 @Override 318 public void close() throws IOException { 319 for (KMSClientProvider provider : providers) { 320 try { 321 provider.close(); 322 } catch (IOException ioe) { 323 LOG.error("Error closing provider with url" 324 + "[" + provider.getKMSUrl() + "]"); 325 } 326 } 327 } 328 329 330 @Override 331 public void flush() throws IOException { 332 for (KMSClientProvider provider : providers) { 333 try { 334 provider.flush(); 335 } catch (IOException ioe) { 336 LOG.error("Error flushing provider with url" 337 + "[" + provider.getKMSUrl() + "]"); 338 } 339 } 340 } 341 342 private static KMSClientProvider[] shuffle(KMSClientProvider[] providers) { 343 List<KMSClientProvider> list = Arrays.asList(providers); 344 Collections.shuffle(list); 345 return list.toArray(providers); 346 } 347}