Skip to content

Commit 8f5e45e

Browse files
Merge pull request #537 from secureness:weak_cred_tester_airbyte
PiperOrigin-RevId: 781560878 Change-Id: I209b392b35c5743d5366fea7a05acfeb7c9eb564
2 parents d5fb1c9 + 03a9ca1 commit 8f5e45e

6 files changed

Lines changed: 347 additions & 16 deletions

File tree

google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetector.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,7 @@ public final class GenericWeakCredentialDetector implements VulnDetector {
7979
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
8080
private static final Severity DEFAULT_SEVERITY = Severity.CRITICAL;
8181

82-
@VisibleForTesting
83-
final ImmutableSet<CredentialProvider> providers;
82+
@VisibleForTesting final ImmutableSet<CredentialProvider> providers;
8483
private final ImmutableSet<CredentialTester> testers;
8584
private final Clock utcClock;
8685

@@ -258,5 +257,4 @@ private static AdditionalDetail buildCredentialDetail(
258257
.setCredentials(Credentials.newBuilder().addAllCredential(credentials))
259258
.build();
260259
}
261-
262260
}

google/detectors/credentials/generic_weak_credential_detector/src/main/java/com/google/tsunami/plugins/detectors/credentials/genericweakcredentialdetector/GenericWeakCredentialDetectorBootstrapModule.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,21 @@
3636
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.DefaultCredentials;
3737
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.Top100Passwords;
3838
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.tester.CredentialTester;
39+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.airbyte.AirbyteCredentialTester;
3940
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.airflow.AirflowCredentialTester;
41+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.argocd.ArgoCdCredentialTester;
4042
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.grafana.GrafanaCredentialTester;
43+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.hive.HiveCredentialTester;
4144
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.hydra.HydraCredentialTester;
4245
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.jenkins.JenkinsCredentialTester;
43-
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.argocd.ArgoCdCredentialTester;
4446
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.mlflow.MlFlowCredentialTester;
4547
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.mysql.MysqlCredentialTester;
46-
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.hive.HiveCredentialTester;
4748
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.ncrack.NcrackCredentialTester;
4849
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.postgres.PostgresCredentialTester;
4950
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.rabbitmq.RabbitMQCredentialTester;
50-
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.wordpress.WordpressCredentialTester;
5151
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.rstudio.RStudioCredentialTester;
52+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.wordpress.WordpressCredentialTester;
5253
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.zenml.ZenMlCredentialTester;
53-
5454
import java.io.FileNotFoundException;
5555
import java.io.IOException;
5656
import java.nio.file.Files;
@@ -70,19 +70,20 @@ protected void configurePlugin() {
7070

7171
Multibinder<CredentialTester> credentialTesterBinder =
7272
Multibinder.newSetBinder(binder(), CredentialTester.class);
73+
credentialTesterBinder.addBinding().to(AirbyteCredentialTester.class);
7374
credentialTesterBinder.addBinding().to(AirflowCredentialTester.class);
7475
credentialTesterBinder.addBinding().to(ArgoCdCredentialTester.class);
76+
credentialTesterBinder.addBinding().to(GrafanaCredentialTester.class);
77+
credentialTesterBinder.addBinding().to(HiveCredentialTester.class);
78+
credentialTesterBinder.addBinding().to(HydraCredentialTester.class);
7579
credentialTesterBinder.addBinding().to(JenkinsCredentialTester.class);
7680
credentialTesterBinder.addBinding().to(MlFlowCredentialTester.class);
7781
credentialTesterBinder.addBinding().to(MysqlCredentialTester.class);
78-
credentialTesterBinder.addBinding().to(HiveCredentialTester.class);
79-
credentialTesterBinder.addBinding().to(HydraCredentialTester.class);
8082
credentialTesterBinder.addBinding().to(NcrackCredentialTester.class);
8183
credentialTesterBinder.addBinding().to(PostgresCredentialTester.class);
82-
credentialTesterBinder.addBinding().to(WordpressCredentialTester.class);
83-
credentialTesterBinder.addBinding().to(GrafanaCredentialTester.class);
84-
credentialTesterBinder.addBinding().to(RStudioCredentialTester.class);
8584
credentialTesterBinder.addBinding().to(RabbitMQCredentialTester.class);
85+
credentialTesterBinder.addBinding().to(RStudioCredentialTester.class);
86+
credentialTesterBinder.addBinding().to(WordpressCredentialTester.class);
8687
credentialTesterBinder.addBinding().to(ZenMlCredentialTester.class);
8788

8889
Multibinder<CredentialProvider> credentialProviderBinder =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2024 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+
17+
package com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.airbyte;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static com.google.tsunami.common.net.http.HttpRequest.post;
21+
22+
import com.google.common.collect.ImmutableList;
23+
import com.google.common.flogger.GoogleLogger;
24+
import com.google.protobuf.ByteString;
25+
import com.google.tsunami.common.data.NetworkEndpointUtils;
26+
import com.google.tsunami.common.data.NetworkServiceUtils;
27+
import com.google.tsunami.common.net.http.HttpClient;
28+
import com.google.tsunami.common.net.http.HttpHeaders;
29+
import com.google.tsunami.common.net.http.HttpResponse;
30+
import com.google.tsunami.common.net.http.HttpStatus;
31+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.TestCredential;
32+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.tester.CredentialTester;
33+
import com.google.tsunami.proto.NetworkService;
34+
import java.io.IOException;
35+
import java.util.List;
36+
import javax.inject.Inject;
37+
38+
/** Credential tester specifically for airbyte. */
39+
public final class AirbyteCredentialTester extends CredentialTester {
40+
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
41+
private static final String AIRBYTE_SERVICE = "airbyte";
42+
43+
private final HttpClient httpClient;
44+
45+
@Inject
46+
AirbyteCredentialTester(HttpClient httpClient) {
47+
this.httpClient = checkNotNull(httpClient);
48+
}
49+
50+
@Override
51+
public String name() {
52+
return "AirbyteCredentialTester";
53+
}
54+
55+
@Override
56+
public String description() {
57+
return "Airbyte credential tester.";
58+
}
59+
60+
@Override
61+
public boolean canAccept(NetworkService networkService) {
62+
return NetworkServiceUtils.getWebServiceName(networkService).equals(AIRBYTE_SERVICE);
63+
}
64+
65+
@Override
66+
public boolean batched() {
67+
return false;
68+
}
69+
70+
@Override
71+
public ImmutableList<TestCredential> testValidCredentials(
72+
NetworkService networkService, List<TestCredential> credentials) {
73+
// Always return 1st weak credential to gracefully handle no auth configured case, where we
74+
// return empty credential instead of all the weak credentials
75+
return credentials.stream()
76+
.filter(cred -> isAirbyteAccessible(networkService, cred))
77+
.findFirst()
78+
.map(ImmutableList::of)
79+
.orElseGet(ImmutableList::of);
80+
}
81+
82+
private boolean isAirbyteAccessible(NetworkService networkService, TestCredential credential) {
83+
String rootUrl =
84+
"http://" + NetworkEndpointUtils.toUriAuthority(networkService.getNetworkEndpoint()) + "/";
85+
86+
try {
87+
HttpResponse rootResponse =
88+
httpClient.send(
89+
post(rootUrl + "api/login")
90+
.setHeaders(
91+
HttpHeaders.builder().addHeader("content-type", "application/json").build())
92+
.setRequestBody(
93+
ByteString.copyFromUtf8(
94+
String.format(
95+
"{\"username\":\"%s\",\"password\":\"%s\"}",
96+
credential.username(), credential.password().orElse(""))))
97+
.build());
98+
99+
if (rootResponse.status() == HttpStatus.OK
100+
&& rootResponse.headers().get("set-cookie").isPresent()) {
101+
logger.atInfo().log(
102+
"Using default Airbyte credentials: %s:%s",
103+
credential.username(), credential.password().orElse(""));
104+
return true;
105+
}
106+
} catch (IOException e) {
107+
logger.atWarning().withCause(e).log("Unable to query '%s'.", rootUrl);
108+
return false;
109+
}
110+
return false;
111+
}
112+
}

google/detectors/credentials/generic_weak_credential_detector/src/main/resources/detectors/credentials/genericweakcredentialdetector/data/service_default_credentials.textproto

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,14 @@ service_default_credentials {
9696
service_name: "airflow"
9797
default_usernames: "airflow"
9898
default_passwords: "airflow"
99-
}
99+
}
100+
101+
service_default_credentials {
102+
service_name: "airbyte"
103+
default_usernames: "airbyte"
104+
default_passwords: "password"
105+
default_usernames: "user@company.example"
106+
default_passwords: "new_password"
107+
default_usernames: "your_new_username_here"
108+
default_passwords: "your_new_password_here"
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2023 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+
17+
package com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.testers.airbyte;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static com.google.tsunami.common.data.NetworkEndpointUtils.forHostnameAndPort;
21+
import static org.mockito.ArgumentMatchers.any;
22+
import static org.mockito.Mockito.verifyNoInteractions;
23+
import static org.mockito.Mockito.when;
24+
25+
import com.google.common.collect.ImmutableList;
26+
import com.google.inject.Guice;
27+
import com.google.tsunami.common.net.db.ConnectionProviderInterface;
28+
import com.google.tsunami.common.net.http.HttpClientModule;
29+
import com.google.tsunami.plugins.detectors.credentials.genericweakcredentialdetector.provider.TestCredential;
30+
import com.google.tsunami.proto.NetworkService;
31+
import java.io.IOException;
32+
import java.sql.Connection;
33+
import java.util.Objects;
34+
import java.util.Optional;
35+
import javax.inject.Inject;
36+
import okhttp3.mockwebserver.Dispatcher;
37+
import okhttp3.mockwebserver.MockResponse;
38+
import okhttp3.mockwebserver.MockWebServer;
39+
import okhttp3.mockwebserver.RecordedRequest;
40+
import org.junit.Before;
41+
import org.junit.Rule;
42+
import org.junit.Test;
43+
import org.junit.runner.RunWith;
44+
import org.junit.runners.JUnit4;
45+
import org.mockito.Mock;
46+
import org.mockito.junit.MockitoJUnit;
47+
import org.mockito.junit.MockitoRule;
48+
49+
/** Tests for {@link AirbyteCredentialTester}. */
50+
@RunWith(JUnit4.class)
51+
public class AirbyteCredentialTesterTest {
52+
@Rule public MockitoRule rule = MockitoJUnit.rule();
53+
@Mock private ConnectionProviderInterface mockConnectionProvider;
54+
@Mock private Connection mockConnection;
55+
@Inject private AirbyteCredentialTester tester;
56+
private MockWebServer mockWebServer;
57+
private static final TestCredential WEAK_CRED_1 =
58+
TestCredential.create("user@company.example", Optional.of("new_password"));
59+
private static final TestCredential WRONG_CRED_1 =
60+
TestCredential.create("wrong", Optional.of("wrong"));
61+
62+
@Before
63+
public void setup() {
64+
mockWebServer = new MockWebServer();
65+
Guice.createInjector(new HttpClientModule.Builder().build()).injectMembers(this);
66+
}
67+
68+
@Test
69+
public void detect_weakCredentialsExists_returnsWeakCredentials() throws Exception {
70+
startMockWebServer();
71+
NetworkService targetNetworkService =
72+
NetworkService.newBuilder()
73+
.setNetworkEndpoint(
74+
forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort()))
75+
.setServiceName("airbyte")
76+
.build();
77+
78+
assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WEAK_CRED_1)))
79+
.containsExactly(WEAK_CRED_1);
80+
mockWebServer.shutdown();
81+
}
82+
83+
@Test
84+
public void detect_weakCredentialsExist_returnsFirstWeakCredentials() throws Exception {
85+
startMockWebServer();
86+
NetworkService targetNetworkService =
87+
NetworkService.newBuilder()
88+
.setNetworkEndpoint(
89+
forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort()))
90+
.setServiceName("airbyte")
91+
.build();
92+
93+
assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WEAK_CRED_1)))
94+
.containsExactly(WEAK_CRED_1);
95+
}
96+
97+
@Test
98+
public void detect_airbyteService_canAccept() throws Exception {
99+
startMockWebServer();
100+
NetworkService targetNetworkService =
101+
NetworkService.newBuilder()
102+
.setNetworkEndpoint(
103+
forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort()))
104+
.setServiceName("airbyte")
105+
.build();
106+
107+
assertThat(tester.canAccept(targetNetworkService)).isTrue();
108+
}
109+
110+
@Test
111+
public void detect_weakCredentialsExistAndAirbyteInForeignLanguage_returnsFirstWeakCredentials()
112+
throws Exception {
113+
startMockWebServer();
114+
NetworkService targetNetworkService =
115+
NetworkService.newBuilder()
116+
.setNetworkEndpoint(
117+
forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort()))
118+
.setServiceName("airbyte")
119+
.build();
120+
121+
assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WEAK_CRED_1)))
122+
.containsExactly(WEAK_CRED_1);
123+
}
124+
125+
@Test
126+
public void detect_noWeakCredentials_returnsNoCredentials() throws Exception {
127+
startMockWebServer();
128+
NetworkService targetNetworkService =
129+
NetworkService.newBuilder()
130+
.setNetworkEndpoint(
131+
forHostnameAndPort(mockWebServer.getHostName(), mockWebServer.getPort()))
132+
.setServiceName("airbyte")
133+
.build();
134+
assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WRONG_CRED_1)))
135+
.isEmpty();
136+
}
137+
138+
@Test
139+
public void detect_nonAirbyteService_skips() throws Exception {
140+
when(mockConnectionProvider.getConnection(any(), any(), any())).thenReturn(mockConnection);
141+
NetworkService targetNetworkService =
142+
NetworkService.newBuilder()
143+
.setNetworkEndpoint(forHostnameAndPort("example.com", 8080))
144+
.setServiceName("http")
145+
.build();
146+
147+
assertThat(tester.testValidCredentials(targetNetworkService, ImmutableList.of(WEAK_CRED_1)))
148+
.isEmpty();
149+
verifyNoInteractions(mockConnectionProvider);
150+
}
151+
152+
private void startMockWebServer() throws IOException {
153+
final Dispatcher dispatcher =
154+
new Dispatcher() {
155+
@Override
156+
public MockResponse dispatch(RecordedRequest request) {
157+
if (request.getPath().equals("/api/login")
158+
&& Objects.equals(request.getMethod(), "POST")
159+
&& Objects.equals(request.getHeader("content-type"), "application/json")
160+
&& request
161+
.getBody()
162+
.readUtf8()
163+
.equals(
164+
"{\"username\":\"user@company.example\",\"password\":\"new_password\"}")) {
165+
return new MockResponse().setResponseCode(200).setHeader("set-cookie", "jwt=12345");
166+
} else {
167+
return new MockResponse().setResponseCode(401);
168+
}
169+
}
170+
};
171+
mockWebServer.setDispatcher(dispatcher);
172+
mockWebServer.start();
173+
mockWebServer.url("/");
174+
}
175+
}

0 commit comments

Comments
 (0)