Skip to content

Commit dada6ab

Browse files
authored
Add custom OPA filter implementation (#117)
Signed-off-by: Pavol Loffay <[email protected]>
1 parent aabc0c4 commit dada6ab

26 files changed

+1015
-19
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ List of supported frameworks with additional capabilities:
1919
| [gRPC](https://github.com/grpc/grpc-java) | 1.5+ |
2020
| [OkHttp](https://github.com/square/okhttp/) | 3.0+ |
2121

22+
### Adding custom filter implementation
23+
24+
Custom filter implementations can be added via `FilterProvider` SPI (Java service loader).
25+
The providers can be disabled at startup via `ht.filter.provider.<provider-class-name>.disabled=true`.
2226

2327
## Build
2428

filter-custom-opa/build.gradle.kts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
plugins {
2+
java
3+
}
4+
5+
dependencies {
6+
implementation(project(":filter"))
7+
implementation("org.slf4j:slf4j-api:1.7.30")
8+
implementation("com.squareup.okhttp3:okhttp:3.14.9")
9+
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
10+
implementation("com.fasterxml.jackson.core:jackson-databind:2.11.3")
11+
implementation("com.google.auto.service:auto-service:1.0-rc7")
12+
annotationProcessor("com.google.auto.service:auto-service:1.0-rc7")
13+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright The Hypertrace Authors
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 org.hypertrace.agent.filter.opa.custom;
18+
19+
import com.google.auto.service.AutoService;
20+
import java.util.HashSet;
21+
import java.util.Map;
22+
import java.util.Set;
23+
import java.util.concurrent.Executors;
24+
import java.util.concurrent.ScheduledExecutorService;
25+
import java.util.concurrent.ThreadFactory;
26+
import java.util.concurrent.TimeUnit;
27+
import java.util.concurrent.atomic.AtomicInteger;
28+
import org.hypertrace.agent.filter.ExecutionBlocked;
29+
import org.hypertrace.agent.filter.ExecutionNotBlocked;
30+
import org.hypertrace.agent.filter.Filter;
31+
import org.hypertrace.agent.filter.FilterResult;
32+
import org.hypertrace.agent.filter.opa.custom.evaluator.ICustomPolicyEvaluator;
33+
import org.hypertrace.agent.filter.opa.custom.evaluator.IpAddressPolicyEvaluator;
34+
import org.slf4j.Logger;
35+
import org.slf4j.LoggerFactory;
36+
37+
/** This is a legacy code ported from Traceable Java agent. */
38+
@AutoService(Filter.class)
39+
public class CustomOpaLib implements Filter {
40+
private static final Logger log = LoggerFactory.getLogger(CustomOpaLib.class);
41+
42+
private final OpaCommunicator opaCommunicator = new OpaCommunicator();
43+
private final Set<ICustomPolicyEvaluator> policyEvaluators = new HashSet<>();
44+
45+
private final ScheduledExecutorService scheduledExecutorService =
46+
Executors.newSingleThreadScheduledExecutor(
47+
new ThreadFactory() {
48+
private final AtomicInteger threadSequence = new AtomicInteger(1);
49+
50+
@Override
51+
public Thread newThread(Runnable r) {
52+
String name = "hypertrace-agent-custom-opa" + threadSequence.getAndIncrement();
53+
Thread thread = new Thread(r, name);
54+
thread.setDaemon(true);
55+
return thread;
56+
}
57+
});
58+
59+
public CustomOpaLib(String endpoint, String apiKey, boolean skipVerify, int maxDelay) {
60+
opaCommunicator.init(endpoint, apiKey, skipVerify);
61+
scheduledExecutorService.scheduleWithFixedDelay(
62+
new Runnable() {
63+
@Override
64+
public void run() {
65+
try {
66+
opaCommunicator.pollBlockingData();
67+
} catch (Throwable t) {
68+
log.debug("Unable to poll blocking data", t);
69+
}
70+
}
71+
},
72+
0,
73+
maxDelay,
74+
TimeUnit.SECONDS);
75+
76+
policyEvaluators.add(new IpAddressPolicyEvaluator());
77+
}
78+
79+
// TODO agent should clear resources at the end
80+
// @Override
81+
// public void fini() {
82+
// scheduledExecutorService.shutdownNow();
83+
// scheduledExecutorService = null;
84+
// opaCommunicator.clear();
85+
// policyEvaluators.clear();
86+
// }
87+
88+
@Override
89+
public FilterResult evaluateRequestHeaders(
90+
io.opentelemetry.trace.Span span, Map<String, String> headers) {
91+
// currently as per policy.rego, allowed list has precedence over denylist
92+
boolean allow =
93+
policyEvaluators.stream()
94+
.map(
95+
policyEvaluator ->
96+
policyEvaluator.allow(opaCommunicator.getBlockingData(), headers))
97+
.anyMatch(Boolean::booleanValue);
98+
return allow ? ExecutionNotBlocked.INSTANCE : ExecutionBlocked.INSTANCE;
99+
}
100+
101+
@Override
102+
public FilterResult evaluateRequestBody(io.opentelemetry.trace.Span span, String body) {
103+
return ExecutionNotBlocked.INSTANCE;
104+
}
105+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright The Hypertrace Authors
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 org.hypertrace.agent.filter.opa.custom;
18+
19+
import com.google.auto.service.AutoService;
20+
import org.hypertrace.agent.core.EnvironmentConfig;
21+
import org.hypertrace.agent.filter.Filter;
22+
import org.hypertrace.agent.filter.FilterProvider;
23+
import org.hypertrace.agent.filter.FilterRegistry;
24+
25+
@AutoService(FilterProvider.class)
26+
public class CustomOpaLibProvider implements FilterProvider {
27+
28+
public CustomOpaLibProvider() {
29+
String property = FilterRegistry.getProviderDisabledPropertyName(CustomOpaLibProvider.class);
30+
// by default disable this provider until HT agent config includes OPA
31+
if (EnvironmentConfig.getProperty(property) == null) {
32+
System.setProperty(property, "true");
33+
}
34+
}
35+
36+
@Override
37+
public Filter create() {
38+
String endpoint = EnvironmentConfig.getProperty("hypertrace.opa.endpoint");
39+
String token = EnvironmentConfig.getProperty("hypertrace.reporting.token");
40+
String pullInterval = EnvironmentConfig.getProperty("hypertrace.opa.pull_interval");
41+
String skipVerify = EnvironmentConfig.getProperty("hypertrace.opa.skip_verify");
42+
return new CustomOpaLib(
43+
endpoint, token, Boolean.parseBoolean(skipVerify), Integer.parseInt(pullInterval));
44+
}
45+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright The Hypertrace Authors
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 org.hypertrace.agent.filter.opa.custom;
18+
19+
import com.fasterxml.jackson.databind.JsonNode;
20+
import com.fasterxml.jackson.databind.ObjectMapper;
21+
import java.io.IOException;
22+
import java.security.KeyManagementException;
23+
import java.security.NoSuchAlgorithmException;
24+
import java.security.cert.CertificateException;
25+
import javax.net.ssl.*;
26+
import okhttp3.Interceptor;
27+
import okhttp3.OkHttpClient;
28+
import okhttp3.Request;
29+
import okhttp3.Response;
30+
import org.hypertrace.agent.filter.opa.custom.data.BlockingData;
31+
import org.slf4j.Logger;
32+
import org.slf4j.LoggerFactory;
33+
34+
public class OpaCommunicator {
35+
private static final Logger log = LoggerFactory.getLogger(OpaCommunicator.class);
36+
37+
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
38+
private static final String PATH = "/v1/data";
39+
40+
private OkHttpClient httpClient;
41+
private Request request;
42+
43+
private BlockingData blockingData;
44+
45+
protected OpaCommunicator() {}
46+
47+
public void init(String endpoint, String authToken, boolean skipVerify) {
48+
OkHttpClient.Builder builder = new OkHttpClient.Builder();
49+
if (authToken != null && !authToken.isEmpty()) {
50+
log.info("Adding authentication key");
51+
builder = withAuth(builder, authToken);
52+
}
53+
if (skipVerify) {
54+
builder = withSkipVerify(builder);
55+
}
56+
this.httpClient = builder.build();
57+
if (endpoint.endsWith("/")) {
58+
endpoint = endpoint.substring(0, endpoint.length() - 1);
59+
}
60+
this.request = new Request.Builder().url(endpoint + PATH).get().build();
61+
}
62+
63+
public void pollBlockingData() {
64+
if (httpClient == null) {
65+
return;
66+
}
67+
68+
Response response;
69+
try {
70+
response = httpClient.newCall(request).execute();
71+
} catch (IOException e) {
72+
log.warn("Unable to make a successful get call to the OPA service.", e);
73+
return;
74+
}
75+
76+
log.trace("Received response from OPA service: {}", response);
77+
if (response.isSuccessful()) {
78+
try {
79+
JsonNode jsonNode = OBJECT_MAPPER.readTree(response.body().byteStream());
80+
if (log.isDebugEnabled()) {
81+
log.debug("Received blocking data from OPA service: {}", jsonNode);
82+
}
83+
blockingData =
84+
OBJECT_MAPPER.treeToValue(
85+
jsonNode.at("/result/traceable/http/request"), BlockingData.class);
86+
} catch (IOException e) {
87+
log.warn("Unable to retrieve blocking data from the OPA service.", e);
88+
}
89+
}
90+
}
91+
92+
public BlockingData getBlockingData() {
93+
return blockingData;
94+
}
95+
96+
public void clear() {
97+
httpClient = null;
98+
request = null;
99+
blockingData = null;
100+
}
101+
102+
private static OkHttpClient.Builder withAuth(OkHttpClient.Builder builder, String authToken) {
103+
builder.addInterceptor(getAuthInterceptor("Bearer " + authToken));
104+
return builder;
105+
}
106+
107+
private static OkHttpClient.Builder withSkipVerify(OkHttpClient.Builder builder) {
108+
// Install the all-trusting trust manager
109+
try {
110+
TrustManager[] trustAllCertsManagers = getTrustAllCertsManagers();
111+
final SSLContext sslContext = SSLContext.getInstance("SSL");
112+
sslContext.init(null, trustAllCertsManagers, new java.security.SecureRandom());
113+
// Create an ssl socket factory with our all-trusting manager
114+
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
115+
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustAllCertsManagers[0]);
116+
builder.hostnameVerifier(getSkipVerifyHostnameVerifier());
117+
} catch (NoSuchAlgorithmException e) {
118+
log.warn("Error in getting SSL context. SkipVerify could not be set to true.", e);
119+
} catch (KeyManagementException e) {
120+
log.warn("Error in initializing SSL context. SkipVerify could not be set to true.", e);
121+
}
122+
return builder;
123+
}
124+
125+
private static Interceptor getAuthInterceptor(final String headerValue) {
126+
return new Interceptor() {
127+
@Override
128+
public Response intercept(Chain chain) throws IOException {
129+
return chain.proceed(
130+
chain.request().newBuilder().addHeader("Authorization", headerValue).build());
131+
}
132+
};
133+
}
134+
135+
private static TrustManager[] getTrustAllCertsManagers() {
136+
return new TrustManager[] {
137+
new X509TrustManager() {
138+
@Override
139+
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType)
140+
throws CertificateException {}
141+
142+
@Override
143+
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType)
144+
throws CertificateException {}
145+
146+
@Override
147+
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
148+
return new java.security.cert.X509Certificate[] {};
149+
}
150+
}
151+
};
152+
}
153+
154+
private static HostnameVerifier getSkipVerifyHostnameVerifier() {
155+
return new HostnameVerifier() {
156+
@Override
157+
public boolean verify(String hostname, SSLSession session) {
158+
return true;
159+
}
160+
};
161+
}
162+
}

0 commit comments

Comments
 (0)