ProxyServlet stop working after migration from jetty 8 to jetty 9 - eclipse-plugin

I have an eclipse plugin which uses jetty server with ProxyServlet. Basically, the implementation is the following:
ServletHolder proxyServletHolder = new ServletHolder(new SubClassOfProxyServlet());
proxyServletHolder.setAsyncSupported(true);
ServletHandler proxyServletHandler = new ServletHandler();
proxyServletHandler.addServletWithMapping(proxyServletHolder, "/mapping/url");
After that I add proxy handler to the handler list and set this list to the server:
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] {
. // Other Handlers
.
proxyServletHandler,
.
.
.
new DefaultHandler()
});
server.setHandler(handlers);
Everything worked like a charm against jetty 8 but after migration to jetty 9 I get the following error:
Caused by: java.lang.IllegalStateException: No server executor for proxy
at org.eclipse.jetty.proxy.ProxyServlet.createHttpClient(ProxyServlet.java:279)
at org.eclipse.jetty.proxy.ProxyServlet.init(ProxyServlet.java:123)
... 24 more
Has the mechanism of working with ProxyServer changed? Am I missing something?

You need to update your SubClassOfProxyServlet class to include the various configurations that are now being passed from the Server to the Proxy which are then in turn used by the internal HttpClient
The particular error means you are not passing along the Executor properly.
You have 2 choices for the Executor specific piece (there might be more things for you to configure after this is addressed)
Set the init-parameter maxThreads to a valid integer value.
or Create an Executor, and set it in the servlet context attributes at ServletContext.setAttribute("org.eclipse.jetty.server.Executor", myExecutor) on application deployment / startup. - You could probably do this as well in your SubClassOfProxyServlet.init(ServletConfig config) method.

I was able to get it working via the maxThreads method mentioned above, setting it on creation. Applying this to the original example would result in this:
ServletHolder proxyServletHolder = new ServletHolder(new SubClassOfProxyServlet());
proxyServletHolder.setAsyncSupported(true);
proxyServletHolder.setInitParameter("maxThreads", "2");
ServletHandler proxyServletHandler = new ServletHandler();
proxyServletHandler.addServletWithMapping(proxyServletHolder, "/mapping/url");

Here is an example of how you can add a servlet to a list of handlers:
private void addWebApp(String contextPath, String resourceBase, Server server) {
WebAppContext webAppContext = new WebAppContext();
// webAppContext.setDescriptor(webapp + "/WEB-INF/web.xml");
webAppContext.setResourceBase(resourceBase);
webAppContext.setContextPath(contextPath);
webAppContext.setParentLoaderPriority(true);
webAppContext.setWelcomeFiles(new String[] {"index.html"});
webAppContext.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
webAppContext.setInitParameter("org.eclipse.jetty.servlet.Default.useFileMappedBuffer", "false");
final ServletHolder servletHolder =new ServletHolder();
servletHolder.setAsyncSupported(ContentBasedProxyServlet.class);
servletHolder.setAsyncSupported(true);
webAppContext.addServlet(servletHolder, "/*");
HandlerList handlers = (HandlerList) server.getHandler();
handlers.addHandler(webAppContext);
}

In addition you can put maxThreads to web.xml as well:
<servlet>
<servlet-name>proxy</servlet-name>
<servlet-class>example.MyProxyServlet</servlet-class>
<init-param>
<param-name>maxThreads</param-name>
<param-value>5</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>

Related

Mutual Authentication with Reactive Netty on restricted urls

I am using spring cloud gateway to front a legacy application so that we can start migrating things behind the scenes. Some of the urls that are hosted by the application are public facing and some are device restricted. We control the devices and they use a browser client to access the restricted urls. We have mutual authentication setup for the device restricted urls on the server using tomcat and security constraints like this in web.xml:
<security-constraint>
<web-resource-collection>
<web-resource-name>Certificate Content</web-resource-name>
<!-- URL for authentication endpoint - this is locked down with the role assigned by tomcat -->
<url-pattern>/rest/secure/url1</url-pattern>
<url-pattern>/rest/secure/url2</url-pattern>
<url-pattern>/rest/secure/url3</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>certificate</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<!-- All other endpoints- force the switch from http to https with transport-guarantee -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected Context</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>CLIENT-CERT</auth-method>
</login-config>
<security-role>
<role-name>certificate</role-name>
</security-role>
That is coupled with a truststore setup in tomcat's server.xml (I can add it, but I don't think that is relevant to this conversation).
My goal is to implement a similar setup in spring cloud gateway which is using reactive netty under-the-hood and remove the web.xml restrictions from the legacy application. I think I could switch it to using tomcat and probably get the web.xml from above to work, but I'd rather stick to the performance benefits of using reactive netty.
Key Goals:
Only deploy one api gateway for the app. The number of urls that
require mutual auth is very small so I'd rather not include a whole
other container to manage just to support them.
Do not ask for a client cert on the public urls.
Require valid client certs for the restricted urls.
I've setup mutual authentication and can get it to work with need/want/none as expected (truststores setup, etc), but it applies to ALL urls. I've also setup X509 security restrictions and that all seems to work.
I think what I want to setup is tsl renegotiation using the SslHandler after the http request is decrypted (so that I can access the url) based on the path. But I'm having trouble with the details and I've failed at finding any examples that incorporate spring-boot applications using reactive netty to do a tsl renegotiation. Any tips on how to perform a renegotiation of the ssl connection with needClientAuth set to true would be appreciated. I think I need to invalidate the session or something because when I try to do it manually it appears that it is skipping negotiation because the connection is already marked as negotiated in the ssl engine.
This is one of the iterations I've tried (this doesn't restrict on urls, but I plan to add that after I get this working):
#Component
public class NettyWebServerFactoryGatewayCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
private static final Logger LOG = LoggerFactory.getLogger(NettyWebServerFactoryGatewayCustomizer.class);
#Override
public void customize(NettyReactiveWebServerFactory serverFactory) {
serverFactory.addServerCustomizers(httpServer -> {
httpServer = httpServer.wiretap(true);
return httpServer.tcpConfiguration(tcpServer -> {
tcpServer = tcpServer.doOnConnection(connection ->
connection.addHandler("request client cert",
new SimpleChannelInboundHandler<HttpRequest>() {
#Override
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest httpRequest) {
LOG.error("HttpRequest: {}", httpRequest);
final ChannelPipeline pipeline = ctx.pipeline();
final SslHandler sslHandler = pipeline.get(SslHandler.class);
final SSLEngine sslEngine = sslHandler.engine();
sslEngine.setNeedClientAuth(true);
sslHandler.renegotiate()
.addListener(future -> ctx.fireChannelRead(httpRequest));
}
}
)
);
return tcpServer;
});
});
}
}
I see it performing the renegotiation in the debugger, but it still seems to be set to client auth none (as set in the application.properties) instead of need as set in the code before renegotiation. I've tried sslEngine.getSession().invalidate(); but that didn't help. I've also tried generating a new ssl handler from the ssl provider but that seemed to really screw things up.
Thank you for any help provided.
Edit: Doing more research it appears that this approach is not appropriate going forward since ssl renegotiation is being dropped entirely in tsl 1.3 (see https://security.stackexchange.com/a/230327). Is there a way to perform the equivalent of SSL verify client post handshake as described here: https://www.openssl.org/docs/manmaster/man3/SSL_verify_client_post_handshake.html ?
Edit2: Looks like this was an issue where TLS1.3 post handshake was not supported by the browser I was testing with. Setting the server to just accept TLS 1.2 seemed to work. Not sure if there is a better way to solve this but this is what I added to my application.properties:
server.ssl.enabled-protocols=TLSv1.2
Here is what I used to get it to work. I'm going to leave out the spring security side of it since that is separate from requesting the certificate from the client.
There are so many ways to configure the child pipeline that is used to process the request. Please let me know if there is a more accepted way to configure it.
Configure the HttpServer by adding to the bootstrap pipeline that is applied when a connection is established with the client:
#Component
public class NettyWebServerFactoryGatewayCustomizer implements WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
private static final HttpRenegotiateClientCertHandler HTTP_RENEGOTIATE_CLIENT_CERT_HANDLER =
new HttpRenegotiateClientCertHandler(SecurityConfig.X509_PROTECTED_ENDPOINTS);
#Override
public void customize(NettyReactiveWebServerFactory serverFactory) {
serverFactory.addServerCustomizers(NettyWebServerFactoryGatewayCustomizer::addRenegotiateHandlerToHttpServer);
}
private static HttpServer addRenegotiateHandlerToHttpServer(HttpServer httpServer) {
return httpServer.tcpConfiguration(NettyWebServerFactoryGatewayCustomizer::addRenegotiateHandlerToTcpServer);
}
private static TcpServer addRenegotiateHandlerToTcpServer(TcpServer server) {
return server.doOnBind(NettyWebServerFactoryGatewayCustomizer::addRenegotiateHandlerToServerBootstrap);
}
private static void addRenegotiateHandlerToServerBootstrap(ServerBootstrap serverBootstrap) {
BootstrapHandlers.updateConfiguration(
serverBootstrap,
HttpRenegotiateClientCertHandler.NAME,
NettyWebServerFactoryGatewayCustomizer::addRenegotiateHandlerToChannel
);
}
private static void addRenegotiateHandlerToChannel(ConnectionObserver connectionObserver, Channel channel) {
final ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(HttpRenegotiateClientCertHandler.NAME, HTTP_RENEGOTIATE_CLIENT_CERT_HANDLER);
}
}
Child Handler that performs the renegotiation:
#ChannelHandler.Sharable
public class HttpRenegotiateClientCertHandler extends SimpleChannelInboundHandler<HttpRequest> {
public static final String NAME = NettyPipeline.LEFT + "clientRenegotiate";
private static final PathPatternParser DEFAULT_PATTERN_PARSER = new PathPatternParser();
private final Collection<PathPattern> pathPatterns;
public HttpRenegotiateClientCertHandler(String ... antPatterns) {
Assert.notNull(antPatterns, "patterns cannot be null");
Assert.notEmpty(antPatterns, "patterns cannot be empty");
Assert.noNullElements(antPatterns, "patterns cannot have null items");
pathPatterns = Arrays.stream(antPatterns)
.map(DEFAULT_PATTERN_PARSER::parse)
.collect(Collectors.toSet());
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest request) {
if (shouldNotRenegotiate(request)) {
ctx.fireChannelRead(request);
return;
}
final ChannelPipeline pipeline = ctx.pipeline();
final SslHandler sslHandler = pipeline.get(SslHandler.class);
final SSLEngine sslEngine = sslHandler.engine();
sslEngine.setNeedClientAuth(true);
sslHandler.renegotiate()
.addListener(renegotiateFuture -> ctx.fireChannelRead(request));
}
/**
* Determine if the request uri matches the configured uris for this handler.
* #param request to match the path from.
* #return true if any of the path patterns are matched.
*/
private boolean shouldNotRenegotiate(HttpRequest request) {
final String requestUri = request.uri();
final PathContainer path = PathContainer.parsePath(requestUri);
return pathPatterns.stream()
.noneMatch(matcher -> matcher.matches(path));
}
}
And these configurations in application.properties:
# Setup Client Auth Truststore:
server.ssl.trust-store=<path to truststore>
server.ssl.trust-store-password=<truststore password>
server.ssl.trust-store-type=<truststore type>
# Set to none by default so we do not ask for client auth until needed.
server.ssl.client-auth=none
# This is specifically not including TLSv1.3 because there are issues
# with older browsers' implementation of TLSv1.3 that prevent verify
# client post handshake client from working.
server.ssl.enabled-protocols=TLSv1.2
Edit: Updated because handler gateway route code wasn't being invoked properly.

TomEE Embedded: Resource defined in resources.xml not available within webapp

I'm currently trying to run a simple webapp on TomEE Embedded (TomEE Version 7.0.5).
According to the docs, I can start the TomEE and deploy the classpath as a webapp like this. I've set the document base to src/main/webapp.
try (final Container container = new Container(new Configuration())
.deployClasspathAsWebApp("", new File("src/main/webapp"))) {
container.await();
}
I have defined a datasource in WEB-INF/resources.xml which looks like this:
<Resource id="myDataSource" type="javax.sql.DataSource">
JdbcDriver org.hsqldb.jdbcDriver
JdbcUrl jdbc:hsqldb:file:hsqldb
UserName sa
Password
</Resource>
And I've setup a reference in the web.xml:
<resource-ref>
<res-ref-name>myDataSource</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
</resource-ref>
Then I try to lookup this datasource in my Servlet via JNDI.
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
Context initCtx = new InitialContext();
DataSource ds = (DataSource) initCtx.lookup("java:comp/env/myDataSource");
Connection connection = ds.getConnection();
...
}
When the TomEE starts, it seems like my DataSource is created (at least there is some output about that in the logs). However when I try to lookup the DataSource in my servlet, I get an unconfigured dbcp2 connection pool as a DataSource which throws the following exception when ds.getConnection() is called:
java.sql.SQLException: Cannot create JDBC driver of class '' for connect URL 'null'
at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.createConnectionFactory(BasicDataSource.java:2186)
at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.createDataSource(BasicDataSource.java:2066)
at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.getConnection(BasicDataSource.java:1525)
at TestServlet.doGet(TestServlet.java:32)
...
The same configuration works fine on a standalone TomEE (I tried TomEE Webprofile) or when using the TomEE Maven Plugin. Is there anything I'm missing to get it running also for Embedded TomEE?
Thanks in advance
Tomee embedded does not bind a custom webapp classloader by default so does not have comp/ always bound. You can pass properties to the context to force it to be openejb one or use openejb:Resource/myDataSource or java:openejb/Resource/myDataSource naming.

Autofac PerRequest WebApi 2 + Owin

Trying to implement autofac with my WebApi ... but having some issues with lifetime for my objects...
My startup webapi class:
var builder = new ContainerBuilder();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
container.RegisterType<MyConcreteClass>().As<IMyInterface>().InstancePerRequest();
var container = builder.Build();
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
var csl = new AutofacServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => csl);
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
But not works
Unable to resolve the type 'IMyInterface' because the lifetime scope it belongs in can't be located. The following services are exposed by this registration:
- IMyInterface
Details ---> No scope with a tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.
If you see this during execution of a web application, it generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario). Under the web integration always request dependencies from the dependency resolver or the request lifetime scope, never from the container itself. (See inner exception for details.)
Removing this part .InstancePerRequest(); , then works, but the object is not disposing.
What am i doing wrong ?
Thanks!
I strongly suspect the problem lies with the following code:
var csl = new AutofacServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => csl);
The error message indicates you must resolve dependencies using the dependency resolver, but this is bypassing that and using the container itself.
On a side note, using a service locator is anti-pattern. You should be injecting dependencies into your controllers and other MVC extension points rather than using this approach.

Quartz scheduled jobs could not access datasource in Websphere

I am developing a web app where batch programs need to run for specific times. I used Quartz library to schedule the jobs. The web app is deployed on Websphere 8.5.5 and its working fine, accessing the tables through datasources (Datasource given in code is java:comp/env/jdbc/db_datasource). The job is also triggered at the mentioned times.
I am getting an error when the scheduled job makes a DB connection through the datasource and the error is:
javax.naming.ConfigurationException: A JNDI operation on a "java:" name cannot be completed because the server runtime is not able to associate the operation's thread with any J2EE application component. This condition can occur when the JNDI client using the "java:" name is not executed on the thread of a server application request. Make sure that a J2EE application does not execute JNDI operations on "java:" names within static code blocks or in threads created by that J2EE application. Such code does not necessarily run on the thread of a server application request and therefore is not supported by JNDI operations on "java:" names. [Root exception is javax.naming.NameNotFoundException: Name comp/env/jdbc not found in context "java:".]
at com.ibm.ws.naming.java.javaURLContextImpl.throwExceptionIfDefaultJavaNS(javaURLContextImpl.java:522)
at com.ibm.ws.naming.java.javaURLContextImpl.throwConfigurationExceptionWithDefaultJavaNS(javaURLContextImpl.java:552)
at com.ibm.ws.naming.java.javaURLContextImpl.lookupExt(javaURLContextImpl.java:481)
at com.ibm.ws.naming.java.javaURLContextRoot.lookupExt(javaURLContextRoot.java:485)
at com.ibm.ws.naming.java.javaURLContextRoot.lookup(javaURLContextRoot.java:370)
I understand from the error message is that the job is running outside the J2ee container and so the datasource is not available for the Job to make the connection, which I cannot agree as the Quartz is implemented as the ServletContextListener and the same is mentioned in web.xml.
Web.xml
<listener>
<listener-class>com.ehacampaign.helper.EHAJobSchedulerListener</listener-class>
</listener>
EHAJobSchedulerListener.java
public class EHAJobSchedulerListener implements ServletContextListener {..}
As you can see the code, the class is registered in the web and I do not understand why it cannot use the datasource in the J2EE container.
Questions are:
Why servlet registered class cannot access the datasource in J2EE
container?
If datasource in container cannot be used, then how to make a
connection to the DB while executing the job?
NOTE: I have the same setup in JBoss AS 7.1 and the jobs are running smoothly accessing the datasource configured in JBoss AS 7.1. I have to develop this in Websphere as the customer demands it.
UPDATED
I have attached the modified quartz property file. Even after adding the workmanagerthread, I am getting the same error.
org.quartz.threadPool.threadCount=1
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
org.quartz.threadExecutor.class=org.quartz.commonj.WorkManagerThreadExecutor
org.quartz.threadExecutor.workManagerName=wm/default
In order to perform JNDI lookups in WebSpehre, your code must be running on a managed thread. In order to have Quartz run on one of WebSphere's managed threads, you must set the following 2 properties in your quartz.properties (as Alasdair mentioned in the comments):
org.quartz.threadExecutor.class=org.quartz.commonj.WorkManagerThreadExecutor
org.quartz.threadExecutor.workManagerName=wm/default
The name for org.quartz.threadExecutor.workManagerName can be the JNDI name of any Work Manager that you have configured in WebSphere. I recommend simply using wm/default because it is in your configuration by default.
With all the help provided by aguibert and Alasdair and reference from here, I am able to fix the issue.
The Quartz property file is:
org.quartz.threadPool.threadCount=1
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
org.quartz.threadExecutor.class=org.quartz.commonj.WorkManagerThreadExecutor
org.quartz.threadExecutor.workManagerName=wm/default
The database connection or JNDI lookup should happen within the empty constructor of the JOB Implemented class. For ex,
public class ContractIdFromPartyServiceJob implements Job {
private DataSource ds;
public ContractIdFromPartyServiceJob() {
try {
Logger.info("Gets the data source");
Context context = new InitialContext();
ds = (DataSource) context.lookup(ApplicationConstants.RESOURCE_REFERENCE_JDBC);
} catch (RException e) {
e.printStackTrace();
}
}
#Override
public void execute(JobExecutionContext arg0) throws JobExecutionException
{
EHAMarsDAO marsDao = new EHAMarsDAO();
Connection con = getConnection();
try {
marsDao.callDBMethod(con);
} finally {
con.close();
}
}
public Connection getConnection() throws RACVException
{
Connection con = null;
try {
con = ds.getConnection();
con.setAutoCommit(false);
con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
} catch (SQLException e) {
throw new RException(Constants.ERROR_CODE_002, Constants.E012_DB_CONNECTION_ERROR, e);
}
return con;
}
}

Annotation JCacheResult is not working in Infinispan and Glassfish 3.1.1

I am trying to integrate JCache from Infinispan into my existing EJB project.
I have added Infinispan 5.0.1 CDI and Core packages to Maven pom.
Added Infinispan Interceptors in beans.xml and able to use the CacheResult annotation.
I am deploying the app in Glassfish 3.1.1. I have checked the Weld jar version, which is
Module : org.jboss.weld.osgi-bundle:1.1.1.Final
In the runtime, the CacheResult Method interceptor is not caching the method result and its always called.
My code looks like this,
public void cacheTest() {
Thread.currentThread().setContextClassLoader(
this.getClass().getClassLoader());
EmbeddedCacheManager manager = createCacheConfig();
Set<String> cacheList = manager.getCacheNames(); // new
// DefaultCacheManager().getCacheNames();
for (String cache : cacheList) {
System.out.println("Cache name " + cache);
}
defaultCache = manager.getCache("test-cache");
defaultCache.put("aa", "AA");
String user = "User";
greet(user);
Set<String> keys = defaultCache.keySet();
for (String key : keys) {
System.out.println("Key is -" + key + "Value is -"
+ defaultCache.get(key));
}
}
#CacheResult(cacheName = "test-cache")
public String greet(#CacheKeyParam String user) {
user += "Hello";
return user;
}
public EmbeddedCacheManager createCacheConfig() {
EmbeddedCacheManager manager = new DefaultCacheManager();
Configuration conf = new Configuration();
conf.fluent().eviction().strategy(EvictionStrategy.FIFO).maxEntries(10)
.expiration().maxIdle(1200000L).build();
conf.fluent().clustering().sync();
manager.start();
manager.defineConfiguration("test-cache", conf);
return manager;
}
greet() method gets called but it will never add the method result to the test-cache. I feel am I missing some configuration or...I dont know. Please help me on this.
when I Inject the classes, they wont get constructed and they are null. The code is like this,
#Inject
private static org.infinispan.Cache<String, String> defaultCache;
#Inject
private static EmbeddedCacheManager defaultCacheManager;
These gets executed without any error, but they wont get initialized.
I have no clue...But I am able to inject other EJBs with in this class easily. By the way I am trying to add Jcache functionality in one of EJBs.
I would appreciate your help...
Thanks...
Raj S
Your greet method is in a CDI bean or in an EJB, right?
The cache defined in JCache annotations is looked up in the cache manager provided by Infinispan CDI. This cache manager contains the cache configured with CDI (for more information, see https://docs.jboss.org/author/display/ISPN/CDI+Support). In your example the test-cache configuration will have no effect.
Another thing, if your cacheTest and greet methods are in the same class the greet method cannot be intercepted. If that's not the case maybe you're hitting GLASSFISH-17184.
For the Cache and EmbeddedCacheManager injections the problem is that you're doing a static injection, not supported by CDI. From CDI (JSR-299) specification
An injected field is a non-static, non-final field of a bean class, or of any Java EE component class supporting injection.
If your method result is not cached, I think it's because the CacheResultInterceptor is not called. I've just made the test with the Infinispan CDI quickstart. If the interceptors are in a lib they are not enabled. I think it's a bug in Glassfish.
Btw, you can see an example of code in the Infinispan CDI quickstart here.
Hope this help!