Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
## [Unreleased]

### Changed
- Add possibility to set dependency to group of services by tag mechanism
- flaky test fixed
- remove duplicated routes


## [0.19.26]

### Changed
Expand Down
4 changes: 2 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Property

## Permissions
Property | Description | Default value
-------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------
-------------------------------------------------------------------------------------------------------------------------------------------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ---------
**envoy-control.envoy.snapshot.incoming-permissions.enabled** | Enable incoming permissions | false
**envoy-control.envoy.snapshot.incoming-permissions.client-identity-headers** | Headers that identify the client calling the endpoint. In most cases `client-identity-header` should include `service-name-header` value to correctly identify other services in the mesh. | [ x-service-name ]
**envoy-control.envoy.snapshot.incoming-permissions.clients-allowed-to-all-endpoints** | Client names which are allowed to even call service if incoming permissions are enabled. | empty list
Expand Down Expand Up @@ -127,7 +127,7 @@ Property
**envoy-control.envoy.snapshot.outgoing-permissions.services-allowed-to-use-wildcard** | Services that are allowed to have wildcard in outgoing.dependency field | empty set
**envoy-control.envoy.snapshot.outgoing-permissions.rbac.clients-lists.default-clients-list** | List of clients which will be applied to each rbac policy, if none of the lists defined in `custom-clients-lists` have been matched | empty list
**envoy-control.envoy.snapshot.outgoing-permissions.rbac.clients-lists.custom-clients-lists** | Lists of clients which will be applied to each rbac policy, only if key for defined list is present in clients for defined endpoint | empty map

**envoy-control.envoy.snapshot.outgoing-permissions.tag-prefix** | Value that specify which tags are allowed to be used in dependencies by prefix | empty string
## Load Balancing
Property | Description | Default value
------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------
Expand Down
1 change: 1 addition & 0 deletions docs/features/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ metadata:
- service: service-b
handleInternalRedirect: true
- domain: http://www.example.com
- tag: tag-a
incoming:
endpoints:
- path: /example
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import pl.allegro.tech.servicemesh.envoycontrol.server.callbacks.MetricsDiscover
import pl.allegro.tech.servicemesh.envoycontrol.services.MultiClusterState
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.EnvoySnapshotFactory
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.NoopSnapshotChangeAuditor
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.RouteSpecificationFactory
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotChangeAuditor
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotUpdater
import pl.allegro.tech.servicemesh.envoycontrol.snapshot.SnapshotsVersions
Expand Down Expand Up @@ -167,7 +168,10 @@ class ControlPlane private constructor(
val snapshotProperties = properties.envoy.snapshot
val envoySnapshotFactory = EnvoySnapshotFactory(
ingressRoutesFactory = EnvoyIngressRoutesFactory(snapshotProperties, envoyHttpFilters),
egressRoutesFactory = EnvoyEgressRoutesFactory(snapshotProperties),
egressRoutesFactory = EnvoyEgressRoutesFactory(
snapshotProperties.egress,
snapshotProperties.incomingPermissions
),
clustersFactory = EnvoyClustersFactory(snapshotProperties),
endpointsFactory = EnvoyEndpointsFactory(
snapshotProperties, ServiceTagMetadataGenerator(snapshotProperties.routing.serviceTags)
Expand All @@ -176,6 +180,7 @@ class ControlPlane private constructor(
snapshotProperties,
envoyHttpFilters
),
routeSpecificationFactory = RouteSpecificationFactory(snapshotProperties),
// Remember when LDS change we have to send RDS again
snapshotsVersions = snapshotsVersions,
properties = snapshotProperties,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ fun Value?.toHeaderFilter(default: String? = null): HeaderFilterSettings? {
}
}

private class RawDependency(val service: String?, val domain: String?, val domainPattern: String?, val value: Value)
private class RawDependency(
val service: String?,
val domain: String?,
val domainPattern: String?,
val tag: String?,
val value: Value
)

private fun defaultRetryPolicy(retryPolicy: RetryPolicyProperties) = if (retryPolicy.enabled) {
RetryPolicy(
Expand Down Expand Up @@ -145,10 +151,19 @@ fun Value?.toOutgoing(properties: SnapshotProperties): Outgoing {
val domainPatterns = rawDependencies.filter { it.domainPattern != null }
.onEach { validateDomainPatternFormat(it) }
.map { DomainPatternDependency(it.domainPattern.orEmpty(), it.value.toSettings(defaultSettingsFromProperties)) }

val tags = rawDependencies.filter { it.tag != null }
.map {
TagDependency(
tag = it.tag!!,
settings = it.value.toSettings(allServicesDefaultSettings)
)
}
return Outgoing(
serviceDependencies = services,
domainDependencies = domains,
domainPatternDependencies = domainPatterns,
tagDependencies = tags,
defaultServiceSettings = allServicesDefaultSettings,
allServicesDependencies = allServicesDependencies != null
)
Expand All @@ -159,6 +174,7 @@ private fun toRawDependency(it: Value): RawDependency {
val service = it.field("service")?.stringValue
val domain = it.field("domain")?.stringValue
val domainPattern = it.field("domainPattern")?.stringValue
val tag = it.field("tag")?.stringValue
var count = 0
if (!service.isNullOrBlank()) {
count += 1
Expand All @@ -169,6 +185,9 @@ private fun toRawDependency(it: Value): RawDependency {
if (!domainPattern.isNullOrBlank()) {
count += 1
}
if (!tag.isNullOrBlank()) {
count += 1
}
if (count != 1) {
throw NodeMetadataValidationException(
"Define one of: 'service', 'domain' or 'domainPattern' as an outgoing dependency"
Expand All @@ -178,6 +197,7 @@ private fun toRawDependency(it: Value): RawDependency {
service = service,
domain = domain,
domainPattern = domainPattern,
tag = tag,
value = it
)
}
Expand Down Expand Up @@ -499,27 +519,30 @@ data class Outgoing(
private val serviceDependencies: List<ServiceDependency> = emptyList(),
private val domainDependencies: List<DomainDependency> = emptyList(),
private val domainPatternDependencies: List<DomainPatternDependency> = emptyList(),
private val tagDependencies: List<TagDependency> = emptyList(),
val allServicesDependencies: Boolean = false,
val defaultServiceSettings: DependencySettings = DependencySettings()
) {

// not declared in primary constructor to exclude from equals(), copy(), etc.
private val deduplicatedDomainDependencies: List<DomainDependency> = domainDependencies
.map { it.domain to it }
.toMap().values.toList()
private val deduplicatedDomainDependencies: List<DomainDependency> =
domainDependencies.associateBy { it.domain }.values.toList()

private val deduplicatedServiceDependencies: List<ServiceDependency> =
serviceDependencies.associateBy { it.service }.values.toList()

private val deduplicatedServiceDependencies: List<ServiceDependency> = serviceDependencies
.map { it.service to it }
.toMap().values.toList()
private val deduplicatedDomainPatternDependencies: List<DomainPatternDependency> =
domainPatternDependencies.associateBy { it.domainPattern }.values.toList()

private val deduplicatedDomainPatternDependencies: List<DomainPatternDependency> = domainPatternDependencies
.map { it.domainPattern to it }
.toMap().values.toList()
private val deduplicatedTagDependency: List<TagDependency> =
tagDependencies.associateBy { it.tag }.values.toList()

fun getDomainDependencies(): List<DomainDependency> = deduplicatedDomainDependencies
fun getServiceDependencies(): List<ServiceDependency> = deduplicatedServiceDependencies
fun getDomainPatternDependencies(): List<DomainPatternDependency> = deduplicatedDomainPatternDependencies

fun getTagDependencies(): List<TagDependency> = deduplicatedTagDependency

data class TimeoutPolicy(
val idleTimeout: Duration? = null,
val connectionIdleTimeout: Duration? = null,
Expand Down Expand Up @@ -581,6 +604,20 @@ data class DomainPatternDependency(
override fun useSsl() = DEFAULT_HTTPS_POLICY
}

data class TagDependency(
val tag: String,
val settings: DependencySettings = DependencySettings()
) : Dependency {
companion object {
private const val DEFAULT_HTTP_PORT = 80
private const val DEFAULT_HTTPS_POLICY = false
}

override fun getPort() = DEFAULT_HTTP_PORT

override fun useSsl() = DEFAULT_HTTPS_POLICY
}

data class DependencySettings(
val handleInternalRedirect: Boolean = false,
val timeoutPolicy: Outgoing.TimeoutPolicy = Outgoing.TimeoutPolicy(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class AllDependenciesValidationException(serviceName: String?) : NodeMetadataVal
"Blocked service $serviceName from using all dependencies. Only defined services can use all dependencies"
)

class TagDependencyValidationException(serviceName: String?, tags: List<String>) : NodeMetadataValidationException(
"Blocked service $serviceName from using tag dependencies $tags. Only allowed tags are supported."
)

class WildcardPrincipalValidationException(serviceName: String?) : NodeMetadataValidationException(
"Blocked service $serviceName from allowing everyone in incoming permissions. " +
"Only defined services can use that."
Expand Down Expand Up @@ -99,17 +103,26 @@ class NodeMetadataValidator(
if (!properties.outgoingPermissions.enabled) {
return
}
validateEndpointPermissionsMethods(metadata)
if (hasAllServicesDependencies(metadata) && !isAllowedToHaveAllServiceDependencies(metadata)) {
throw AllDependenciesValidationException(metadata.serviceName)
}
if (properties.outgoingPermissions.tagPrefix.isNotBlank()) {
val unsupportedTags = metadata.proxySettings.outgoing.getTagDependencies()
.filter { !it.tag.startsWith(properties.outgoingPermissions.tagPrefix) }
.map { it.tag }
if (unsupportedTags.isNotEmpty()) {
throw TagDependencyValidationException(metadata.serviceName, unsupportedTags)
}
}
}

private fun validateIncomingEndpoints(metadata: NodeMetadata) {
if (!properties.incomingPermissions.enabled) {
return
}

validateEndpointPermissionsMethods(metadata)

metadata.proxySettings.incoming.endpoints.forEach { incomingEndpoint ->
val clients = incomingEndpoint.clients.map { it.name }

Expand Down
Loading