Line data Source code
1 : // Copyright (C) 2015 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.gpg; 16 : 17 : import com.google.gerrit.server.IdentifiedUser; 18 : import com.google.gerrit.server.util.MagicBranch; 19 : import com.google.inject.Inject; 20 : import com.google.inject.Provider; 21 : import com.google.inject.Singleton; 22 : import java.util.Collection; 23 : import org.eclipse.jgit.transport.PreReceiveHook; 24 : import org.eclipse.jgit.transport.PushCertificate; 25 : import org.eclipse.jgit.transport.ReceiveCommand; 26 : import org.eclipse.jgit.transport.ReceivePack; 27 : 28 : /** 29 : * Pre-receive hook to check signed pushes. 30 : * 31 : * <p>If configured, prior to processing any push using {@code ReceiveCommits}, requires that any 32 : * push certificate present must be valid. 33 : */ 34 : @Singleton 35 : public class SignedPushPreReceiveHook implements PreReceiveHook { 36 : public static class Required implements PreReceiveHook { 37 3 : public static final Required INSTANCE = new Required(); 38 : 39 : @Override 40 : public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) { 41 3 : if (rp.getPushCertificate() == null) { 42 3 : rp.sendMessage("ERROR: Signed push is required"); 43 3 : reject(commands, "push cert error"); 44 : } 45 3 : } 46 : 47 : private Required() {} 48 : } 49 : 50 : private final Provider<IdentifiedUser> user; 51 : private final GerritPushCertificateCheckerFactory checkerFactory; 52 : 53 : @Inject 54 : public SignedPushPreReceiveHook( 55 7 : Provider<IdentifiedUser> user, GerritPushCertificateCheckerFactory checkerFactory) { 56 7 : this.user = user; 57 7 : this.checkerFactory = checkerFactory; 58 7 : } 59 : 60 : @Override 61 : public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) { 62 3 : PushCertificate cert = rp.getPushCertificate(); 63 3 : if (cert == null) { 64 3 : return; 65 : } 66 0 : CheckResult result = 67 0 : checkerFactory.create(user.get()).setCheckNonce(true).check(cert).getCheckResult(); 68 0 : if (!isAllowed(result, commands)) { 69 0 : for (String problem : result.getProblems()) { 70 0 : rp.sendMessage(problem); 71 0 : } 72 0 : reject(commands, "invalid push cert"); 73 : } 74 0 : } 75 : 76 : private static boolean isAllowed(CheckResult result, Collection<ReceiveCommand> commands) { 77 0 : if (onlyMagicBranches(commands)) { 78 : // Only pushing magic branches: allow a valid push certificate even if the 79 : // key is not ultimately trusted. Assume anyone with Submit permission to 80 : // the branch is able to verify during review that the code is legitimate. 81 0 : return result.isOk(); 82 : } 83 : // Directly updating one or more refs: require a trusted key. 84 0 : return result.isTrusted(); 85 : } 86 : 87 : private static boolean onlyMagicBranches(Iterable<ReceiveCommand> commands) { 88 0 : for (ReceiveCommand c : commands) { 89 0 : if (!MagicBranch.isMagicBranch(c.getRefName())) { 90 0 : return false; 91 : } 92 0 : } 93 0 : return true; 94 : } 95 : 96 : private static void reject(Collection<ReceiveCommand> commands, String reason) { 97 3 : for (ReceiveCommand cmd : commands) { 98 3 : if (cmd.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) { 99 3 : cmd.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, reason); 100 : } 101 3 : } 102 3 : } 103 : }