🤬
  • Add RemoteVulnDetector for handling vulnerability detection running on the language servers.

    PiperOrigin-RevId: 461036689
    Change-Id: I678d8d496402debac5d4ba1dbcd7eb88bf5118f3
  • Loading...
  • John Y. Kim committed with Copybara-Service 2 years ago
    e442ccf3
    1 parent 524cba70
Revision indexing in progress... (symbol navigation in revisions will be accurate after indexed)
  • ■ ■ ■ ■
    plugin/src/main/java/com/google/tsunami/plugin/PluginType.java
    skipped 26 lines
    27 27   SERVICE_FINGERPRINT,
    28 28   
    29 29   /** A plugin that detects certain vulnerabilities on an exposed network service. */
    30  - VULN_DETECTION
     30 + VULN_DETECTION,
     31 + 
     32 + /** A plugin that contains vulnerability detectors from language servers. */
     33 + REMOTE_VULN_DETECTION
    31 34  }
    32 35   
  • ■ ■ ■ ■ ■ ■
    plugin/src/main/java/com/google/tsunami/plugin/RemoteVulnDetector.java
     1 +/*
     2 + * Copyright 2022 Google LLC
     3 + *
     4 + * Licensed under the Apache License, Version 2.0 (the "License");
     5 + * you may not use this file except in compliance with the License.
     6 + * You may obtain a copy of the License at
     7 + *
     8 + * http://www.apache.org/licenses/LICENSE-2.0
     9 + *
     10 + * Unless required by applicable law or agreed to in writing, software
     11 + * distributed under the License is distributed on an "AS IS" BASIS,
     12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 + * See the License for the specific language governing permissions and
     14 + * limitations under the License.
     15 + */
     16 +package com.google.tsunami.plugin;
     17 + 
     18 +import com.google.common.collect.ImmutableList;
     19 +import com.google.tsunami.proto.MatchedPlugin;
     20 +import com.google.tsunami.proto.PluginDefinition;
     21 + 
     22 +/**
     23 + * A special {@link VulnDetector} to execute vulnerability detector plugins from their specified
     24 + * language server.
     25 + */
     26 +public interface RemoteVulnDetector extends VulnDetector {
     27 + 
     28 + /**
     29 + * Retrieve all plugins from the language server through an RPC call.
     30 + *
     31 + * @return List of all plugin definitions. If the language server throws an error, an empty list
     32 + * will be returned.
     33 + */
     34 + ImmutableList<PluginDefinition> getAllPlugins();
     35 + 
     36 + /**
     37 + * Add a {@link MatchedPlugin} to allow this {@link RemoteVulnDetector} to run detection for this
     38 + * plugin through the language server.
     39 + *
     40 + * @param pluginToRun The plugin to allow this {@link RemoteVulnDetector} to run.
     41 + */
     42 + void addMatchedPluginToDetect(MatchedPlugin pluginToRun);
     43 +}
     44 + 
  • ■ ■ ■ ■ ■ ■
    plugin/src/main/java/com/google/tsunami/plugin/RemoteVulnDetectorImpl.java
     1 +/*
     2 + * Copyright 2022 Google LLC
     3 + *
     4 + * Licensed under the Apache License, Version 2.0 (the "License");
     5 + * you may not use this file except in compliance with the License.
     6 + * You may obtain a copy of the License at
     7 + *
     8 + * http://www.apache.org/licenses/LICENSE-2.0
     9 + *
     10 + * Unless required by applicable law or agreed to in writing, software
     11 + * distributed under the License is distributed on an "AS IS" BASIS,
     12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 + * See the License for the specific language governing permissions and
     14 + * limitations under the License.
     15 + */
     16 +package com.google.tsunami.plugin;
     17 + 
     18 +import static com.google.common.base.Preconditions.checkNotNull;
     19 +import static java.util.concurrent.TimeUnit.SECONDS;
     20 + 
     21 +import com.google.common.collect.ImmutableList;
     22 +import com.google.common.collect.Sets;
     23 +import com.google.common.flogger.GoogleLogger;
     24 +import com.google.tsunami.proto.DetectionReportList;
     25 +import com.google.tsunami.proto.ListPluginsRequest;
     26 +import com.google.tsunami.proto.MatchedPlugin;
     27 +import com.google.tsunami.proto.NetworkService;
     28 +import com.google.tsunami.proto.PluginDefinition;
     29 +import com.google.tsunami.proto.RunRequest;
     30 +import com.google.tsunami.proto.TargetInfo;
     31 +import io.grpc.Channel;
     32 +import io.grpc.Deadline;
     33 +import io.grpc.health.v1.HealthCheckRequest;
     34 +import io.grpc.health.v1.HealthCheckResponse;
     35 +import java.util.Set;
     36 +import java.util.concurrent.ExecutionException;
     37 + 
     38 +final class RemoteVulnDetectorImpl implements RemoteVulnDetector {
     39 + private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
     40 + // Default duration deadline for all RPC calls
     41 + private static final Deadline DEFAULT_DEADLINE = Deadline.after(30, SECONDS);
     42 + 
     43 + private final PluginServiceClient service;
     44 + private final Set<MatchedPlugin> pluginsToRun;
     45 + 
     46 + RemoteVulnDetectorImpl(Channel channel) {
     47 + this.service = new PluginServiceClient(checkNotNull(channel));
     48 + this.pluginsToRun = Sets.newHashSet();
     49 + }
     50 + 
     51 + @Override
     52 + public DetectionReportList detect(
     53 + TargetInfo target, ImmutableList<NetworkService> matchedServices) {
     54 + try {
     55 + if (service
     56 + .checkHealthWithDeadline(HealthCheckRequest.getDefaultInstance(), DEFAULT_DEADLINE)
     57 + .get()
     58 + .getStatus()
     59 + .equals(HealthCheckResponse.ServingStatus.SERVING)) {
     60 + return service
     61 + .runWithDeadline(
     62 + RunRequest.newBuilder().setTarget(target).addAllPlugins(pluginsToRun).build(),
     63 + DEFAULT_DEADLINE)
     64 + .get()
     65 + .getReports();
     66 + } else {
     67 + logger.atWarning().log(
     68 + "Server health status is not SERVING. Will not run matched plugins.");
     69 + }
     70 + } catch (InterruptedException | ExecutionException e) {
     71 + throw new LanguageServerException("Failed to get response from language server.", e);
     72 + }
     73 + return DetectionReportList.getDefaultInstance();
     74 + }
     75 + 
     76 + @Override
     77 + public ImmutableList<PluginDefinition> getAllPlugins() {
     78 + try {
     79 + if (service
     80 + .checkHealthWithDeadline(HealthCheckRequest.getDefaultInstance(), DEFAULT_DEADLINE)
     81 + .get()
     82 + .getStatus()
     83 + .equals(HealthCheckResponse.ServingStatus.SERVING)) {
     84 + return ImmutableList.copyOf(
     85 + service
     86 + .listPluginsWithDeadline(ListPluginsRequest.getDefaultInstance(), DEFAULT_DEADLINE)
     87 + .get()
     88 + .getPluginsList());
     89 + } else {
     90 + logger.atWarning().log("Server health status is not SERVING. Will not retrieve plugins.");
     91 + }
     92 + } catch (InterruptedException | ExecutionException e) {
     93 + throw new LanguageServerException("Failed to get plugins from language server.", e);
     94 + }
     95 + return ImmutableList.of();
     96 + }
     97 + 
     98 + @Override
     99 + public void addMatchedPluginToDetect(MatchedPlugin plugin) {
     100 + this.pluginsToRun.add(plugin);
     101 + }
     102 +}
     103 + 
  • ■ ■ ■ ■ ■ ■
    plugin/src/test/java/com/google/tsunami/plugin/RemoteVulnDetectorImplTest.java
     1 +/*
     2 + * Copyright 2022 Google LLC
     3 + *
     4 + * Licensed under the Apache License, Version 2.0 (the "License");
     5 + * you may not use this file except in compliance with the License.
     6 + * You may obtain a copy of the License at
     7 + *
     8 + * http://www.apache.org/licenses/LICENSE-2.0
     9 + *
     10 + * Unless required by applicable law or agreed to in writing, software
     11 + * distributed under the License is distributed on an "AS IS" BASIS,
     12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 + * See the License for the specific language governing permissions and
     14 + * limitations under the License.
     15 + */
     16 +package com.google.tsunami.plugin;
     17 + 
     18 +import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
     19 +import static org.junit.Assert.assertThrows;
     20 + 
     21 +import com.google.common.collect.ImmutableList;
     22 +import com.google.inject.AbstractModule;
     23 +import com.google.inject.Guice;
     24 +import com.google.tsunami.common.data.NetworkEndpointUtils;
     25 +import com.google.tsunami.proto.DetectionReport;
     26 +import com.google.tsunami.proto.DetectionReportList;
     27 +import com.google.tsunami.proto.ListPluginsRequest;
     28 +import com.google.tsunami.proto.ListPluginsResponse;
     29 +import com.google.tsunami.proto.MatchedPlugin;
     30 +import com.google.tsunami.proto.NetworkService;
     31 +import com.google.tsunami.proto.PluginDefinition;
     32 +import com.google.tsunami.proto.PluginInfo;
     33 +import com.google.tsunami.proto.PluginServiceGrpc.PluginServiceImplBase;
     34 +import com.google.tsunami.proto.RunRequest;
     35 +import com.google.tsunami.proto.RunResponse;
     36 +import com.google.tsunami.proto.TargetInfo;
     37 +import com.google.tsunami.proto.TransportProtocol;
     38 +import io.grpc.health.v1.HealthCheckRequest;
     39 +import io.grpc.health.v1.HealthCheckResponse;
     40 +import io.grpc.health.v1.HealthCheckResponse.ServingStatus;
     41 +import io.grpc.health.v1.HealthGrpc.HealthImplBase;
     42 +import io.grpc.inprocess.InProcessChannelBuilder;
     43 +import io.grpc.inprocess.InProcessServerBuilder;
     44 +import io.grpc.stub.StreamObserver;
     45 +import io.grpc.testing.GrpcCleanupRule;
     46 +import io.grpc.util.MutableHandlerRegistry;
     47 +import org.junit.Before;
     48 +import org.junit.Rule;
     49 +import org.junit.Test;
     50 +import org.junit.runner.RunWith;
     51 +import org.junit.runners.JUnit4;
     52 + 
     53 +@RunWith(JUnit4.class)
     54 +public final class RemoteVulnDetectorImplTest {
     55 + 
     56 + private static final String PLUGIN_VERSION = "0.0.1";
     57 + private static final String PLUGIN_DESCRIPTION = "test description";
     58 + private static final String PLUGIN_AUTHOR = "tester";
     59 + 
     60 + private final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry();
     61 + 
     62 + @Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
     63 + 
     64 + @Before
     65 + public void setUp() throws Exception {
     66 + serviceRegistry.addService(
     67 + new PluginServiceImplBase() {
     68 + @Override
     69 + public void run(RunRequest request, StreamObserver<RunResponse> responseObserver) {
     70 + DetectionReportList.Builder reportListBuilder = DetectionReportList.newBuilder();
     71 + for (MatchedPlugin plugin : request.getPluginsList()) {
     72 + reportListBuilder.addDetectionReports(
     73 + DetectionReport.newBuilder()
     74 + .setTargetInfo(request.getTarget())
     75 + .setNetworkService(plugin.getServices(0)));
     76 + }
     77 + responseObserver.onNext(RunResponse.newBuilder().setReports(reportListBuilder).build());
     78 + responseObserver.onCompleted();
     79 + }
     80 + });
     81 + }
     82 + 
     83 + @Test
     84 + public void detect_withServingServer_returnsSuccessfulDetectionReportList() throws Exception {
     85 + registerHealthCheckWithStatus(ServingStatus.SERVING);
     86 + 
     87 + RemoteVulnDetector pluginToTest = getNewRemoteVulnDetectorInstance();
     88 + var endpointToTest = NetworkEndpointUtils.forIpAndPort("1.1.1.1", 80);
     89 + var serviceToTest =
     90 + NetworkService.newBuilder()
     91 + .setNetworkEndpoint(endpointToTest)
     92 + .setTransportProtocol(TransportProtocol.TCP)
     93 + .setServiceName("http")
     94 + .build();
     95 + 
     96 + TargetInfo testTarget = TargetInfo.newBuilder().addNetworkEndpoints(endpointToTest).build();
     97 + pluginToTest.addMatchedPluginToDetect(
     98 + MatchedPlugin.newBuilder()
     99 + .addServices(serviceToTest)
     100 + .setPlugin(createSinglePluginDefinitionWithName("test"))
     101 + .build());
     102 + assertThat(pluginToTest.detect(testTarget, ImmutableList.of()).getDetectionReportsList())
     103 + .comparingExpectedFieldsOnly()
     104 + .containsExactly(
     105 + DetectionReport.newBuilder()
     106 + .setTargetInfo(testTarget)
     107 + .setNetworkService(serviceToTest)
     108 + .build());
     109 + }
     110 + 
     111 + @Test
     112 + public void detect_withNonServingServer_returnsEmptyDetectionReportList() throws Exception {
     113 + registerHealthCheckWithStatus(ServingStatus.NOT_SERVING);
     114 + 
     115 + RemoteVulnDetector pluginToTest = getNewRemoteVulnDetectorInstance();
     116 + var endpointToTest = NetworkEndpointUtils.forIpAndPort("1.1.1.1", 80);
     117 + var serviceToTest =
     118 + NetworkService.newBuilder()
     119 + .setNetworkEndpoint(endpointToTest)
     120 + .setTransportProtocol(TransportProtocol.TCP)
     121 + .setServiceName("http")
     122 + .build();
     123 + 
     124 + TargetInfo testTarget = TargetInfo.newBuilder().addNetworkEndpoints(endpointToTest).build();
     125 + pluginToTest.addMatchedPluginToDetect(
     126 + MatchedPlugin.newBuilder()
     127 + .addServices(serviceToTest)
     128 + .setPlugin(createSinglePluginDefinitionWithName("test"))
     129 + .build());
     130 + assertThat(pluginToTest.detect(testTarget, ImmutableList.of()).getDetectionReportsList())
     131 + .isEmpty();
     132 + }
     133 + 
     134 + @Test
     135 + public void detect_withRpcError_throwsLanguageServerException() throws Exception {
     136 + registerHealthCheckWithError();
     137 + 
     138 + assertThrows(
     139 + LanguageServerException.class,
     140 + () ->
     141 + getNewRemoteVulnDetectorInstance()
     142 + .detect(TargetInfo.getDefaultInstance(), ImmutableList.of()));
     143 + }
     144 + 
     145 + @Test
     146 + public void getAllPlugins_withServingServer_returnsSuccessfulList() throws Exception {
     147 + registerHealthCheckWithStatus(ServingStatus.SERVING);
     148 + 
     149 + var plugin = createSinglePluginDefinitionWithName("test");
     150 + RemoteVulnDetector pluginToTest = getNewRemoteVulnDetectorInstance();
     151 + serviceRegistry.addService(
     152 + new PluginServiceImplBase() {
     153 + @Override
     154 + public void listPlugins(
     155 + ListPluginsRequest request, StreamObserver<ListPluginsResponse> responseObserver) {
     156 + responseObserver.onNext(ListPluginsResponse.newBuilder().addPlugins(plugin).build());
     157 + responseObserver.onCompleted();
     158 + }
     159 + });
     160 + 
     161 + assertThat(pluginToTest.getAllPlugins()).containsExactly(plugin);
     162 + }
     163 + 
     164 + @Test
     165 + public void getAllPlugins_withNonServingServer_returnsEmptyList() throws Exception {
     166 + registerHealthCheckWithStatus(ServingStatus.NOT_SERVING);
     167 + assertThat(getNewRemoteVulnDetectorInstance().getAllPlugins()).isEmpty();
     168 + }
     169 + 
     170 + @Test
     171 + public void getAllPlugins_withRpcError_throwsLanguageServerException() throws Exception {
     172 + registerHealthCheckWithError();
     173 + assertThrows(LanguageServerException.class, getNewRemoteVulnDetectorInstance()::getAllPlugins);
     174 + }
     175 + 
     176 + private RemoteVulnDetector getNewRemoteVulnDetectorInstance() throws Exception {
     177 + String serverName = InProcessServerBuilder.generateName();
     178 + grpcCleanup.register(
     179 + InProcessServerBuilder.forName(serverName)
     180 + .fallbackHandlerRegistry(serviceRegistry)
     181 + .directExecutor()
     182 + .build()
     183 + .start());
     184 + 
     185 + return Guice.createInjector(
     186 + new AbstractModule() {
     187 + @Override
     188 + protected void configure() {
     189 + bind(RemoteVulnDetector.class)
     190 + .toInstance(
     191 + new RemoteVulnDetectorImpl(
     192 + InProcessChannelBuilder.forName(serverName).directExecutor().build()));
     193 + }
     194 + })
     195 + .getInstance(RemoteVulnDetector.class);
     196 + }
     197 + 
     198 + private void registerHealthCheckWithError() {
     199 + serviceRegistry.addService(
     200 + new HealthImplBase() {
     201 + @Override
     202 + public void check(
     203 + HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {
     204 + responseObserver.onError(new RuntimeException("Test failure."));
     205 + responseObserver.onCompleted();
     206 + }
     207 + });
     208 + }
     209 + 
     210 + private void registerHealthCheckWithStatus(ServingStatus status) {
     211 + serviceRegistry.addService(
     212 + new HealthImplBase() {
     213 + @Override
     214 + public void check(
     215 + HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {
     216 + responseObserver.onNext(HealthCheckResponse.newBuilder().setStatus(status).build());
     217 + responseObserver.onCompleted();
     218 + }
     219 + });
     220 + }
     221 + 
     222 + private PluginDefinition createSinglePluginDefinitionWithName(String name) {
     223 + return PluginDefinition.newBuilder()
     224 + .setInfo(
     225 + PluginInfo.newBuilder()
     226 + .setType(PluginInfo.PluginType.VULN_DETECTION)
     227 + .setName(name)
     228 + .setVersion(PLUGIN_VERSION)
     229 + .setDescription(PLUGIN_DESCRIPTION)
     230 + .setAuthor(PLUGIN_AUTHOR))
     231 + .build();
     232 + }
     233 +}
     234 + 
Please wait...
Page is in error, reload to recover