Skip to content

Commit

Permalink
feature white list for separated status routes
Browse files Browse the repository at this point in the history
  • Loading branch information
radoslaw.chrzanowski committed Mar 7, 2025
1 parent 12d6b45 commit 283152e
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Lists all changes with user impact.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

## [0.22.11]
### Changed
- white list for enabling separated routes for status endpoints

## [0.22.10]
### Changed
- changes for `x-envoy-upstream-service-tags` response header:
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Property
**envoy-control.envoy.snapshot.local-service.response-timeout** | Response timeout for localService | 15s
**envoy-control.envoy.snapshot.local-service.connection-idle-timeout** | Connection idle timeout for localService | 120s
**envoy-control.envoy.snapshot.routes.status.enabled** | Enable status route | false
**envoy-control.envoy.snapshot.routes.status.separated-route-white-list** | List of services for which we create a separated route for status endpoints | empty list
**envoy-control.envoy.snapshot.routes.status.endpoints** | List of endpoints with path or prefix of status routes | /status
**envoy-control.envoy.snapshot.routes.status.create-virtual-cluster** | Create virtual cluster for status route | false
**envoy-control.envoy.snapshot.state-sample-duration** | Duration of state sampling (this is used to prevent surges in consul events overloading control plane) | 1s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ class AdminRouteProperties {

class StatusRouteProperties {
var enabled = false
var separatedRouteWhiteList = FeatureWhiteList(emptyList())
var endpoints: MutableList<EndpointMatch> = mutableListOf()
var createVirtualCluster = false
}
Expand Down Expand Up @@ -459,3 +460,9 @@ data class ResetHeader(val name: String, val format: String)

typealias ProviderName = String
typealias TokenField = String

data class FeatureWhiteList(val services: List<String>) {
fun enabledFor(serviceName: String): Boolean {
return services.contains("*") || services.contains(serviceName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,22 @@ class EnvoyIngressRoutesFactory(
.toMap()

private fun ingressRoutes(proxySettings: ProxySettings, group: Group): List<Route> {
return ingressRoute(proxySettings, group, RoutingPriority.HIGH, "/status/") +
ingressRoute(proxySettings, group, RoutingPriority.DEFAULT, "/")
val defaultRoute = ingressRoute(proxySettings, group, RoutingPriority.DEFAULT, PathMatchingType.PATH_PREFIX,"/")
if (properties.routes.status.separatedRouteWhiteList.enabledFor(group.serviceName)) {
val statusRoutes = statusEndpointsMatch.flatMap {
ingressRoute(proxySettings, group, RoutingPriority.HIGH, it.matchingType, it.path)
}
return statusRoutes + defaultRoute
}
return ingressRoute(proxySettings, group, RoutingPriority.DEFAULT, PathMatchingType.PATH_PREFIX,"/")
}

private fun ingressRoute(
proxySettings: ProxySettings,
group: Group,
priority: RoutingPriority,
prefix: String
matchingType: PathMatchingType,
path: String
): List<Route> {
val localRouteAction = clusterRouteAction(
proxySettings.incoming.timeoutPolicy.responseTimeout,
Expand All @@ -201,17 +208,15 @@ class EnvoyIngressRoutesFactory(

val nonRetryRoute = Route.newBuilder()
.setMatch(
RouteMatch.newBuilder()
.setPrefix(prefix)
routeMatcher(matchingType, path)
)
.setRoute(localRouteAction)
val retryRoutes = perMethodRetryPolicies
.map { (method, retryPolicy) ->
Route.newBuilder()
.setMatch(
RouteMatch.newBuilder()
routeMatcher(matchingType, path)
.addHeaders(httpMethodMatcher(method))
.setPrefix(prefix)
)
.setRoute(clusterRouteActionWithRetryPolicy(retryPolicy, localRouteAction))
}
Expand All @@ -220,6 +225,17 @@ class EnvoyIngressRoutesFactory(
}
}

private fun routeMatcher(
matchingType: PathMatchingType,
path: String
): RouteMatch.Builder {
return when (matchingType) {
PathMatchingType.PATH -> RouteMatch.newBuilder().setPath(path)
PathMatchingType.PATH_PREFIX -> RouteMatch.newBuilder().setPrefix(path)
PathMatchingType.PATH_REGEX -> RouteMatch.newBuilder()
.setSafeRegex(RegexMatcher.newBuilder().setRegex(path))
}
}
private fun customHealthCheckRoute(proxySettings: ProxySettings): List<Route> {
if (proxySettings.incoming.healthCheck.hasCustomHealthCheck()) {
val healthCheckRouteAction = clusterRouteAction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import pl.allegro.tech.servicemesh.envoycontrol.groups.publicAccess
import pl.allegro.tech.servicemesh.envoycontrol.groups.toCluster
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.CustomRuteProperties
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.EndpointMatch
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.FeatureWhiteList
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.LocalRetryPoliciesProperties
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.LocalRetryPolicyProperties
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SecuredRoute
Expand Down Expand Up @@ -94,7 +95,15 @@ internal class EnvoyIngressRoutesFactoryTest {
// given
val routesFactory = EnvoyIngressRoutesFactory(SnapshotProperties().apply {
routes.status.enabled = true
routes.status.endpoints = mutableListOf(EndpointMatch())
routes.status.apply {
enabled = true
endpoints = mutableListOf(EndpointMatch().apply {
path = "/status/"
matchingType = PathMatchingType.PATH_PREFIX
})
createVirtualCluster = true
separatedRouteWhiteList = FeatureWhiteList(listOf("service_1"))
}
routes.status.createVirtualCluster = true
localService.retryPolicy = retryPolicyProps
routes.admin.publicAccessEnabled = true
Expand All @@ -111,6 +120,7 @@ internal class EnvoyIngressRoutesFactoryTest {
value = "/status/wrapper/"
}
})

}, currentZone = currentZone)
val responseTimeout = Durations.fromSeconds(777)
val idleTimeout = Durations.fromSeconds(61)
Expand Down Expand Up @@ -221,6 +231,157 @@ internal class EnvoyIngressRoutesFactoryTest {
}
}


@Test
@Suppress("LongMethod")
fun `should create not create routes for status endpoints if the service is not whitelisted`() {
// given
val routesFactory = EnvoyIngressRoutesFactory(SnapshotProperties().apply {
routes.status.enabled = true
routes.status.apply {
enabled = true
endpoints = mutableListOf(EndpointMatch().apply {
path = "/status/"
matchingType = PathMatchingType.PATH_PREFIX
})
createVirtualCluster = true
separatedRouteWhiteList = FeatureWhiteList(listOf("service_123"))
}
routes.status.createVirtualCluster = true
localService.retryPolicy = retryPolicyProps
routes.admin.publicAccessEnabled = true
routes.admin.token = "test_token"
routes.admin.securedPaths.add(SecuredRoute().apply {
pathPrefix = "/config_dump"
method = "GET"
})
}, currentZone = currentZone)
val proxySettingsOneEndpoint = ProxySettings(

)
val group = ServicesGroup(
communicationMode = CommunicationMode.XDS,
serviceName = "service_1",
discoveryServiceName = "service_1",
proxySettings = proxySettingsOneEndpoint
)

// when
val routeConfig = routesFactory.createSecuredIngressRouteConfig(
"service_1",
proxySettingsOneEndpoint,
group
)

// then
routeConfig
.hasSingleVirtualHostThat {
hasStatusVirtualClusters()
hasOneDomain("*")
hasOnlyRoutesInOrder(
*adminRoutes,
{
ingressServiceRoute()
matchingOnMethod("GET")
matchingRetryPolicy(retryPolicyProps.perHttpMethod["GET"]!!)
},
{
ingressServiceRoute()
matchingOnMethod("HEAD")
matchingRetryPolicy(retryPolicyProps.perHttpMethod["HEAD"]!!)
},
{
ingressServiceRoute()
matchingOnAnyMethod()
hasNoRetryPolicy()
}
)
matchingRetryPolicy(retryPolicyProps.default)
}
}

@Test
@Suppress("LongMethod")
fun `should create create routes for status endpoints when whitelist contains wildcard`() {
// given
val routesFactory = EnvoyIngressRoutesFactory(SnapshotProperties().apply {
routes.status.enabled = true
routes.status.apply {
enabled = true
endpoints = mutableListOf(EndpointMatch().apply {
path = "/status/"
matchingType = PathMatchingType.PATH_PREFIX
})
createVirtualCluster = true
separatedRouteWhiteList = FeatureWhiteList(listOf("service_123", "*"))
}
routes.status.createVirtualCluster = true
localService.retryPolicy = retryPolicyProps
routes.admin.publicAccessEnabled = true
routes.admin.token = "test_token"
routes.admin.securedPaths.add(SecuredRoute().apply {
pathPrefix = "/config_dump"
method = "GET"
})
}, currentZone = currentZone)
val proxySettingsOneEndpoint = ProxySettings(

)
val group = ServicesGroup(
communicationMode = CommunicationMode.XDS,
serviceName = "service_1",
discoveryServiceName = "service_1",
proxySettings = proxySettingsOneEndpoint
)

// when
val routeConfig = routesFactory.createSecuredIngressRouteConfig(
"service_1",
proxySettingsOneEndpoint,
group
)

// then
routeConfig
.hasSingleVirtualHostThat {
hasStatusVirtualClusters()
hasOneDomain("*")
hasOnlyRoutesInOrder(
*adminRoutes,
{
ingresStatusRoute()
matchingOnMethod("GET")
matchingRetryPolicy(retryPolicyProps.perHttpMethod["GET"]!!)
},
{
ingresStatusRoute()
matchingOnMethod("HEAD")
matchingRetryPolicy(retryPolicyProps.perHttpMethod["HEAD"]!!)
},
{
ingresStatusRoute()
matchingOnAnyMethod()
hasNoRetryPolicy()
},
{
ingressServiceRoute()
matchingOnMethod("GET")
matchingRetryPolicy(retryPolicyProps.perHttpMethod["GET"]!!)
},
{
ingressServiceRoute()
matchingOnMethod("HEAD")
matchingRetryPolicy(retryPolicyProps.perHttpMethod["HEAD"]!!)
},
{
ingressServiceRoute()
matchingOnAnyMethod()
hasNoRetryPolicy()
}
)
matchingRetryPolicy(retryPolicyProps.default)
}
}
@Test
fun `should create route config with headers to remove and add`() {
// given
Expand Down

0 comments on commit 283152e

Please sign in to comment.