Line data Source code
1 : // Copyright (C) 2009 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.sshd; 16 : 17 : import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME; 18 : 19 : import com.google.common.cache.CacheLoader; 20 : import com.google.common.cache.LoadingCache; 21 : import com.google.common.flogger.FluentLogger; 22 : import com.google.gerrit.server.account.AccountSshKey; 23 : import com.google.gerrit.server.account.VersionedAuthorizedKeys; 24 : import com.google.gerrit.server.account.externalids.ExternalId; 25 : import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory; 26 : import com.google.gerrit.server.account.externalids.ExternalIds; 27 : import com.google.gerrit.server.cache.CacheModule; 28 : import com.google.gerrit.server.logging.Metadata; 29 : import com.google.gerrit.server.logging.TraceContext; 30 : import com.google.gerrit.server.logging.TraceContext.TraceTimer; 31 : import com.google.gerrit.server.ssh.SshKeyCache; 32 : import com.google.gerrit.server.ssh.SshKeyCreator; 33 : import com.google.inject.Inject; 34 : import com.google.inject.Module; 35 : import com.google.inject.Singleton; 36 : import com.google.inject.TypeLiteral; 37 : import com.google.inject.name.Named; 38 : import java.io.IOException; 39 : import java.util.ArrayList; 40 : import java.util.Arrays; 41 : import java.util.Collections; 42 : import java.util.List; 43 : import java.util.Optional; 44 : import java.util.concurrent.ExecutionException; 45 : import org.eclipse.jgit.errors.ConfigInvalidException; 46 : 47 : /** Provides the {@link SshKeyCacheEntry}. */ 48 : @Singleton 49 : public class SshKeyCacheImpl implements SshKeyCache { 50 17 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 51 : 52 : private static final String CACHE_NAME = "sshkeys"; 53 : 54 17 : static final Iterable<SshKeyCacheEntry> NO_SUCH_USER = none(); 55 17 : static final Iterable<SshKeyCacheEntry> NO_KEYS = none(); 56 : 57 : public static Module module() { 58 17 : return new CacheModule() { 59 : @Override 60 : protected void configure() { 61 17 : cache(CACHE_NAME, String.class, new TypeLiteral<Iterable<SshKeyCacheEntry>>() {}) 62 17 : .loader(Loader.class); 63 17 : bind(SshKeyCacheImpl.class); 64 17 : bind(SshKeyCache.class).to(SshKeyCacheImpl.class); 65 17 : bind(SshKeyCreator.class).to(SshKeyCreatorImpl.class); 66 17 : } 67 : }; 68 : } 69 : 70 : private static Iterable<SshKeyCacheEntry> none() { 71 17 : return Collections.unmodifiableCollection(Arrays.asList(new SshKeyCacheEntry[0])); 72 : } 73 : 74 : private final LoadingCache<String, Iterable<SshKeyCacheEntry>> cache; 75 : 76 : @Inject 77 17 : SshKeyCacheImpl(@Named(CACHE_NAME) LoadingCache<String, Iterable<SshKeyCacheEntry>> cache) { 78 17 : this.cache = cache; 79 17 : } 80 : 81 : Iterable<SshKeyCacheEntry> get(String username) { 82 : try { 83 16 : return cache.get(username); 84 0 : } catch (ExecutionException e) { 85 0 : logger.atWarning().withCause(e).log("Cannot load SSH keys for %s", username); 86 0 : return Collections.emptyList(); 87 : } 88 : } 89 : 90 : @Override 91 : public void evict(String username) { 92 16 : if (username != null) { 93 16 : logger.atFine().log("Evict SSH key for username %s", username); 94 16 : cache.invalidate(username); 95 : } 96 16 : } 97 : 98 : static class Loader extends CacheLoader<String, Iterable<SshKeyCacheEntry>> { 99 : private final ExternalIds externalIds; 100 : private final VersionedAuthorizedKeys.Accessor authorizedKeys; 101 : private final ExternalIdKeyFactory externalIdKeyFactory; 102 : 103 : @Inject 104 : Loader( 105 : ExternalIds externalIds, 106 : VersionedAuthorizedKeys.Accessor authorizedKeys, 107 17 : ExternalIdKeyFactory externalIdKeyFactory) { 108 17 : this.externalIds = externalIds; 109 17 : this.authorizedKeys = authorizedKeys; 110 17 : this.externalIdKeyFactory = externalIdKeyFactory; 111 17 : } 112 : 113 : @Override 114 : public Iterable<SshKeyCacheEntry> load(String username) throws Exception { 115 16 : try (TraceTimer timer = 116 16 : TraceContext.newTimer( 117 : "Loading SSH keys for account with username", 118 16 : Metadata.builder().username(username).build())) { 119 16 : Optional<ExternalId> user = 120 16 : externalIds.get(externalIdKeyFactory.create(SCHEME_USERNAME, username)); 121 16 : if (!user.isPresent()) { 122 0 : return NO_SUCH_USER; 123 : } 124 : 125 16 : List<SshKeyCacheEntry> kl = new ArrayList<>(4); 126 16 : for (AccountSshKey k : authorizedKeys.getKeys(user.get().accountId())) { 127 16 : if (k.valid()) { 128 16 : add(kl, k); 129 : } 130 16 : } 131 : 132 16 : if (kl.isEmpty()) { 133 0 : return NO_KEYS; 134 : } 135 16 : return Collections.unmodifiableList(kl); 136 0 : } 137 : } 138 : 139 : private void add(List<SshKeyCacheEntry> kl, AccountSshKey k) { 140 : try { 141 16 : kl.add(new SshKeyCacheEntry(k.accountId(), SshUtil.parse(k))); 142 0 : } catch (OutOfMemoryError e) { 143 : // This is the only case where we assume the problem has nothing 144 : // to do with the key object, and instead we must abort this load. 145 : // 146 0 : throw e; 147 0 : } catch (Exception e) { 148 0 : markInvalid(k); 149 16 : } 150 16 : } 151 : 152 : private void markInvalid(AccountSshKey k) { 153 : try { 154 0 : logger.atInfo().log("Flagging SSH key %d of account %s invalid", k.seq(), k.accountId()); 155 0 : authorizedKeys.markKeyInvalid(k.accountId(), k.seq()); 156 0 : } catch (IOException | ConfigInvalidException e) { 157 0 : logger.atSevere().withCause(e).log( 158 0 : "Failed to mark SSH key %d of account %s invalid", k.seq(), k.accountId()); 159 0 : } 160 0 : } 161 : } 162 : }