LCOV - code coverage report
Current view: top level - sshd - SuExec.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 44 63 69.8 %
Date: 2022-11-19 15:00:39 Functions: 6 6 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2010 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 java.nio.charset.StandardCharsets.UTF_8;
      18             : 
      19             : import com.google.common.base.Throwables;
      20             : import com.google.common.util.concurrent.Atomics;
      21             : import com.google.gerrit.entities.Account;
      22             : import com.google.gerrit.extensions.restapi.AuthException;
      23             : import com.google.gerrit.server.CurrentUser;
      24             : import com.google.gerrit.server.DynamicOptions;
      25             : import com.google.gerrit.server.IdentifiedUser;
      26             : import com.google.gerrit.server.PeerDaemonUser;
      27             : import com.google.gerrit.server.config.AuthConfig;
      28             : import com.google.gerrit.server.permissions.GlobalPermission;
      29             : import com.google.gerrit.server.permissions.PermissionBackend;
      30             : import com.google.gerrit.server.permissions.PermissionBackendException;
      31             : import com.google.gerrit.sshd.SshScope.Context;
      32             : import com.google.inject.Inject;
      33             : import java.io.IOException;
      34             : import java.net.SocketAddress;
      35             : import java.util.ArrayList;
      36             : import java.util.List;
      37             : import java.util.concurrent.atomic.AtomicReference;
      38             : import org.apache.sshd.server.Environment;
      39             : import org.apache.sshd.server.channel.ChannelSession;
      40             : import org.apache.sshd.server.command.Command;
      41             : import org.kohsuke.args4j.Argument;
      42             : import org.kohsuke.args4j.Option;
      43             : 
      44             : /**
      45             :  * Executes any other command as a different user identity.
      46             :  *
      47             :  * <p>The calling user must be authenticated as a {@link PeerDaemonUser}, which usually requires
      48             :  * public key authentication using this daemon's private host key, or a key on this daemon's peer
      49             :  * host key ring.
      50             :  */
      51             : public final class SuExec extends BaseCommand {
      52             :   private final SshScope sshScope;
      53             :   private final DispatchCommandProvider dispatcher;
      54             :   private final PermissionBackend permissionBackend;
      55             : 
      56             :   private boolean enableRunAs;
      57             :   private CurrentUser caller;
      58             :   private SshSession session;
      59             :   private IdentifiedUser.GenericFactory userFactory;
      60             :   private SshScope.Context callingContext;
      61             : 
      62             :   @Option(name = "--as", required = true)
      63             :   private Account.Id accountId;
      64             : 
      65             :   @Option(name = "--from")
      66             :   private SocketAddress peerAddress;
      67             : 
      68           1 :   @Argument(index = 0, multiValued = true, metaVar = "COMMAND")
      69             :   private List<String> args = new ArrayList<>();
      70             : 
      71             :   private final AtomicReference<Command> atomicCmd;
      72             : 
      73             :   @Inject
      74             :   SuExec(
      75             :       final SshScope sshScope,
      76             :       @CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher,
      77             :       PermissionBackend permissionBackend,
      78             :       final CurrentUser caller,
      79             :       final SshSession session,
      80             :       final IdentifiedUser.GenericFactory userFactory,
      81             :       final SshScope.Context callingContext,
      82           1 :       AuthConfig config) {
      83           1 :     this.sshScope = sshScope;
      84           1 :     this.dispatcher = dispatcher;
      85           1 :     this.permissionBackend = permissionBackend;
      86           1 :     this.caller = caller;
      87           1 :     this.session = session;
      88           1 :     this.userFactory = userFactory;
      89           1 :     this.callingContext = callingContext;
      90           1 :     this.enableRunAs = config.isRunAsEnabled();
      91           1 :     atomicCmd = Atomics.newReference();
      92           1 :   }
      93             : 
      94             :   @Override
      95             :   public void start(ChannelSession channel, Environment env) throws IOException {
      96           1 :     try (DynamicOptions pluginOptions = new DynamicOptions(injector, dynamicBeans)) {
      97           1 :       checkCanRunAs();
      98           1 :       parseCommandLine(pluginOptions);
      99             : 
     100           1 :       final Context ctx = callingContext.subContext(newSession(), join(args));
     101           1 :       final Context old = sshScope.set(ctx);
     102             :       try {
     103           1 :         final BaseCommand cmd = dispatcher.get();
     104           1 :         cmd.setArguments(args.toArray(new String[args.size()]));
     105           1 :         provideStateTo(cmd);
     106           1 :         atomicCmd.set(cmd);
     107           1 :         cmd.start(channel, env);
     108             :       } finally {
     109           1 :         sshScope.set(old);
     110             :       }
     111           0 :     } catch (UnloggedFailure e) {
     112           0 :       String msg = e.getMessage();
     113           0 :       if (!msg.endsWith("\n")) {
     114           0 :         msg += "\n";
     115             :       }
     116           0 :       err.write(msg.getBytes(UTF_8));
     117           0 :       err.flush();
     118           0 :       onExit(1);
     119           1 :     }
     120           1 :   }
     121             : 
     122             :   private void checkCanRunAs() throws UnloggedFailure {
     123           1 :     if (caller instanceof PeerDaemonUser) {
     124             :       // OK.
     125           0 :     } else if (!enableRunAs) {
     126           0 :       throw die("suexec disabled by auth.enableRunAs = false");
     127             :     } else {
     128             :       try {
     129           0 :         permissionBackend.user(caller).check(GlobalPermission.RUN_AS);
     130           0 :       } catch (AuthException e) {
     131           0 :         throw die("suexec not permitted", e);
     132           0 :       } catch (PermissionBackendException e) {
     133           0 :         throw die("suexec not available", e);
     134           0 :       }
     135             :     }
     136           1 :   }
     137             : 
     138             :   private SshSession newSession() {
     139             :     final SocketAddress peer;
     140           1 :     if (peerAddress == null) {
     141           1 :       peer = session.getRemoteAddress();
     142             :     } else {
     143           0 :       peer = peerAddress;
     144             :     }
     145           1 :     if (caller instanceof PeerDaemonUser) {
     146           1 :       caller = null;
     147             :     }
     148           1 :     return new SshSession(session, peer, userFactory.runAs(peer, accountId, caller));
     149             :   }
     150             : 
     151             :   private static String join(List<String> args) {
     152           1 :     StringBuilder r = new StringBuilder();
     153           1 :     for (String a : args) {
     154           1 :       if (r.length() > 0) {
     155           1 :         r.append(" ");
     156             :       }
     157           1 :       r.append(a);
     158           1 :     }
     159           1 :     return r.toString();
     160             :   }
     161             : 
     162             :   @Override
     163             :   public void destroy(ChannelSession channel) {
     164           1 :     Command cmd = atomicCmd.getAndSet(null);
     165           1 :     if (cmd != null) {
     166             :       try {
     167           1 :         cmd.destroy(channel);
     168           0 :       } catch (Exception e) {
     169           0 :         Throwables.throwIfUnchecked(e);
     170           0 :         throw new RuntimeException(e);
     171           1 :       }
     172             :     }
     173           1 :   }
     174             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750