From 8c99d4a3a8f801fc8295f98f1b510827a5cfda8a Mon Sep 17 00:00:00 2001 From: Gary Tully Date: Sun, 20 Oct 2024 06:11:54 +0100 Subject: [PATCH] add support for loading a customAuthenticator class #1001 - needs client_java 1.4 (#1002) Signed-off-by: Gary Tully --- docs/README.md | 22 ++ .../AuthenticatorClassTest.java | 37 +++ .../JavaAgent/application.sh | 6 + .../JavaAgent/exporter.yaml | 7 + .../prometheus/jmx/CustomAuthenticator.java | 56 ++++ .../jmx/common/http/HTTPServerFactory.java | 272 ++++++++++++------ .../common/http/HTTPServerFactoryTest.java | 184 ++++++++++++ .../authenticator/CustomAuthenticator451.java | 26 ++ .../CustomAuthenticatorWithSubject.java | 32 +++ pom.xml | 2 +- 10 files changed, 557 insertions(+), 87 deletions(-) create mode 100644 integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest.java create mode 100644 integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest/JavaAgent/application.sh create mode 100644 integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest/JavaAgent/exporter.yaml create mode 100644 integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/CustomAuthenticator.java create mode 100644 jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/HTTPServerFactoryTest.java create mode 100644 jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/authenticator/CustomAuthenticator451.java create mode 100644 jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/authenticator/CustomAuthenticatorWithSubject.java diff --git a/docs/README.md b/docs/README.md index 464553fa..59b9701f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -251,6 +251,28 @@ httpServer: --- +### Java agent use of a custom com.sun.net.httpserver.Authenticator class +It is possible to provide a custom Authenticator implementation for the Java agent. +The custom class needs to be on the jvm classpath. +The class name to load is provided through `authentication/customAuthenticator` configuration as follows: + +```yaml +httpServer: + authentication: + customAuthenticator: + authenticatorClass: my.custom.AuthenticatorWithNoArgConstructor +``` +If the custom authenticatorClass needs to provide an authenticated Subject visible to the application, it can set a named attribute on the HttpExchange with that subject. The agent will arrange that subsequent calls occur in a Subject.doAs(). +The name of the attributed must be provided through `subjectAttributeName` configuration as follows: +```yaml +httpServer: + authentication: + customAuthenticator: + authenticatorClass: my.custom.AuthenticatorWithNoArgConstructorThatSetsASubjectAttribute + subjectAttributeName: "custom.subject.for.doAs"); +``` + + ## HTTPS support (optional) HTTPS support can be configured using either a JKS or PKCS12 format keystore via two possible methods: diff --git a/integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest.java b/integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest.java new file mode 100644 index 00000000..ce62a7d6 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/java/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 The Prometheus jmx_exporter Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.prometheus.jmx.test.http.authentication; + +import io.prometheus.jmx.test.common.AbstractExporterTest; +import io.prometheus.jmx.test.common.ExporterTestEnvironment; +import io.prometheus.jmx.test.support.JmxExporterMode; +import java.util.stream.Stream; +import org.antublue.verifyica.api.Verifyica; + +public class AuthenticatorClassTest extends BasicAuthenticationPlaintextTest { + + @Verifyica.ArgumentSupplier + public static Stream arguments() { + // custom authenticator class is only on the agent classpath + return AbstractExporterTest.arguments() + .filter( + exporterTestEnvironment -> + exporterTestEnvironment + .getName() + .contains(JmxExporterMode.JavaAgent.name())); + } +} diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest/JavaAgent/application.sh b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest/JavaAgent/application.sh new file mode 100644 index 00000000..9e5717d6 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest/JavaAgent/application.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +java \ + -Xmx512M \ + -javaagent:jmx_prometheus_javaagent.jar=8888:exporter.yaml \ + -jar jmx_example_application.jar \ No newline at end of file diff --git a/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest/JavaAgent/exporter.yaml b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest/JavaAgent/exporter.yaml new file mode 100644 index 00000000..91097999 --- /dev/null +++ b/integration_test_suite/integration_tests/src/test/resources/io/prometheus/jmx/test/http/authentication/AuthenticatorClassTest/JavaAgent/exporter.yaml @@ -0,0 +1,7 @@ +httpServer: + authentication: + customAuthenticator: + authenticatorClass: io.prometheus.jmx.CustomAuthenticator + subjectAttributeName: io.prometheus.jmx.CustomAuthenticatorSubjectAttribute +rules: + - pattern: ".*" \ No newline at end of file diff --git a/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/CustomAuthenticator.java b/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/CustomAuthenticator.java new file mode 100644 index 00000000..22a5d5f8 --- /dev/null +++ b/integration_test_suite/jmx_example_application/src/main/java/io/prometheus/jmx/CustomAuthenticator.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prometheus.jmx; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; +import com.sun.security.auth.UserPrincipal; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import javax.security.auth.Subject; + +public class CustomAuthenticator extends Authenticator { + + @Override + public Result authenticate(HttpExchange exch) { + // nothing too custom, so the test works, just to demonstrate that it is plug-able + Headers rmap = exch.getRequestHeaders(); + String auth = rmap.getFirst("Authorization"); + if (auth == null) { + return new Authenticator.Retry(401); + } + int sp = auth.indexOf(' '); + if (sp == -1 || !auth.substring(0, sp).equals("Basic")) { + return new Authenticator.Failure(401); + } + byte[] b = Base64.getDecoder().decode(auth.substring(sp + 1)); + String userpass = new String(b, StandardCharsets.UTF_8); + int colon = userpass.indexOf(':'); + String uname = userpass.substring(0, colon); + String pass = userpass.substring(colon + 1); + + if ("Prometheus".equals(uname) && "secret".equals(pass)) { + Subject subject = new Subject(); + subject.getPrincipals().add(new UserPrincipal(uname)); + // to communicate an authenticated subject for subsequent handler calls via Subject.doAs + exch.setAttribute("io.prometheus.jmx.CustomAuthenticatorSubjectAttribute", subject); + return new Authenticator.Success(new HttpPrincipal(uname, "/")); + } else { + return new Authenticator.Failure(401); + } + } +} diff --git a/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/HTTPServerFactory.java b/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/HTTPServerFactory.java index db967d07..592565db 100644 --- a/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/HTTPServerFactory.java +++ b/jmx_prometheus_common/src/main/java/io/prometheus/jmx/common/http/HTTPServerFactory.java @@ -39,6 +39,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionHandler; @@ -253,131 +254,230 @@ private void configureThreads(HTTPServer.Builder httpServerBuilder) { * @param httpServerBuilder httpServerBuilder */ private void configureAuthentication(HTTPServer.Builder httpServerBuilder) { + + Authenticator authenticator; + if (rootYamlMapAccessor.containsPath("/httpServer/authentication")) { - YamlMapAccessor httpServerAuthenticationBasicYamlMapAccessor = - rootYamlMapAccessor - .get("/httpServer/authentication/basic") - .map( - new ConvertToMapAccessor( - ConfigurationException.supplier( - "Invalid configuration for" - + " /httpServer/authentication/basic"))) - .orElseThrow( - ConfigurationException.supplier( - "/httpServer/authentication/basic configuration values" - + " are required")); - String username = - httpServerAuthenticationBasicYamlMapAccessor - .get("/username") - .map( - new ConvertToString( - ConfigurationException.supplier( - "Invalid configuration for" - + " /httpServer/authentication/basic/username" - + " must be a string"))) - .map( - new ValidateStringIsNotBlank( - ConfigurationException.supplier( - "Invalid configuration for" - + " /httpServer/authentication/basic/username" - + " must not be blank"))) - .orElseThrow( - ConfigurationException.supplier( - "/httpServer/authentication/basic/username is a" - + " required string")); + Optional authenticatorClassAttribute = + rootYamlMapAccessor.get("/httpServer/authentication/customAuthenticator"); + if (authenticatorClassAttribute.isPresent()) { - String algorithm = - httpServerAuthenticationBasicYamlMapAccessor - .get("/algorithm") - .map( - new ConvertToString( - ConfigurationException.supplier( - "Invalid configuration for" - + " /httpServer/authentication/basic/algorithm" - + " must be a string"))) - .map( - new ValidateStringIsNotBlank( - ConfigurationException.supplier( - "Invalid configuration for" - + " /httpServer/authentication/basic/algorithm" - + " must not be blank"))) - .orElse(PLAINTEXT); + YamlMapAccessor httpServerAuthenticationCustomAuthenticatorYamlMapAccessor = + rootYamlMapAccessor + .get("/httpServer/authentication/customAuthenticator") + .map( + new ConvertToMapAccessor( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/customAuthenticator"))) + .orElseThrow( + ConfigurationException.supplier( + "/httpServer/authentication/customAuthenticator" + + " configuration values are required")); - Authenticator authenticator; + String authenticatorClass = + httpServerAuthenticationCustomAuthenticatorYamlMapAccessor + .get("/authenticatorClass") + .map( + new ConvertToString( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/customAuthenticator/authenticatorClass," + + " it must be a string"))) + .map( + new ValidateStringIsNotBlank( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/customAuthenticator/authenticatorClass," + + " it must not be blank"))) + .orElseThrow( + ConfigurationException.supplier( + "/httpServer/authentication/customAuthenticator/authenticatorClass" + + " is a required string")); + + Optional subjectAttribute = + httpServerAuthenticationCustomAuthenticatorYamlMapAccessor.get( + "/subjectAttributeName"); + if (subjectAttribute.isPresent()) { + + String subjectAttributeName = + subjectAttribute + .map( + new ConvertToString( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/customAuthenticator/subjectAttributeName" + + " must be a string"))) + .map( + new ValidateStringIsNotBlank( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/customAuthenticator/subjectAttributeName" + + " must not be blank"))) + .get(); + + // need subject.doAs for subsequent handlers + httpServerBuilder.authenticatedSubjectAttributeName(subjectAttributeName); + } + + authenticator = loadAuthenticator(authenticatorClass); + } else { + + YamlMapAccessor httpServerAuthenticationBasicYamlMapAccessor = + rootYamlMapAccessor + .get("/httpServer/authentication/basic") + .map( + new ConvertToMapAccessor( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/basic"))) + .orElseThrow( + ConfigurationException.supplier( + "/httpServer/authentication/basic configuration" + + " values are required")); - if (PLAINTEXT.equalsIgnoreCase(algorithm)) { - String password = + String username = httpServerAuthenticationBasicYamlMapAccessor - .get("/password") + .get("/username") .map( new ConvertToString( ConfigurationException.supplier( "Invalid configuration for" - + " /httpServer/authentication/basic/password" + + " /httpServer/authentication/basic/username" + " must be a string"))) .map( new ValidateStringIsNotBlank( ConfigurationException.supplier( "Invalid configuration for" - + " /httpServer/authentication/basic/password" + + " /httpServer/authentication/basic/username" + " must not be blank"))) .orElseThrow( ConfigurationException.supplier( - "/httpServer/authentication/basic/password is a" + "/httpServer/authentication/basic/username is a" + " required string")); - authenticator = new PlaintextAuthenticator("/", username, password); - } else if (SHA_ALGORITHMS.contains(algorithm) - || PBKDF2_ALGORITHMS.contains(algorithm)) { - String hash = + String algorithm = httpServerAuthenticationBasicYamlMapAccessor - .get("/passwordHash") + .get("/algorithm") .map( new ConvertToString( ConfigurationException.supplier( "Invalid configuration for" - + " /httpServer/authentication/basic/passwordHash" + + " /httpServer/authentication/basic/algorithm" + " must be a string"))) .map( new ValidateStringIsNotBlank( ConfigurationException.supplier( "Invalid configuration for" - + " /httpServer/authentication/basic/passwordHash" + + " /httpServer/authentication/basic/algorithm" + " must not be blank"))) - .orElseThrow( - ConfigurationException.supplier( - "/httpServer/authentication/basic/passwordHash is a" - + " required string")); - - if (SHA_ALGORITHMS.contains(algorithm)) { - authenticator = - createMessageDigestAuthenticator( - httpServerAuthenticationBasicYamlMapAccessor, - REALM, - username, - hash, - algorithm); + .orElse(PLAINTEXT); + + if (PLAINTEXT.equalsIgnoreCase(algorithm)) { + String password = + httpServerAuthenticationBasicYamlMapAccessor + .get("/password") + .map( + new ConvertToString( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/basic/password" + + " must be a string"))) + .map( + new ValidateStringIsNotBlank( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/basic/password" + + " must not be blank"))) + .orElseThrow( + ConfigurationException.supplier( + "/httpServer/authentication/basic/password is a" + + " required string")); + + authenticator = new PlaintextAuthenticator("/", username, password); + } else if (SHA_ALGORITHMS.contains(algorithm) + || PBKDF2_ALGORITHMS.contains(algorithm)) { + String hash = + httpServerAuthenticationBasicYamlMapAccessor + .get("/passwordHash") + .map( + new ConvertToString( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/basic/passwordHash" + + " must be a string"))) + .map( + new ValidateStringIsNotBlank( + ConfigurationException.supplier( + "Invalid configuration for" + + " /httpServer/authentication/basic/passwordHash" + + " must not be blank"))) + .orElseThrow( + ConfigurationException.supplier( + "/httpServer/authentication/basic/passwordHash" + + " is a required string")); + + if (SHA_ALGORITHMS.contains(algorithm)) { + authenticator = + createMessageDigestAuthenticator( + httpServerAuthenticationBasicYamlMapAccessor, + REALM, + username, + hash, + algorithm); + } else { + authenticator = + createPBKDF2Authenticator( + httpServerAuthenticationBasicYamlMapAccessor, + REALM, + username, + hash, + algorithm); + } } else { - authenticator = - createPBKDF2Authenticator( - httpServerAuthenticationBasicYamlMapAccessor, - REALM, - username, - hash, - algorithm); + throw new ConfigurationException( + String.format( + "Unsupported /httpServer/authentication/basic/algorithm [%s]", + algorithm)); } - } else { - throw new ConfigurationException( - String.format( - "Unsupported /httpServer/authentication/basic/algorithm [%s]", - algorithm)); } httpServerBuilder.authenticator(authenticator); } } + private Authenticator loadAuthenticator(String className) { + + Class clazz; + try { + clazz = this.getClass().getClassLoader().loadClass(className); + } catch (ClassNotFoundException e) { + throw new ConfigurationException( + String.format( + "configured /httpServer/authentication/authenticatorClass [%s]" + + " not found, loadClass resulted in [%s:%s]", + className, e.getClass(), e.getMessage())); + } + if (!Authenticator.class.isAssignableFrom(clazz)) { + throw new ConfigurationException( + String.format( + "configured /httpServer/authentication/authenticatorClass [%s]" + + " loadClass resulted in [%s] of the wrong type, is not assignable" + + " from Authenticator", + className, clazz.getCanonicalName())); + } + try { + return (Authenticator) clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new ConfigurationException( + String.format( + "configured /httpServer/authentication/authenticatorClass [%s] no arg" + + " constructor newInstance resulted in exception [%s:%s]", + className, e.getClass(), e.getMessage())); + } + } + /** * Method to create a MessageDigestAuthenticator * diff --git a/jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/HTTPServerFactoryTest.java b/jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/HTTPServerFactoryTest.java new file mode 100644 index 00000000..f73b9926 --- /dev/null +++ b/jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/HTTPServerFactoryTest.java @@ -0,0 +1,184 @@ +package io.prometheus.jmx.common.http; + +import static org.junit.Assert.assertTrue; + +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import io.prometheus.metrics.model.registry.PrometheusRegistry; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class HTTPServerFactoryTest { + + @Rule + public TemporaryFolder temporaryFolder = TemporaryFolder.builder().assureDeletion().build(); + + HTTPServer httpServer; + + @After + public void stopServer() { + if (httpServer != null) { + httpServer.stop(); + } + } + + @Test + public void createHTTPServerWithCustomAuthenticatorClass451RoundTrip() throws Exception { + + File config = temporaryFolder.newFile("ok"); + PrintWriter writer = new PrintWriter(config); + writer.println("httpServer:"); + writer.println(" authentication:"); + writer.println(" customAuthenticator:"); + writer.println( + " authenticatorClass:" + + " io.prometheus.jmx.common.http.authenticator.CustomAuthenticator451"); + writer.close(); + + httpServer = startServer(config); + + verifyExpectedResponse(httpServer, "HTTP/1.1 451"); + } + + @Test + public void createHTTPServerWithCustomAuthenticatorClassSubjectOkRoundTrip() throws Exception { + + File config = temporaryFolder.newFile("ok"); + PrintWriter writer = new PrintWriter(config); + writer.println("httpServer:"); + writer.println(" authentication:"); + writer.println(" customAuthenticator:"); + writer.println( + " authenticatorClass:" + + " io.prometheus.jmx.common.http.authenticator.CustomAuthenticatorWithSubject"); + writer.println(" subjectAttributeName: custom.subject"); + + writer.close(); + + httpServer = startServer(config); + + verifyExpectedResponse(httpServer, "HTTP/1.1 200 OK"); + } + + @Test + public void createHTTPServerWithCustomAuthenticatorClassSubjectNotMatchingRoundTrip() + throws Exception { + + File config = temporaryFolder.newFile("unmatched_subjectAttributeName"); + PrintWriter writer = new PrintWriter(config); + writer.println("httpServer:"); + writer.println(" authentication:"); + writer.println(" customAuthenticator:"); + writer.println( + " authenticatorClass:" + + " io.prometheus.jmx.common.http.authenticator.CustomAuthenticatorWithSubject"); + writer.println(" subjectAttributeName: not.the.correct.custom.subject.attribute"); + + writer.close(); + + httpServer = startServer(config); + + verifyExpectedResponse(httpServer, "HTTP/1.1 403"); + } + + private void verifyExpectedResponse(HTTPServer httpServer, String expectedResponseSubString) + throws Exception { + Socket socket = new Socket(); + try { + socket.setSoTimeout(1000); + socket.connect(new InetSocketAddress("localhost", httpServer.getPort())); + socket.getOutputStream() + .write("GET /metrics HTTP/1.1 \r\n".getBytes(StandardCharsets.UTF_8)); + socket.getOutputStream() + .write("HOST: localhost \r\n\r\n".getBytes(StandardCharsets.UTF_8)); + socket.getOutputStream().flush(); + + String actualResponse = ""; + byte[] resp = new byte[500]; + int read = socket.getInputStream().read(resp, 0, resp.length); + if (read > 0) { + actualResponse = new String(resp, 0, read); + } + assertTrue(actualResponse.contains(expectedResponseSubString)); + } finally { + socket.close(); + } + } + + @Test(expected = ConfigurationException.class) + public void createHTTPServerWithCustomAuthenticatorClassNOkNoConstructor() throws Exception { + + File config = temporaryFolder.newFile("error_no_constructor"); + PrintWriter writer = new PrintWriter(config); + writer.println("httpServer:"); + writer.println(" authentication:"); + writer.println(" customAuthenticator:"); + writer.println( + " authenticatorClass:" + + " io.prometheus.jmx.common.http.authenticator.PlaintextAuthenticator"); + writer.close(); + + httpServer = startServer(config); + } + + @Test(expected = ConfigurationException.class) + public void createHTTPServerWithCustomAuthenticatorClassNokNotFound() throws Exception { + + File config = temporaryFolder.newFile("notFound"); + PrintWriter writer = new PrintWriter(config); + writer.println("httpServer:"); + writer.println(" authentication:"); + writer.println(" customAuthenticator:"); + writer.println( + " authenticatorClass:" + + " myio.jmx.common.notThere.authenticator.PlaintextAuthenticator"); + writer.close(); + + httpServer = startServer(config); + } + + @Test(expected = ConfigurationException.class) + public void createHTTPServerWithCustomAuthenticatorClassNokNotString() throws Exception { + + File config = temporaryFolder.newFile("as_int"); + PrintWriter writer = new PrintWriter(config); + writer.println("httpServer:"); + writer.println(" authentication:"); + writer.println(" customAuthenticator:"); + writer.println(" authenticatorClass: 10"); + writer.close(); + + httpServer = startServer(config); + } + + @Test(expected = ConfigurationException.class) + public void createHTTPServerWithCustomAuthenticatorClassNokMissingString() throws Exception { + + File config = temporaryFolder.newFile("missing"); + PrintWriter writer = new PrintWriter(config); + writer.println("httpServer:"); + writer.println(" authentication:"); + writer.println(" customAuthenticator:"); + writer.println(" authenticatorClass:"); + writer.close(); + + httpServer = startServer(config); + } + + private HTTPServer startServer(File config) throws IOException { + return new HTTPServerFactory() + .createHTTPServer( + InetAddress.getByName("0.0.0.0"), + 0, + PrometheusRegistry.defaultRegistry, + config); + } +} diff --git a/jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/authenticator/CustomAuthenticator451.java b/jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/authenticator/CustomAuthenticator451.java new file mode 100644 index 00000000..10f97d6e --- /dev/null +++ b/jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/authenticator/CustomAuthenticator451.java @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prometheus.jmx.common.http.authenticator; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.HttpExchange; + +public class CustomAuthenticator451 extends Authenticator { + + @Override + public Result authenticate(HttpExchange exch) { + return new Authenticator.Failure(451); + } +} diff --git a/jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/authenticator/CustomAuthenticatorWithSubject.java b/jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/authenticator/CustomAuthenticatorWithSubject.java new file mode 100644 index 00000000..8d0e6d04 --- /dev/null +++ b/jmx_prometheus_common/src/test/java/io/prometheus/jmx/common/http/authenticator/CustomAuthenticatorWithSubject.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + *

http://www.apache.org/licenses/LICENSE-2.0 + * + *

Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.prometheus.jmx.common.http.authenticator; + +import com.sun.net.httpserver.Authenticator; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpPrincipal; +import javax.security.auth.Subject; + +public class CustomAuthenticatorWithSubject extends Authenticator { + final String authenticatedUser = "guest"; + + @Override + public Result authenticate(HttpExchange exch) { + Subject subject = new Subject(); + subject.getPrincipals().add(() -> authenticatedUser); + exch.setAttribute("custom.subject", subject); + return new Success(new HttpPrincipal(authenticatedUser, "/")); + } +} diff --git a/pom.xml b/pom.xml index 21f31182..3d961785 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ UTF-8 - 1.3.1 + 1.4.0-SNAPSHOT