Skip to content

Commit 2d8d507

Browse files
psasidharSasi Palaka
andauthored
Adding support to use port-uri.json to start application ports and filter requests (#3190)
Signed-off-by: Sasi Palaka <palakas@yahooinc.com> Co-authored-by: Sasi Palaka <palakas@yahooinc.com>
1 parent 231a2ff commit 2d8d507

22 files changed

+3157
-26
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
{
2+
"ports": [
3+
{
4+
"port": 9443,
5+
"mtls_required": false,
6+
"description": "Instance registration port - restricted endpoints only",
7+
"allowed_endpoints": [
8+
{
9+
"path": "/zts/v1/instance",
10+
"methods": ["POST"],
11+
"description": "Instance registration - initial registration"
12+
}
13+
]
14+
},
15+
{
16+
"port": 4443,
17+
"mtls_required": true,
18+
"description": "Main API port - all endpoints allowed with mTLS required",
19+
"allowed_endpoints": []
20+
},
21+
{
22+
"port": 8443,
23+
"mtls_required": false,
24+
"description": "Health/status port - /zts/v1/status (ZTS API) and /status (file-based health check returning OK)",
25+
"allowed_endpoints": [
26+
{
27+
"path": "/zts/v1/status",
28+
"methods": ["GET"],
29+
"description": "ZTS API status - returns JSON { \"code\": 200, \"message\": \"OK\" }"
30+
},
31+
{
32+
"path": "/status",
33+
"methods": ["GET"],
34+
"description": "Legacy file-based health check - returns OK when athenz.health_check_uri_list includes /status"
35+
}
36+
]
37+
},
38+
{
39+
"port": 443,
40+
"mtls_required": false,
41+
"description": "JWKS and OpenID discovery endpoints",
42+
"allowed_endpoints": [
43+
{
44+
"path": "/zts/v1/.well-known/openid-configuration",
45+
"methods": ["GET"],
46+
"description": "OpenID Connect discovery"
47+
},
48+
{
49+
"path": "/zts/v1/oauth2/keys",
50+
"methods": ["GET"],
51+
"description": "OAuth2 JWKS public keys"
52+
},
53+
{
54+
"path_starts_with": "/zts/v1/.well-known",
55+
"path_ends_with": "openid-configuration",
56+
"methods": ["GET"],
57+
"description": "OpenID discovery (alternative using path_starts_with and path_ends_with)"
58+
}
59+
]
60+
}
61+
]
62+
}

containers/jetty/src/main/java/com/yahoo/athenz/container/AthenzConsts.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,31 +63,32 @@ public final class AthenzConsts {
6363
public static final String ATHENZ_PROP_HOSTNAME = "athenz.hostname";
6464
public static final String ATHENZ_PROP_JETTY_HOME = "athenz.jetty_home";
6565
public static final String ATHENZ_PROP_DEBUG = "athenz.debug";
66-
public static final String ATHENZ_PROP_HEALTH_CHECK_URI_LIST = "athenz.health_check_uri_list";
67-
public static final String ATHENZ_PROP_HEALTH_CHECK_PATH = "athenz.health_check_path";
68-
public static final String ATHENZ_PROP_LOG_FORWARDED_FOR_ADDR = "athenz.log_forwarded_for_addr";
66+
public static final String ATHENZ_PROP_HEALTH_CHECK_URI_LIST = "athenz.health_check_uri_list";
67+
public static final String ATHENZ_PROP_HEALTH_CHECK_PATH = "athenz.health_check_path";
68+
public static final String ATHENZ_PROP_LOG_FORWARDED_FOR_ADDR = "athenz.log_forwarded_for_addr";
6969
public static final String ATHENZ_PROP_DECODE_AMBIGUOUS_URIS = "athenz.decode_ambiguous_uris";
7070
public static final String ATHENZ_PROP_SEND_HOST_HEADER = "athenz.send_host_header";
7171

7272
public static final String ATHENZ_PROP_RATE_LIMIT_FACTORY_CLASS = "athenz.ratelimit_factory_class";
7373
public static final String ATHENZ_PROP_PRIVATE_KEY_STORE_FACTORY_CLASS = "athenz.private_keystore_factory_class";
74+
public static final String ATHENZ_PROP_PORT_URI_CONFIG = "athenz.port_uri_config";
7475

7576
public static final String ATHENZ_PROP_SERVER_POOL_SET_ENABLED = "athenz.server.pool.set_enabled";
7677

7778
public static final String STR_DEF_ROOT = "/home/athenz";
7879

79-
public static final String ATHENZ_PROP_HTTP_PORT = "athenz.port";
80-
public static final String ATHENZ_PROP_HTTPS_PORT = "athenz.tls_port";
81-
public static final String ATHENZ_PROP_OIDC_PORT = "athenz.oidc_port";
82-
public static final String ATHENZ_PROP_STATUS_PORT = "athenz.status_port";
83-
80+
public static final String ATHENZ_PROP_HTTP_PORT = "athenz.port";
81+
public static final String ATHENZ_PROP_HTTPS_PORT = "athenz.tls_port";
82+
public static final String ATHENZ_PROP_OIDC_PORT = "athenz.oidc_port";
83+
public static final String ATHENZ_PROP_STATUS_PORT = "athenz.status_port";
84+
8485
public static final int ATHENZ_HTTPS_PORT_DEFAULT = 4443;
8586
public static final int ATHENZ_HTTP_PORT_DEFAULT = 4080;
8687
public static final int ATHENZ_HTTP_MAX_THREADS = 1024;
87-
88+
8889
public static final String ATHENZ_RATE_LIMIT_FACTORY_CLASS = "com.yahoo.athenz.common.filter.impl.NoOpRateLimitFactory";
8990
public static final String ATHENZ_PKEY_STORE_FACTORY_CLASS = "com.yahoo.athenz.auth.impl.FilePrivateKeyStoreFactory";
90-
91+
9192
public static final String ATHENZ_PROP_KEYSTORE_PASSWORD_APPNAME = "athenz.ssl_key_store_password_appname";
9293
public static final String ATHENZ_PROP_KEYSTORE_PASSWORD_KEYGROUPNAME = "athenz.ssl_key_store_password_keygroupname";
9394

containers/jetty/src/main/java/com/yahoo/athenz/container/AthenzJettyContainer.java

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
import com.yahoo.athenz.common.server.log.jetty.JettyConnectionLoggerFactory;
2727
import com.yahoo.athenz.common.server.util.ConfigProperties;
2828
import com.yahoo.athenz.common.server.util.config.providers.ConfigProviderFile;
29+
import com.yahoo.athenz.container.config.PortConfig;
30+
import com.yahoo.athenz.container.config.PortUriConfiguration;
31+
import com.yahoo.athenz.container.config.PortUriConfigurationManager;
2932
import com.yahoo.athenz.container.filter.HealthCheckFilter;
3033
import jakarta.servlet.DispatcherType;
3134
import org.eclipse.jetty.deploy.DeploymentManager;
@@ -68,14 +71,15 @@ public class AthenzJettyContainer {
6871

6972
static final String ATHENZ_DEFAULT_EXCLUDED_PROTOCOLS = "SSLv2,SSLv3";
7073

71-
private Server server = null;
74+
Server server = null;
7275
private String banner = null;
7376
private Handler.Sequence handlers = null;
7477
private PrivateKeyStore privateKeyStore;
7578
private final boolean decodeAmbiguousUris;
7679
private final AthenzConnectionListener connectionListener = new AthenzConnectionListener();
7780
private final JettyConnectionLoggerFactory jettyConnectionLoggerFactory = new JettyConnectionLoggerFactory();
78-
81+
82+
7983
public AthenzJettyContainer() {
8084

8185
// check to see if we want to support ambiguous uris
@@ -290,6 +294,9 @@ void addServletHandlers(final String serverHostName) {
290294
servletCtxHandler.addFilter(filterHolder, checkUri.trim(), EnumSet.of(DispatcherType.REQUEST));
291295
}
292296
}
297+
298+
// PortFilter is applied to all webapps (e.g. ZTS API) via webdefault.xml; no need to add it here.
299+
293300
contexts.addHandler(servletCtxHandler);
294301

295302
final String jettyHome = System.getProperty(AthenzConsts.ATHENZ_PROP_JETTY_HOME, getRootDir());
@@ -441,7 +448,7 @@ SslContextFactory.Server createSSLContextObject(boolean needClientAuth) {
441448

442449
void addHTTPConnector(HttpConfiguration httpConfig, int httpPort, boolean proxyProtocol,
443450
String listenHost, int idleTimeout) {
444-
451+
445452
ServerConnector connector;
446453
if (proxyProtocol) {
447454
connector = new ServerConnector(server, new ProxyConnectionFactory(), new HttpConnectionFactory(httpConfig));
@@ -525,12 +532,6 @@ public void addHTTPConnectors(HttpConfiguration httpConfig, int httpPort, int ht
525532
boolean proxyProtocol = Boolean.parseBoolean(
526533
System.getProperty(AthenzConsts.ATHENZ_PROP_PROXY_PROTOCOL, "false"));
527534

528-
// HTTP Connector
529-
530-
if (httpPort > 0) {
531-
addHTTPConnector(httpConfig, httpPort, proxyProtocol, listenHost, idleTimeout);
532-
}
533-
534535
// check to see if we need to create our connection logger
535536
// for TLS connection failures
536537

@@ -545,27 +546,80 @@ public void addHTTPConnectors(HttpConfiguration httpConfig, int httpPort, int ht
545546
System.getProperty(AthenzConsts.ATHENZ_PROP_SNI_REQUIRED, "false"));
546547
boolean sniHostCheck = Boolean.parseBoolean(
547548
System.getProperty(AthenzConsts.ATHENZ_PROP_SNI_HOSTCHECK, "true"));
549+
550+
// Use port-uri.json for connectors if configured; otherwise use properties
551+
PortUriConfigurationManager configManager = PortUriConfigurationManager.getInstance();
552+
553+
if (configManager.isPortListConfigured()) {
554+
LOG.info("Creating HTTP/HTTPS connectors based on port-uri.json configuration");
555+
addConnectorsFromPortConfig(configManager.getConfiguration(), httpConfig,
556+
proxyProtocol, listenHost, idleTimeout, sniRequired, sniHostCheck, connectionLogger);
557+
} else {
558+
LOG.info("Creating HTTP/HTTPS connectors based on properties configuration");
559+
addConnectorsFromProperties(httpConfig, httpPort, httpsPort, oidcPort, statusPort,
560+
proxyProtocol, listenHost, idleTimeout, sniRequired, sniHostCheck, connectionLogger);
561+
}
562+
}
563+
564+
/**
565+
* Add HTTP/HTTPS connectors based on port-uri.json configuration
566+
*/
567+
private void addConnectorsFromPortConfig(PortUriConfiguration config, HttpConfiguration httpConfig,
568+
boolean proxyProtocol, String listenHost, int idleTimeout, boolean sniRequired,
569+
boolean sniHostCheck, JettyConnectionLogger connectionLogger) {
570+
571+
for (PortConfig portConfig : config.getPorts()) {
572+
int port = portConfig.getPort();
573+
574+
if (port <= 0) {
575+
continue;
576+
}
577+
578+
// Determine if this port needs client authentication (mTLS)
579+
boolean needClientAuth = portConfig.isMtlsRequired();
580+
581+
// Create HTTPS connector for this port
582+
HttpConfiguration httpsConfig = getHttpsConfig(httpConfig, port, sniRequired, sniHostCheck);
583+
addHTTPSConnector(httpsConfig, port, proxyProtocol, listenHost,
584+
idleTimeout, needClientAuth, connectionLogger);
585+
586+
LOG.info("Added connector for port {} (mTLS: {}, description: {})",
587+
port, needClientAuth, portConfig.getDescription());
588+
}
589+
}
590+
591+
/**
592+
* Add HTTP/HTTPS connectors based on property configuration
593+
*/
594+
private void addConnectorsFromProperties(HttpConfiguration httpConfig, int httpPort, int httpsPort,
595+
int oidcPort, int statusPort, boolean proxyProtocol, String listenHost,
596+
int idleTimeout, boolean sniRequired, boolean sniHostCheck,
597+
JettyConnectionLogger connectionLogger) {
598+
599+
// Default client auth setting from properties
548600
boolean needClientAuth = Boolean.parseBoolean(
549601
System.getProperty(AthenzConsts.ATHENZ_PROP_CLIENT_AUTH, "false"));
550602

551-
// HTTPS Connector
603+
// HTTP Connector
604+
if (httpPort > 0) {
605+
addHTTPConnector(httpConfig, httpPort, proxyProtocol, listenHost, idleTimeout);
606+
}
552607

608+
// HTTPS Connector
553609
if (httpsPort > 0) {
554610
HttpConfiguration httpsConfig = getHttpsConfig(httpConfig, httpsPort, sniRequired, sniHostCheck);
555611
addHTTPSConnector(httpsConfig, httpsPort, proxyProtocol, listenHost,
556612
idleTimeout, needClientAuth, connectionLogger);
557613
}
558614

559615
// OIDC Connector - only if it's different from HTTPS
560-
561616
if (oidcPort > 0 && oidcPort != httpsPort) {
562617
HttpConfiguration httpsConfig = getHttpsConfig(httpConfig, oidcPort, sniRequired, sniHostCheck);
563618
addHTTPSConnector(httpsConfig, oidcPort, proxyProtocol, listenHost,
564619
idleTimeout, needClientAuth, connectionLogger);
565620
}
566621

567622
// Status Connector - only if it's different from HTTP/HTTPS
568-
569623
if (statusPort > 0 && statusPort != httpPort && statusPort != httpsPort) {
570624
if (httpsPort > 0) {
571625
HttpConfiguration httpsConfig = getHttpsConfig(httpConfig, httpsPort, false, false);
@@ -612,9 +666,9 @@ public static AthenzJettyContainer createJettyContainer() {
612666
// for status port we'll use the protocol specified for the regular http
613667
// port. if both http and https are provided then https will be picked
614668
// it could also be either one of the values specified as well
615-
669+
616670
int statusPort = ConfigProperties.getPortNumber(AthenzConsts.ATHENZ_PROP_STATUS_PORT, 0);
617-
671+
618672
String serverHostName = getServerHostName();
619673

620674
AthenzJettyContainer container = new AthenzJettyContainer();
@@ -626,6 +680,10 @@ public static AthenzJettyContainer createJettyContainer() {
626680
Integer.toString(AthenzConsts.ATHENZ_HTTP_MAX_THREADS)));
627681
container.createServer(maxThreads);
628682

683+
// Load port-uri.json configuration before creating connectors
684+
// This must be done before addHTTPConnectors() so the configuration is available
685+
loadPortUriConfiguration();
686+
629687
HttpConfiguration httpConfig = container.newHttpConfiguration();
630688
container.addHTTPConnectors(httpConfig, httpPort, httpsPort, oidcPort, statusPort);
631689

@@ -655,6 +713,21 @@ public static void initConfigManager() {
655713
CONFIG_MANAGER.addConfigSource(ConfigProviderFile.PROVIDER_DESCRIPTION_PREFIX + propFile);
656714
}
657715

716+
/**
717+
* Load port-uri.json configuration if available.
718+
* This must be called before creating HTTP/HTTPS connectors.
719+
*/
720+
static void loadPortUriConfiguration() {
721+
// Configuration is automatically loaded by PortUriConfigurationManager singleton
722+
// upon first getInstance() call. Just trigger initialization and log the result.
723+
PortUriConfigurationManager configManager = PortUriConfigurationManager.getInstance();
724+
if (configManager.isPortListConfigured()) {
725+
LOG.info("Port-uri configuration successfully loaded");
726+
} else {
727+
LOG.info("Port-uri configuration not available, will use properties for port configuration");
728+
}
729+
}
730+
658731
public void run() {
659732
try {
660733
server.setDumpAfterStart(Boolean.parseBoolean(System.getProperty(AthenzConsts.ATHENZ_PROP_DUMP_AFTER_START, "false")));
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright The Athenz 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+
package com.yahoo.athenz.container.config;
17+
18+
import com.fasterxml.jackson.annotation.JsonProperty;
19+
20+
import java.util.List;
21+
22+
/**
23+
* Configuration for an allowed endpoint including path (exact or prefix/suffix), HTTP methods, and description.
24+
* Use "path" for exact match, or "path_starts_with" and/or "path_ends_with" for prefix/suffix matching.
25+
*/
26+
public class EndpointConfig {
27+
28+
private String path;
29+
@JsonProperty("path_starts_with")
30+
private String pathStartsWith;
31+
@JsonProperty("path_ends_with")
32+
private String pathEndsWith;
33+
private List<String> methods;
34+
private String description;
35+
36+
public String getPath() {
37+
return path;
38+
}
39+
40+
public void setPath(String path) {
41+
this.path = path;
42+
}
43+
44+
public String getPathStartsWith() {
45+
return pathStartsWith;
46+
}
47+
48+
public void setPathStartsWith(String pathStartsWith) {
49+
this.pathStartsWith = pathStartsWith;
50+
}
51+
52+
public String getPathEndsWith() {
53+
return pathEndsWith;
54+
}
55+
56+
public void setPathEndsWith(String pathEndsWith) {
57+
this.pathEndsWith = pathEndsWith;
58+
}
59+
60+
public List<String> getMethods() {
61+
return methods;
62+
}
63+
64+
public void setMethods(List<String> methods) {
65+
this.methods = methods;
66+
}
67+
68+
public String getDescription() {
69+
return description;
70+
}
71+
72+
public void setDescription(String description) {
73+
this.description = description;
74+
}
75+
76+
/**
77+
* Check if this endpoint allows the given HTTP method
78+
*
79+
* @param method HTTP method to check
80+
* @return true if method is allowed (or if no method restrictions configured)
81+
*/
82+
public boolean allowsMethod(String method) {
83+
return methods == null || methods.isEmpty() || methods.contains(method);
84+
}
85+
}

0 commit comments

Comments
 (0)