Skip to content

Commit d376a59

Browse files
gtullyzeitlinger
andauthored
http server, add subject.doAs handler wrapper for exchange attribute configured via authenticatedSubjectAttributeName #1088 (#1089)
Signed-off-by: Gary Tully <[email protected]> Co-authored-by: Gregor Zeitlinger <[email protected]>
1 parent 5973270 commit d376a59

File tree

2 files changed

+153
-6
lines changed
  • prometheus-metrics-exporter-httpserver/src

2 files changed

+153
-6
lines changed

prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HTTPServer.java

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.sun.net.httpserver.Authenticator;
44
import com.sun.net.httpserver.HttpContext;
5+
import com.sun.net.httpserver.HttpExchange;
56
import com.sun.net.httpserver.HttpHandler;
67
import com.sun.net.httpserver.HttpServer;
78
import com.sun.net.httpserver.HttpsConfigurator;
@@ -10,14 +11,18 @@
1011
import io.prometheus.metrics.model.registry.PrometheusRegistry;
1112
import java.io.Closeable;
1213
import java.io.IOException;
14+
import java.io.InputStream;
1315
import java.net.InetAddress;
1416
import java.net.InetSocketAddress;
17+
import java.security.PrivilegedActionException;
18+
import java.security.PrivilegedExceptionAction;
1519
import java.util.concurrent.ExecutionException;
1620
import java.util.concurrent.ExecutorService;
1721
import java.util.concurrent.RejectedExecutionHandler;
1822
import java.util.concurrent.SynchronousQueue;
1923
import java.util.concurrent.ThreadPoolExecutor;
2024
import java.util.concurrent.TimeUnit;
25+
import javax.security.auth.Subject;
2126

2227
/**
2328
* Expose Prometheus metrics using a plain Java HttpServer.
@@ -51,16 +56,25 @@ private HTTPServer(
5156
HttpServer httpServer,
5257
PrometheusRegistry registry,
5358
Authenticator authenticator,
59+
String authenticatedSubjectAttributeName,
5460
HttpHandler defaultHandler) {
5561
if (httpServer.getAddress() == null) {
5662
throw new IllegalArgumentException("HttpServer hasn't been bound to an address");
5763
}
5864
this.server = httpServer;
5965
this.executorService = executorService;
6066
registerHandler(
61-
"/", defaultHandler == null ? new DefaultHandler() : defaultHandler, authenticator);
62-
registerHandler("/metrics", new MetricsHandler(config, registry), authenticator);
63-
registerHandler("/-/healthy", new HealthyHandler(), authenticator);
67+
"/",
68+
defaultHandler == null ? new DefaultHandler() : defaultHandler,
69+
authenticator,
70+
authenticatedSubjectAttributeName);
71+
registerHandler(
72+
"/metrics",
73+
new MetricsHandler(config, registry),
74+
authenticator,
75+
authenticatedSubjectAttributeName);
76+
registerHandler(
77+
"/-/healthy", new HealthyHandler(), authenticator, authenticatedSubjectAttributeName);
6478
try {
6579
// HttpServer.start() starts the HttpServer in a new background thread.
6680
// If we call HttpServer.start() from a thread of the executorService,
@@ -74,13 +88,54 @@ private HTTPServer(
7488
}
7589
}
7690

77-
private void registerHandler(String path, HttpHandler handler, Authenticator authenticator) {
78-
HttpContext context = server.createContext(path, handler);
91+
private void registerHandler(
92+
String path, HttpHandler handler, Authenticator authenticator, String subjectAttributeName) {
93+
HttpContext context = server.createContext(path, wrapWithDoAs(handler, subjectAttributeName));
7994
if (authenticator != null) {
8095
context.setAuthenticator(authenticator);
8196
}
8297
}
8398

99+
private HttpHandler wrapWithDoAs(HttpHandler handler, String subjectAttributeName) {
100+
if (subjectAttributeName == null) {
101+
return handler;
102+
}
103+
104+
// invoke handler using the subject.doAs from the named attribute
105+
return new HttpHandler() {
106+
@Override
107+
public void handle(HttpExchange exchange) throws IOException {
108+
Object authSubject = exchange.getAttribute(subjectAttributeName);
109+
if (authSubject instanceof Subject) {
110+
try {
111+
Subject.doAs(
112+
(Subject) authSubject,
113+
(PrivilegedExceptionAction<IOException>)
114+
() -> {
115+
handler.handle(exchange);
116+
return null;
117+
});
118+
} catch (PrivilegedActionException e) {
119+
if (e.getException() != null) {
120+
throw new IOException(e.getException());
121+
} else throw new IOException(e);
122+
}
123+
} else {
124+
drainInputAndClose(exchange);
125+
exchange.sendResponseHeaders(403, -1);
126+
}
127+
}
128+
};
129+
}
130+
131+
private void drainInputAndClose(HttpExchange httpExchange) throws IOException {
132+
InputStream inputStream = httpExchange.getRequestBody();
133+
byte[] b = new byte[4096];
134+
while (inputStream.read(b) != -1)
135+
;
136+
inputStream.close();
137+
}
138+
84139
/** Stop the HTTP server. Same as {@link #close()}. */
85140
public void stop() {
86141
close();
@@ -120,6 +175,7 @@ public static class Builder {
120175
private Authenticator authenticator = null;
121176
private HttpsConfigurator httpsConfigurator = null;
122177
private HttpHandler defaultHandler = null;
178+
private String authenticatedSubjectAttributeName = null;
123179

124180
private Builder(PrometheusProperties config) {
125181
this.config = config;
@@ -171,6 +227,12 @@ public Builder authenticator(Authenticator authenticator) {
171227
return this;
172228
}
173229

230+
/** Optional: the attribute name of a Subject from a custom authenticator. */
231+
public Builder authenticatedSubjectAttributeName(String authenticatedSubjectAttributeName) {
232+
this.authenticatedSubjectAttributeName = authenticatedSubjectAttributeName;
233+
return this;
234+
}
235+
174236
/** Optional: {@link HttpsConfigurator} for TLS/SSL */
175237
public Builder httpsConfigurator(HttpsConfigurator configurator) {
176238
this.httpsConfigurator = configurator;
@@ -201,7 +263,13 @@ public HTTPServer buildAndStart() throws IOException {
201263
ExecutorService executorService = makeExecutorService();
202264
httpServer.setExecutor(executorService);
203265
return new HTTPServer(
204-
config, executorService, httpServer, registry, authenticator, defaultHandler);
266+
config,
267+
executorService,
268+
httpServer,
269+
registry,
270+
authenticator,
271+
authenticatedSubjectAttributeName,
272+
defaultHandler);
205273
}
206274

207275
private InetSocketAddress makeInetSocketAddress() {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package io.prometheus.metrics.exporter.httpserver;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import com.sun.net.httpserver.Authenticator;
6+
import com.sun.net.httpserver.HttpExchange;
7+
import com.sun.net.httpserver.HttpHandler;
8+
import com.sun.net.httpserver.HttpPrincipal;
9+
import java.io.IOException;
10+
import java.net.InetSocketAddress;
11+
import java.net.Socket;
12+
import java.nio.charset.StandardCharsets;
13+
import java.security.AccessController;
14+
import java.security.Principal;
15+
import javax.security.auth.Subject;
16+
import org.junit.jupiter.api.Test;
17+
18+
public class HTTPServerTest {
19+
20+
@Test
21+
public void testSubjectDoAs() throws Exception {
22+
23+
final String user = "joe";
24+
final Subject subject = new Subject();
25+
subject.getPrincipals().add(() -> (user));
26+
27+
Authenticator authenticator =
28+
new Authenticator() {
29+
@Override
30+
public Result authenticate(HttpExchange exchange) {
31+
exchange.setAttribute("aa", subject);
32+
return new Success(new HttpPrincipal(user, "/"));
33+
}
34+
};
35+
36+
HttpHandler handler =
37+
exchange -> {
38+
boolean found = false;
39+
Subject current = Subject.getSubject(AccessController.getContext());
40+
for (Principal p : current.getPrincipals()) {
41+
if (user.equals(p.getName())) {
42+
found = true;
43+
break;
44+
}
45+
}
46+
if (!found) {
47+
throw new IOException("Expected validated user joe!");
48+
}
49+
exchange.sendResponseHeaders(204, -1);
50+
};
51+
HTTPServer server =
52+
HTTPServer.builder()
53+
.port(0)
54+
.authenticator(authenticator)
55+
.defaultHandler(handler)
56+
.authenticatedSubjectAttributeName("aa")
57+
.buildAndStart();
58+
59+
Socket socket = new Socket();
60+
try {
61+
socket.connect(new InetSocketAddress("localhost", server.getPort()));
62+
63+
socket.getOutputStream().write("GET / HTTP/1.1 \r\n".getBytes(StandardCharsets.UTF_8));
64+
socket.getOutputStream().write("HOST: localhost \r\n\r\n".getBytes(StandardCharsets.UTF_8));
65+
socket.getOutputStream().flush();
66+
67+
String actualResponse = "";
68+
byte[] resp = new byte[500];
69+
int read = socket.getInputStream().read(resp, 0, resp.length);
70+
if (read > 0) {
71+
actualResponse = new String(resp, 0, read);
72+
}
73+
assertThat(actualResponse).contains("204");
74+
75+
} finally {
76+
socket.close();
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)