Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kozjan committed Dec 13, 2024
1 parent 866410f commit 20a3241
Show file tree
Hide file tree
Showing 11 changed files with 510 additions and 454 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ data class IncomingRateLimitEndpoint(
)

enum class PathMatchingType {
PATH, PATH_PREFIX, PATH_REGEX
PATH, PATH_PREFIX, PATH_REGEX, PATHS
}

enum class CommunicationMode {
Expand All @@ -846,6 +846,15 @@ interface EndpointBase {
val path: String
val pathMatchingType: PathMatchingType
val methods: Set<String>

fun extractRule(): String {
return when (pathMatchingType) {
PathMatchingType.PATH -> "path:$path"
PathMatchingType.PATH_PREFIX -> "pathPrefix:$path"
PathMatchingType.PATH_REGEX -> "pathRegex:$path"
PathMatchingType.PATHS -> "paths${paths.joinToString(",")}"
}
}
}

// We don't distinguish between absence of the field and the field with explicit null value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,66 +117,50 @@ class JwtFilterFactory(
return emptySet()
}

return if (endpoint.paths.isNotEmpty()) {
endpoint.paths.map {
requirementRuleWithURITemplateMatching(it, endpoint.methods, providers)
}.toSet()
} else {
setOf(requirementRuleWithPathMatching(
endpoint.path, endpoint.pathMatchingType, endpoint.methods, providers))
}
return requirementRuleWithPathMatching(endpoint, endpoint.methods, providers)
}

private fun requirementRuleWithURITemplateMatching(
pathGlobPattern: String,
private fun requirementRuleWithPathMatching(
endpoint: IncomingEndpoint,
methods: Set<String>,
providers: MutableSet<String>
): RequirementRule {
val pathMatching = RouteMatch.newBuilder().setPathMatchPolicy(
TypedExtensionConfig.newBuilder()
.setName("envoy.path.match.uri_template.uri_template_matcher")
.setTypedConfig(
Any.pack(
UriTemplateMatchConfig.newBuilder()
.setPathTemplate(pathGlobPattern)
.build()
): Set<RequirementRule> {
val pathMatching = when (endpoint.pathMatchingType) {
PathMatchingType.PATH -> listOf(RouteMatch.newBuilder().setPath(endpoint.path))
PathMatchingType.PATH_PREFIX -> listOf(RouteMatch.newBuilder().setPrefix(endpoint.path))
PathMatchingType.PATH_REGEX -> listOf(
RouteMatch.newBuilder()
.setSafeRegex(
RegexMatcher.newBuilder().setRegex(endpoint.path).setGoogleRe2(
RegexMatcher.GoogleRE2.getDefaultInstance()
).build()
)
).build()
)
if (methods.isNotEmpty()) {
pathMatching.addHeaders(createHeaderMatcherBuilder(methods))
}

return RequirementRule.newBuilder()
.setMatch(pathMatching)
.setRequires(createJwtRequirement(providers))
.build()
}
)

private fun requirementRuleWithPathMatching(
path: String,
pathMatchingType: PathMatchingType,
methods: Set<String>,
providers: MutableSet<String>
): RequirementRule {
val pathMatching = when (pathMatchingType) {
PathMatchingType.PATH -> RouteMatch.newBuilder().setPath(path)
PathMatchingType.PATH_PREFIX -> RouteMatch.newBuilder().setPrefix(path)
PathMatchingType.PATH_REGEX -> RouteMatch.newBuilder()
.setSafeRegex(
RegexMatcher.newBuilder().setRegex(path).setGoogleRe2(
RegexMatcher.GoogleRE2.getDefaultInstance()
).build()
PathMatchingType.PATHS -> endpoint.paths.map { path ->
RouteMatch.newBuilder().setPathMatchPolicy(
TypedExtensionConfig.newBuilder()
.setName("envoy.path.match.uri_template.uri_template_matcher")
.setTypedConfig(
Any.pack(
UriTemplateMatchConfig.newBuilder()
.setPathTemplate(path)
.build()
)
).build()
)
}
}

if (methods.isNotEmpty()) {
pathMatching.addHeaders(createHeaderMatcherBuilder(methods))
pathMatching.forEach { it.addHeaders(createHeaderMatcherBuilder(methods)) }
}

return RequirementRule.newBuilder()
.setMatch(pathMatching)
.setRequires(createJwtRequirement(providers))
.build()
return pathMatching.map {
RequirementRule.newBuilder()
.setMatch(it)
.setRequires(createJwtRequirement(providers))
.build()
}.toSet()
}

private fun createHeaderMatcherBuilder(methods: Set<String>): HeaderMatcher.Builder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import pl.allegro.tech.servicemesh.envoycontrol.groups.Group
import pl.allegro.tech.servicemesh.envoycontrol.groups.Incoming
import pl.allegro.tech.servicemesh.envoycontrol.groups.IncomingEndpoint
import pl.allegro.tech.servicemesh.envoycontrol.groups.OAuth
import pl.allegro.tech.servicemesh.envoycontrol.groups.PathMatchingType
import pl.allegro.tech.servicemesh.envoycontrol.groups.Role
import pl.allegro.tech.servicemesh.envoycontrol.logger
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.Client
Expand Down Expand Up @@ -180,13 +181,13 @@ class RBACFilterFactory(
it.endpoint.unlistedClientsPolicy == Incoming.UnlistedPolicy.BLOCKANDLOG ||
it.endpoint.oauth?.policy != null
}
.map { (endpoint, policy) -> "$endpoint" to policy }.toMap()
.map { (endpoint, policy) -> endpoint.extractRule() to policy }.toMap()

val loggedEndpointsPolicies = incomingEndpointsPolicies.asSequence()
.filter {
it.endpoint.unlistedClientsPolicy == Incoming.UnlistedPolicy.LOG && it.endpoint.oauth?.policy == null
}
.map { (endpoint, policy) -> "$endpoint" to policy }.toMap()
.map { (endpoint, policy) -> endpoint.extractRule() to policy }.toMap()

val allowUnlistedPolicies = unlistedAndLoggedEndpointsPolicies(
incomingPermissions,
Expand Down Expand Up @@ -277,9 +278,16 @@ class RBACFilterFactory(
return if (statusRouteProperties.enabled) {
val permissions = statusRouteProperties.endpoints
.map {
rBACFilterPermissions.createPathPermission(
IncomingEndpoint(
paths = emptySet(),
path = it.path,
matchingType = it.matchingType
pathMatchingType = PathMatchingType.PATH_PREFIX
)
}
.map {
rBACFilterPermissions.createPathPermission(
endpoint = it,
matchingType = it.pathMatchingType
).build()
}
val policy = Policy.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package pl.allegro.tech.servicemesh.envoycontrol.snapshot.resource.listeners.filters

import com.google.protobuf.Any
import com.google.protobuf.MessageOrBuilder
import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig
import io.envoyproxy.envoy.config.rbac.v3.Permission
import io.envoyproxy.envoy.config.route.v3.HeaderMatcher
Expand All @@ -14,42 +15,33 @@ import pl.allegro.tech.servicemesh.envoycontrol.groups.PathMatchingType
class RBACFilterPermissions {
fun createCombinedPermissions(incomingEndpoint: IncomingEndpoint): Permission.Builder {
val permissions = listOfNotNull(
if (incomingEndpoint.paths.isNotEmpty()) {
createPathTemplatesPermissionForEndpoint(incomingEndpoint)
} else {
createPathPermissionForEndpoint(incomingEndpoint)
},
createPathPermissionForEndpoint(incomingEndpoint),
createMethodPermissions(incomingEndpoint),
)
.map { it.build() }
).map { it.build() }

return permission().setAndRules(
Permission.Set.newBuilder().addAllRules(permissions)
)
}

private fun createPathTemplatesPermissionForEndpoint(incomingEndpoint: IncomingEndpoint): Permission.Builder {
return permission()
.setOrRules(Permission.Set.newBuilder().addAllRules(
incomingEndpoint.paths.map(this::createPathTemplate)))
}

private fun createPathTemplate(path: String): Permission {
return permission().setUriTemplate(TypedExtensionConfig.newBuilder()
.setName("envoy.path.match.uri_template.uri_template_matcher")
.setTypedConfig(Any.pack(
UriTemplateMatchConfig.newBuilder()
.setPathTemplate(path)
.build()
))).build()
}
fun createPathPermission(endpoint: IncomingEndpoint, matchingType: PathMatchingType): Permission.Builder {
val matcherBuilder = createMatcherBuilder(endpoint, matchingType)
return when (matchingType) {
PathMatchingType.PATHS -> {
val pathTemplates =
matcherBuilder.map { permission().setUriTemplate(it as TypedExtensionConfig.Builder) }
permission().setOrRules(Permission.Set.newBuilder().addAllRules(pathTemplates.map { it.build() }))
}

fun createPathPermission(path: String, matchingType: PathMatchingType): Permission.Builder {
return permission().setUrlPath(createPathMatcher(path, matchingType))
else -> {
val pathMatcher = PathMatcher.newBuilder().setPath(matcherBuilder.first() as StringMatcher).build()
permission().setUrlPath(pathMatcher)
}
}
}

private fun createPathPermissionForEndpoint(incomingEndpoint: IncomingEndpoint): Permission.Builder {
return createPathPermission(incomingEndpoint.path, incomingEndpoint.pathMatchingType)
return createPathPermission(incomingEndpoint, incomingEndpoint.pathMatchingType)
}

private fun createMethodPermissions(incomingEndpoint: IncomingEndpoint): Permission.Builder? {
Expand All @@ -67,35 +59,39 @@ class RBACFilterPermissions {
return permission().setHeader(methodMatch).build()
}

private fun createPathMatcher(path: String, matchingType: PathMatchingType): PathMatcher {
val matcher = when (matchingType) {
PathMatchingType.PATH -> StringMatcher.newBuilder().setExact(path).build()
PathMatchingType.PATH_PREFIX -> StringMatcher.newBuilder().setPrefix(path).build()
PathMatchingType.PATH_REGEX -> safeRegexMatcher(path)
private fun createMatcherBuilder(
endpoint: IncomingEndpoint, matchingType: PathMatchingType
): List<MessageOrBuilder> {
val builder = when (matchingType) {
PathMatchingType.PATH -> listOf(StringMatcher.newBuilder().setExact(endpoint.path).build())
PathMatchingType.PATH_PREFIX -> listOf(StringMatcher.newBuilder().setPrefix(endpoint.path).build())
PathMatchingType.PATH_REGEX -> listOf(safeRegexMatcher(endpoint.path))
PathMatchingType.PATHS -> endpoint.paths.map {
TypedExtensionConfig.newBuilder().setName("envoy.path.match.uri_template.uri_template_matcher")
.setTypedConfig(
Any.pack(
UriTemplateMatchConfig.newBuilder().setPathTemplate(it).build()
)
)
}
}
return PathMatcher.newBuilder().setPath(matcher).build()
return builder
}
}

private fun safeRegexMatcher(regexPattern: String) = StringMatcher.newBuilder()
.setSafeRegex(
RegexMatcher.newBuilder()
.setRegex(regexPattern)
.setGoogleRe2(
RegexMatcher.GoogleRE2.getDefaultInstance()
)
.build()
)
.build()
private fun safeRegexMatcher(regexPattern: String) = StringMatcher.newBuilder().setSafeRegex(
RegexMatcher.newBuilder().setRegex(regexPattern).setGoogleRe2(
RegexMatcher.GoogleRE2.getDefaultInstance()
).build()
).build()

private fun permission() = Permission.newBuilder()
private fun not(permission: Permission.Builder) = permission().setNotRule(permission)
fun anyOf(permissions: Iterable<Permission>): Permission.Builder = permission()
.setOrRules(Permission.Set.newBuilder().addAllRules(permissions))
fun anyOf(permissions: Iterable<Permission>): Permission.Builder =
permission().setOrRules(Permission.Set.newBuilder().addAllRules(permissions))

fun noneOf(permissions: List<Permission>): Permission.Builder =
if (permissions.isNotEmpty()) {
not(anyOf(permissions))
} else {
permission().setAny(true)
}
fun noneOf(permissions: List<Permission>): Permission.Builder = if (permissions.isNotEmpty()) {
not(anyOf(permissions))
} else {
permission().setAny(true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class EnvoyIngressRoutesFactory(
PathMatchingType.PATH_PREFIX -> HeaderMatcher.newBuilder().setName(":path").setPrefixMatch(path).build()
PathMatchingType.PATH -> HeaderMatcher.newBuilder().setName(":path").setExactMatch(path).build()
PathMatchingType.PATH_REGEX -> HeaderMatcher.newBuilder().setName(":path").setRe2Match(path).build()
PathMatchingType.PATHS -> throw IllegalArgumentException("PathMatchingType.PATHS is not supported")
}

private fun HeaderMatcher.Builder.setRe2Match(regexPattern: String) = this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ function envoy_on_response(handle)
local dynamic_metadata = handle:streamInfo():dynamicMetadata()
local rbacMetadata = dynamic_metadata:get('envoy.filters.http.rbac') or {}
local is_shadow_denied = (rbacMetadata['shadow_engine_result'] or '') == 'denied'
local rule = rbacMetadata['shadow_effective_policy_id'] or ''

if is_shadow_denied then
local headers = handle:headers()
Expand All @@ -92,11 +93,11 @@ function envoy_on_response(handle)
if upstream_request_time == nil and status_code == '403' then
rbac_action = 'denied'
end
log_request(handle, lua_metadata, jwt_status, rbac_action)
log_request(handle, rule, lua_metadata, jwt_status, rbac_action)
end
end

function log_request(handle, lua_metadata, jwt_status, rbac_action)
function log_request(handle, rule, lua_metadata, jwt_status, rbac_action)
local client_name = lua_metadata['request.info.client_name'] or ''
local trusted_client = lua_metadata['request.info.trusted_client'] or false
local path = lua_metadata['request.info.path'] or ''
Expand All @@ -112,6 +113,7 @@ function log_request(handle, lua_metadata, jwt_status, rbac_action)

local message = {
'\nINCOMING_PERMISSIONS {"method":"', method,
'","rule":"', rule,
'","path":"', path,
'","clientIp":"', source_ip,
'","clientName":"', escape(client_name),
Expand Down
Loading

0 comments on commit 20a3241

Please sign in to comment.