Skip to content

Commit

Permalink
Add management.port restriction to known issues (#621)
Browse files Browse the repository at this point in the history
* Add management.port restriction to known issues

As found in #616, servlet based servers cannot change the management port.

This PR documents this.

* Add pending test for Jetty, Undertow and Tomcat
  • Loading branch information
timyates authored Jan 15, 2024
1 parent 2a8dc88 commit ac8ec99
Show file tree
Hide file tree
Showing 4 changed files with 374 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package io.micronaut.servlet.jetty

import io.micronaut.context.ApplicationContext
import io.micronaut.context.annotation.Requires
import io.micronaut.core.io.socket.SocketUtils
import io.micronaut.core.util.StringUtils
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.BlockingHttpClient
import io.micronaut.http.client.HttpClient
import io.micronaut.runtime.server.EmbeddedServer
import io.netty.handler.ssl.util.SelfSignedCertificate
import spock.lang.Issue
import spock.lang.PendingFeature
import spock.lang.Specification

import java.nio.file.Files
import java.nio.file.Path
import java.security.KeyStore
import java.security.cert.Certificate

@Issue("https://github.com/micronaut-projects/micronaut-servlet/issues/616")
class JettyManagementPortSpec extends Specification {

static final Path keyStorePath = Files.createTempFile("micronaut-test-key-store", "pkcs12")
static final Path trustStorePath = Files.createTempFile("micronaut-test-trust-store", "jks")

def setupSpec() {
def certificate = new SelfSignedCertificate()

KeyStore ks = KeyStore.getInstance("PKCS12")
ks.load(null, null)
ks.setKeyEntry("key", certificate.key(), "".toCharArray(), new Certificate[]{certificate.cert()})
try (OutputStream os = Files.newOutputStream(keyStorePath)) {
ks.store(os, "".toCharArray())
}

KeyStore ts = KeyStore.getInstance("JKS")
ts.load(null, null)
ts.setCertificateEntry("cert", certificate.cert())
try (OutputStream os = Files.newOutputStream(trustStorePath)) {
ts.store(os, "123456".toCharArray())
}
}

def sslConfig() {
[
"micronaut.server.jetty.ssl.sni-host-check": StringUtils.FALSE,
'micronaut.ssl.enabled' : StringUtils.TRUE,
// Cannot be true!
'micronaut.server.ssl.build-self-signed' : false,
'micronaut.ssl.clientAuthentication' : "need",
'micronaut.ssl.key-store.path' : "file://${keyStorePath.toString()}",
'micronaut.ssl.key-store.type' : 'PKCS12',
'micronaut.ssl.key-store.password' : '',
'micronaut.ssl.trust-store.path' : "file://${trustStorePath.toString()}",
'micronaut.ssl.trust-store.type' : 'JKS',
'micronaut.ssl.trust-store.password' : '123456',
]
}

@PendingFeature
def 'management port can be configured different to main port'() {
given:
def port = SocketUtils.findAvailableTcpPort()
EmbeddedServer server = ApplicationContext.run(EmbeddedServer, [
'spec.name' : 'JettyManagementPortSpec',
'endpoints.all.enabled': true,
'endpoints.all.port' : port,
])
BlockingHttpClient mainClient = server.getApplicationContext().createBean(HttpClient, URI.create("http://localhost:$server.port/")).toBlocking()
BlockingHttpClient managementClient = server.getApplicationContext().createBean(HttpClient, URI.create("http://localhost:$port/")).toBlocking()

when:
def mainResponse = mainClient.exchange('/management-port', String)
def healthResponse = mainClient.exchange('/health', String)

then:
mainResponse.body() == 'Hello world'
healthResponse.body() == '{"status":"UP"}'

cleanup:
mainClient.close()
managementClient.close()
server.stop()
}

@PendingFeature
def 'management port can be configured different to main port and uses ssl if also configured'() {
given:
def port = SocketUtils.findAvailableTcpPort()
EmbeddedServer server = ApplicationContext.run(EmbeddedServer, [
'spec.name' : 'JettyManagementPortSpec',
'endpoints.all.enabled': true,
'endpoints.all.port' : port,
] + sslConfig())
BlockingHttpClient mainClient = server.getApplicationContext().createBean(HttpClient, URI.create("https://localhost:$server.port/")).toBlocking()
BlockingHttpClient managementClient = server.getApplicationContext().createBean(HttpClient, URI.create("https://localhost:$port/")).toBlocking()

when:
def mainResponse = mainClient.exchange('/management-port', String)
def healthResponse = mainClient.exchange('/health', String)

then:
mainResponse.body() == 'Hello world'
healthResponse.body() == '{"status":"UP"}'

cleanup:
mainClient.close()
managementClient.close()
server.stop()
}

@Controller("/management-port")
@Requires(property = "spec.name", value = "JettyManagementPortSpec")
static class TestController {

@Get
String get() {
"Hello world"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package io.micronaut.servlet.tomcat

import io.micronaut.context.ApplicationContext
import io.micronaut.context.annotation.Requires
import io.micronaut.core.io.socket.SocketUtils
import io.micronaut.core.util.StringUtils
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.BlockingHttpClient
import io.micronaut.http.client.HttpClient
import io.micronaut.runtime.server.EmbeddedServer
import io.netty.handler.ssl.util.SelfSignedCertificate
import spock.lang.Issue
import spock.lang.PendingFeature
import spock.lang.Specification

import java.nio.file.Files
import java.nio.file.Path
import java.security.KeyStore
import java.security.cert.Certificate

@Issue("https://github.com/micronaut-projects/micronaut-servlet/issues/616")
class TomcatManagementPortSpec extends Specification {

static final Path keyStorePath = Files.createTempFile("micronaut-test-key-store", "pkcs12")
static final Path trustStorePath = Files.createTempFile("micronaut-test-trust-store", "jks")

def setupSpec() {
def certificate = new SelfSignedCertificate()

KeyStore ks = KeyStore.getInstance("PKCS12")
ks.load(null, null)
ks.setKeyEntry("key", certificate.key(), "".toCharArray(), new Certificate[]{certificate.cert()})
try (OutputStream os = Files.newOutputStream(keyStorePath)) {
ks.store(os, "".toCharArray())
}

KeyStore ts = KeyStore.getInstance("JKS")
ts.load(null, null)
ts.setCertificateEntry("cert", certificate.cert())
try (OutputStream os = Files.newOutputStream(trustStorePath)) {
ts.store(os, "123456".toCharArray())
}
}

def sslConfig() {
[
"micronaut.server.jetty.ssl.sni-host-check": StringUtils.FALSE,
'micronaut.ssl.enabled' : StringUtils.TRUE,
// Cannot be true!
'micronaut.server.ssl.build-self-signed' : false,
'micronaut.ssl.clientAuthentication' : "need",
'micronaut.ssl.key-store.path' : "file://${keyStorePath.toString()}",
'micronaut.ssl.key-store.type' : 'PKCS12',
'micronaut.ssl.key-store.password' : '',
'micronaut.ssl.trust-store.path' : "file://${trustStorePath.toString()}",
'micronaut.ssl.trust-store.type' : 'JKS',
'micronaut.ssl.trust-store.password' : '123456',
]
}

@PendingFeature
def 'management port can be configured different to main port'() {
given:
def port = SocketUtils.findAvailableTcpPort()
EmbeddedServer server = ApplicationContext.run(EmbeddedServer, [
'spec.name' : 'JettyManagementPortSpec',
'endpoints.all.enabled': true,
'endpoints.all.port' : port,
])
BlockingHttpClient mainClient = server.getApplicationContext().createBean(HttpClient, URI.create("http://localhost:$server.port/")).toBlocking()
BlockingHttpClient managementClient = server.getApplicationContext().createBean(HttpClient, URI.create("http://localhost:$port/")).toBlocking()

when:
def mainResponse = mainClient.exchange('/management-port', String)
def healthResponse = mainClient.exchange('/health', String)

then:
mainResponse.body() == 'Hello world'
healthResponse.body() == '{"status":"UP"}'

cleanup:
mainClient.close()
managementClient.close()
server.stop()
}

@PendingFeature
def 'management port can be configured different to main port and uses ssl if also configured'() {
given:
def port = SocketUtils.findAvailableTcpPort()
EmbeddedServer server = ApplicationContext.run(EmbeddedServer, [
'spec.name' : 'JettyManagementPortSpec',
'endpoints.all.enabled': true,
'endpoints.all.port' : port,
] + sslConfig())
BlockingHttpClient mainClient = server.getApplicationContext().createBean(HttpClient, URI.create("https://localhost:$server.port/")).toBlocking()
BlockingHttpClient managementClient = server.getApplicationContext().createBean(HttpClient, URI.create("https://localhost:$port/")).toBlocking()

when:
def mainResponse = mainClient.exchange('/management-port', String)
def healthResponse = mainClient.exchange('/health', String)

then:
mainResponse.body() == 'Hello world'
healthResponse.body() == '{"status":"UP"}'

cleanup:
mainClient.close()
managementClient.close()
server.stop()
}

@Controller("/management-port")
@Requires(property = "spec.name", value = "JettyManagementPortSpec")
static class TestController {

@Get
String get() {
"Hello world"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package io.micronaut.servlet.undertow

import io.micronaut.context.ApplicationContext
import io.micronaut.context.annotation.Requires
import io.micronaut.core.io.socket.SocketUtils
import io.micronaut.core.util.StringUtils
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.BlockingHttpClient
import io.micronaut.http.client.HttpClient
import io.micronaut.runtime.server.EmbeddedServer
import io.netty.handler.ssl.util.SelfSignedCertificate
import spock.lang.Issue
import spock.lang.PendingFeature
import spock.lang.Specification

import java.nio.file.Files
import java.nio.file.Path
import java.security.KeyStore
import java.security.cert.Certificate

@Issue("https://github.com/micronaut-projects/micronaut-servlet/issues/616")
class UndertowManagementPortSpec extends Specification {

static final Path keyStorePath = Files.createTempFile("micronaut-test-key-store", "pkcs12")
static final Path trustStorePath = Files.createTempFile("micronaut-test-trust-store", "jks")

def setupSpec() {
def certificate = new SelfSignedCertificate()

KeyStore ks = KeyStore.getInstance("PKCS12")
ks.load(null, null)
ks.setKeyEntry("key", certificate.key(), "".toCharArray(), new Certificate[]{certificate.cert()})
try (OutputStream os = Files.newOutputStream(keyStorePath)) {
ks.store(os, "".toCharArray())
}

KeyStore ts = KeyStore.getInstance("JKS")
ts.load(null, null)
ts.setCertificateEntry("cert", certificate.cert())
try (OutputStream os = Files.newOutputStream(trustStorePath)) {
ts.store(os, "123456".toCharArray())
}
}

def sslConfig() {
[
"micronaut.server.jetty.ssl.sni-host-check": StringUtils.FALSE,
'micronaut.ssl.enabled' : StringUtils.TRUE,
// Cannot be true!
'micronaut.server.ssl.build-self-signed' : false,
'micronaut.ssl.clientAuthentication' : "need",
'micronaut.ssl.key-store.path' : "file://${keyStorePath.toString()}",
'micronaut.ssl.key-store.type' : 'PKCS12',
'micronaut.ssl.key-store.password' : '',
'micronaut.ssl.trust-store.path' : "file://${trustStorePath.toString()}",
'micronaut.ssl.trust-store.type' : 'JKS',
'micronaut.ssl.trust-store.password' : '123456',
]
}

@PendingFeature
def 'management port can be configured different to main port'() {
given:
def port = SocketUtils.findAvailableTcpPort()
EmbeddedServer server = ApplicationContext.run(EmbeddedServer, [
'spec.name' : 'JettyManagementPortSpec',
'endpoints.all.enabled': true,
'endpoints.all.port' : port,
])
BlockingHttpClient mainClient = server.getApplicationContext().createBean(HttpClient, URI.create("http://localhost:$server.port/")).toBlocking()
BlockingHttpClient managementClient = server.getApplicationContext().createBean(HttpClient, URI.create("http://localhost:$port/")).toBlocking()

when:
def mainResponse = mainClient.exchange('/management-port', String)
def healthResponse = mainClient.exchange('/health', String)

then:
mainResponse.body() == 'Hello world'
healthResponse.body() == '{"status":"UP"}'

cleanup:
mainClient.close()
managementClient.close()
server.stop()
}

@PendingFeature
def 'management port can be configured different to main port and uses ssl if also configured'() {
given:
def port = SocketUtils.findAvailableTcpPort()
EmbeddedServer server = ApplicationContext.run(EmbeddedServer, [
'spec.name' : 'JettyManagementPortSpec',
'endpoints.all.enabled': true,
'endpoints.all.port' : port,
] + sslConfig())
BlockingHttpClient mainClient = server.getApplicationContext().createBean(HttpClient, URI.create("https://localhost:$server.port/")).toBlocking()
BlockingHttpClient managementClient = server.getApplicationContext().createBean(HttpClient, URI.create("https://localhost:$port/")).toBlocking()

when:
def mainResponse = mainClient.exchange('/management-port', String)
def healthResponse = mainClient.exchange('/health', String)

then:
mainResponse.body() == 'Hello world'
healthResponse.body() == '{"status":"UP"}'

cleanup:
mainClient.close()
managementClient.close()
server.stop()
}

@Controller("/management-port")
@Requires(property = "spec.name", value = "JettyManagementPortSpec")
static class TestController {

@Get
String get() {
"Hello world"
}
}
}
5 changes: 5 additions & 0 deletions src/main/docs/guide/knownIssues.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ It is not currently possible to use the HttpProxyClient with Servlet Filters.

Local error handlers that require the request body to be reparsed will not work in Servlet based applications.
The body is read from the request input-stream and so attempting to reparse it for the error handler will fail.

=== Management port

With a Netty based server, you can https://docs.micronaut.io/latest/guide/#_management_port[configure a management port for the server].
This is not currently supported with Servlet based servers, and management endpoints (when enabled) will be available on the same port as the main application.

0 comments on commit ac8ec99

Please sign in to comment.