Skip to content

Commit

Permalink
Added cross-namespace validation support for Sidecar's egress listene…
Browse files Browse the repository at this point in the history
…r host, considering exported ServiceEntries.
  • Loading branch information
hhovsepy committed Oct 15, 2021
1 parent b611044 commit b179c84
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 38 deletions.
3 changes: 2 additions & 1 deletion business/checkers/destination_rules_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type DestinationRulesChecker struct {
ExportedDestinationRules []networking_v1alpha3.DestinationRule
MTLSDetails kubernetes.MTLSDetails
ServiceEntries []networking_v1alpha3.ServiceEntry
ExportedServiceEntries []networking_v1alpha3.ServiceEntry
Namespaces []models.Namespace
}

Expand All @@ -31,7 +32,7 @@ func (in DestinationRulesChecker) Check() models.IstioValidations {
func (in DestinationRulesChecker) runGroupChecks() models.IstioValidations {
validations := models.IstioValidations{}

seHosts := kubernetes.ServiceEntryHostnames(in.ServiceEntries)
seHosts := kubernetes.ServiceEntryHostnames(append(in.ServiceEntries, in.ExportedServiceEntries...))

enabledDRCheckers := []GroupChecker{
destinationrules.MultiMatchChecker{Namespaces: in.Namespaces, DestinationRules: in.DestinationRules, ServiceEntries: seHosts, ExportedDestinationRules: in.ExportedDestinationRules},
Expand Down
35 changes: 10 additions & 25 deletions business/checkers/sidecars/egress_listener_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

core_v1 "k8s.io/api/core/v1"

"github.com/kiali/kiali/config"
"github.com/kiali/kiali/kubernetes"
"github.com/kiali/kiali/models"
)
Expand Down Expand Up @@ -62,7 +61,6 @@ func (elc EgressHostChecker) getHosts() ([]HostWithIndex, bool) {

func (elc EgressHostChecker) validateHost(host string, egrIdx, hostIdx int) ([]*models.IstioCheck, bool) {
checks := make([]*models.IstioCheck, 0)
ins := config.Get().IstioNamespace
sns := elc.Sidecar.Namespace

hostNs, dnsName, valid := getHostComponents(host)
Expand All @@ -75,44 +73,31 @@ func (elc EgressHostChecker) validateHost(host string, egrIdx, hostIdx int) ([]*
return checks, true
}

// Show cross-namespace validation
// when namespace is different to both istio control plane or sidecar namespace
if hostNs != ins && hostNs != sns && hostNs != "." {
return append(checks, buildCheck("validation.unable.cross-namespace", egrIdx, hostIdx)), true
// namespace/* is a valid scenario
if dnsName == "*" {
return checks, true
}

// Lookup services when ns is . or sidecar namespace
if hostNs == sns || hostNs == "." {
// namespace/* is a valid scenario
if dnsName == "*" {
return checks, true
}

// Parse the dnsName to a kubernetes Host
fqdn := kubernetes.ParseHost(dnsName, sns, elc.Sidecar.ClusterName)
if fqdn.Namespace != sns && fqdn.Namespace != "" {
return append(checks, buildCheck("validation.unable.cross-namespace", egrIdx, hostIdx)), true
}
fqdn := kubernetes.ParseHost(dnsName, sns, elc.Sidecar.ClusterName)

// Lookup for matching services
if !elc.HasMatchingService(fqdn, sns) {
checks = append(checks, buildCheck("sidecar.egress.servicenotfound", egrIdx, hostIdx))
}
// Lookup for matching services
if !elc.HasMatchingService(fqdn, sns) {
checks = append(checks, buildCheck("sidecar.egress.servicenotfound", egrIdx, hostIdx))
}

return checks, true
}

func (elc EgressHostChecker) HasMatchingService(host kubernetes.Host, itemNamespace string) bool {
if strings.HasPrefix(host.Service, "*") {
// Check wildcard hosts - needs to match "*" and "*.suffix" also.
if host.IsWildcard() && host.Namespace == itemNamespace {
return true
}

if kubernetes.HasMatchingServices(host.Service, elc.Services) {
return true
}

return kubernetes.HasMatchingServiceEntries(host.Service, elc.ServiceEntries)
return kubernetes.HasMatchingServiceEntries(host.String(), elc.ServiceEntries)
}

func getHostComponents(host string) (string, string, bool) {
Expand Down
158 changes: 154 additions & 4 deletions business/checkers/sidecars/egress_listener_checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestEgressHostFormatCorrect(t *testing.T) {
"~/*",
"./*",
"./reviews.bookinfo.svc.cluster.local",
"./*.bookinfo.svc.cluster.com",
"./*.bookinfo.svc.cluster.local",
"./wikipedia.org",
"bookinfo/*",
"bookinfo/*.bookinfo.svc.cluster.local",
Expand All @@ -39,7 +39,157 @@ func TestEgressHostFormatCorrect(t *testing.T) {
assert.True(valid)
}

func TestEgressHostCrossNamespace(t *testing.T) {
func TestEgressExportedInternalServiceEntryPresent(t *testing.T) {
assert := assert.New(t)

vals, valid := EgressHostChecker{
Services: []core_v1.Service{},
ServiceEntries: kubernetes.ServiceEntryHostnames([]networking_v1alpha3.ServiceEntry{*data.CreateEmptyMeshInternalServiceEntry("details-se", "bookinfo3", []string{"details.bookinfo2.svc.cluster.local"})}),
Sidecar: *sidecarWithHosts([]string{
"bookinfo/details.bookinfo2.svc.cluster.local",
}),
}.Check()

assert.Empty(vals)
assert.True(valid)
}

func TestEgressExportedExternalServiceEntryPresent(t *testing.T) {
assert := assert.New(t)

vals, valid := EgressHostChecker{
Services: []core_v1.Service{},
ServiceEntries: kubernetes.ServiceEntryHostnames([]networking_v1alpha3.ServiceEntry{*data.CreateEmptyMeshExternalServiceEntry("details-se", "bookinfo3", []string{"www.myhost.com"})}),
Sidecar: *sidecarWithHosts([]string{
"bookinfo/www.myhost.com",
}),
}.Check()

assert.Empty(vals)
assert.True(valid)
}

func TestWildcardHostEgressExportedExternalServiceEntryNotPresent(t *testing.T) {
assert := assert.New(t)

vals, valid := EgressHostChecker{
Services: []core_v1.Service{},
ServiceEntries: kubernetes.ServiceEntryHostnames([]networking_v1alpha3.ServiceEntry{*data.CreateEmptyMeshExternalServiceEntry("details-se", "bookinfo3", []string{"www.myhost.com"})}),
Sidecar: *sidecarWithHosts([]string{
"bookinfo/*.myhost.com",
}),
}.Check()

assert.NotEmpty(vals)
assert.True(valid)
assert.Equal(models.WarningSeverity, vals[0].Severity)
assert.Equal("spec/egress[0]/hosts[0]", vals[0].Path)
assert.NoError(validations.ConfirmIstioCheckMessage("sidecar.egress.servicenotfound", vals[0]))
}

func TestEgressExportedExternalWildcardServiceEntryPresent(t *testing.T) {
assert := assert.New(t)

vals, valid := EgressHostChecker{
Services: []core_v1.Service{},
ServiceEntries: kubernetes.ServiceEntryHostnames([]networking_v1alpha3.ServiceEntry{*data.CreateEmptyMeshExternalServiceEntry("details-se", "bookinfo3", []string{"*.myhost.com"})}),
Sidecar: *sidecarWithHosts([]string{
"bookinfo/www.myhost.com",
}),
}.Check()

assert.Empty(vals)
assert.True(valid)
}

func TestEgressExportedInternalServiceEntryNotPresent(t *testing.T) {
assert := assert.New(t)

vals, valid := EgressHostChecker{
Services: []core_v1.Service{},
ServiceEntries: kubernetes.ServiceEntryHostnames([]networking_v1alpha3.ServiceEntry{*data.CreateEmptyMeshInternalServiceEntry("details-se", "bookinfo3", []string{"details.bookinfo2.svc.cluster.local"})}),
Sidecar: *sidecarWithHosts([]string{
"bookinfo/details.bookinfo.svc.cluster.local",
}),
}.Check()

assert.NotEmpty(vals)
assert.True(valid)
assert.Equal(models.WarningSeverity, vals[0].Severity)
assert.Equal("spec/egress[0]/hosts[0]", vals[0].Path)
assert.NoError(validations.ConfirmIstioCheckMessage("sidecar.egress.servicenotfound", vals[0]))
}

func TestEgressExportedExternalServiceEntryNotPresent(t *testing.T) {
assert := assert.New(t)

vals, valid := EgressHostChecker{
Services: []core_v1.Service{},
ServiceEntries: kubernetes.ServiceEntryHostnames([]networking_v1alpha3.ServiceEntry{*data.CreateEmptyMeshExternalServiceEntry("details-se", "bookinfo3", []string{"www.myhost.com"})}),
Sidecar: *sidecarWithHosts([]string{
"bookinfo/www.wrong.com",
}),
}.Check()

assert.NotEmpty(vals)
assert.True(valid)
assert.Equal(models.WarningSeverity, vals[0].Severity)
assert.Equal("spec/egress[0]/hosts[0]", vals[0].Path)
assert.NoError(validations.ConfirmIstioCheckMessage("sidecar.egress.servicenotfound", vals[0]))
}

func TestEgressExportedWildcardInternalServiceEntryPresent(t *testing.T) {
assert := assert.New(t)

vals, valid := EgressHostChecker{
Services: []core_v1.Service{},
ServiceEntries: kubernetes.ServiceEntryHostnames([]networking_v1alpha3.ServiceEntry{*data.CreateEmptyMeshInternalServiceEntry("details-se", "bookinfo3", []string{"*.bookinfo2.svc.cluster.local"})}),
Sidecar: *sidecarWithHosts([]string{
"bookinfo/details.bookinfo2.svc.cluster.local",
}),
}.Check()

assert.Empty(vals)
assert.True(valid)
}

func TestEgressExportedWildcardInternalServiceEntryNotPresent(t *testing.T) {
assert := assert.New(t)

vals, valid := EgressHostChecker{
Services: []core_v1.Service{},
ServiceEntries: kubernetes.ServiceEntryHostnames([]networking_v1alpha3.ServiceEntry{*data.CreateEmptyMeshInternalServiceEntry("details-se", "bookinfo3", []string{"*.bookinfo3.svc.cluster.local"})}),
Sidecar: *sidecarWithHosts([]string{
"bookinfo/*.bookinfo2.svc.cluster.local",
}),
}.Check()

assert.NotEmpty(vals)
assert.True(valid)
assert.Equal(models.WarningSeverity, vals[0].Severity)
assert.Equal("spec/egress[0]/hosts[0]", vals[0].Path)
assert.NoError(validations.ConfirmIstioCheckMessage("sidecar.egress.servicenotfound", vals[0]))
}

func TestEgressExportedNonFQDNInternalServiceEntryNotPresent(t *testing.T) {
assert := assert.New(t)

vals, valid := EgressHostChecker{
Services: []core_v1.Service{},
ServiceEntries: kubernetes.ServiceEntryHostnames([]networking_v1alpha3.ServiceEntry{*data.CreateEmptyMeshInternalServiceEntry("details-se", "bookinfo3", []string{"details"})}),
Sidecar: *sidecarWithHosts([]string{
"bookinfo/details.bookinfo2.svc.cluster.local",
}),
}.Check()

assert.NotEmpty(vals)
assert.True(valid)
assert.Equal(models.WarningSeverity, vals[0].Severity)
assert.Equal("spec/egress[0]/hosts[0]", vals[0].Path)
assert.NoError(validations.ConfirmIstioCheckMessage("sidecar.egress.servicenotfound", vals[0]))
}

func TestEgressHostCrossNamespaceServiceNotFound(t *testing.T) {
assert := assert.New(t)

hosts := []string{
Expand All @@ -62,9 +212,9 @@ func TestEgressHostCrossNamespace(t *testing.T) {
assert.True(valid)

for i, c := range vals {
assert.Equal(models.Unknown, c.Severity)
assert.Equal(models.WarningSeverity, c.Severity)
assert.Equal(fmt.Sprintf("spec/egress[0]/hosts[%d]", i), c.Path)
assert.NoError(validations.ConfirmIstioCheckMessage("validation.unable.cross-namespace", c))
assert.NoError(validations.ConfirmIstioCheckMessage("sidecar.egress.servicenotfound", c))
}
}

Expand Down
14 changes: 8 additions & 6 deletions business/checkers/sidecars_checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import (
const SidecarCheckerType = "sidecar"

type SidecarChecker struct {
Sidecars []networking_v1alpha3.Sidecar
ServiceEntries []networking_v1alpha3.ServiceEntry
Services []core_v1.Service
Namespaces models.Namespaces
WorkloadList models.WorkloadList
Sidecars []networking_v1alpha3.Sidecar
ServiceEntries []networking_v1alpha3.ServiceEntry
ExportedServiceEntries []networking_v1alpha3.ServiceEntry
Services []core_v1.Service
Namespaces models.Namespaces
WorkloadList models.WorkloadList
}

func (s SidecarChecker) Check() models.IstioValidations {
Expand Down Expand Up @@ -57,11 +58,12 @@ func (s SidecarChecker) runIndividualChecks() models.IstioValidations {
func (s SidecarChecker) runChecks(sidecar networking_v1alpha3.Sidecar) models.IstioValidations {
policyName := sidecar.Name
key, rrValidation := EmptyValidValidation(policyName, sidecar.Namespace, SidecarCheckerType)
serviceHosts := kubernetes.ServiceEntryHostnames(s.ServiceEntries)
serviceHosts := kubernetes.ServiceEntryHostnames(append(s.ServiceEntries, s.ExportedServiceEntries...))
selectorLabels := make(map[string]string)
if sidecar.Spec.WorkloadSelector != nil {
selectorLabels = sidecar.Spec.WorkloadSelector.Labels
}

enabledCheckers := []Checker{
common.WorkloadSelectorNoWorkloadFoundChecker(SidecarCheckerType, selectorLabels, s.WorkloadList),
sidecars.EgressHostChecker{Sidecar: sidecar, Services: s.Services, ServiceEntries: serviceHosts},
Expand Down
4 changes: 2 additions & 2 deletions business/istio_validations.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func (in *IstioValidationsService) getAllObjectCheckers(namespace string, istioC
checkers.PeerAuthenticationChecker{PeerAuthentications: mtlsDetails.PeerAuthentications, MTLSDetails: mtlsDetails, WorkloadList: workloads},
checkers.ServiceEntryChecker{ServiceEntries: istioConfigList.ServiceEntries, Namespaces: namespaces},
checkers.AuthorizationPolicyChecker{AuthorizationPolicies: rbacDetails.AuthorizationPolicies, Namespace: namespace, Namespaces: namespaces, Services: services, ServiceEntries: istioConfigList.ServiceEntries, ExportedServiceEntries: exportedResources.ServiceEntries, WorkloadList: workloads, MtlsDetails: mtlsDetails, VirtualServices: istioConfigList.VirtualServices, RegistryStatus: registryStatus},
checkers.SidecarChecker{Sidecars: istioConfigList.Sidecars, Namespaces: namespaces, WorkloadList: workloads, Services: services, ServiceEntries: istioConfigList.ServiceEntries},
checkers.SidecarChecker{Sidecars: istioConfigList.Sidecars, Namespaces: namespaces, WorkloadList: workloads, Services: services, ServiceEntries: istioConfigList.ServiceEntries, ExportedServiceEntries: exportedResources.ServiceEntries},
checkers.RequestAuthenticationChecker{RequestAuthentications: istioConfigList.RequestAuthentications, WorkloadList: workloads},
}
}
Expand Down Expand Up @@ -189,7 +189,7 @@ func (in *IstioValidationsService) GetIstioObjectValidations(namespace string, o
objectCheckers = []ObjectChecker{serviceEntryChecker}
case kubernetes.Sidecars:
sidecarsChecker := checkers.SidecarChecker{Sidecars: istioConfigList.Sidecars, Namespaces: namespaces,
WorkloadList: workloads, Services: services, ServiceEntries: istioConfigList.ServiceEntries}
WorkloadList: workloads, Services: services, ServiceEntries: istioConfigList.ServiceEntries, ExportedServiceEntries: exportedResources.ServiceEntries}
objectCheckers = []ObjectChecker{sidecarsChecker}
case kubernetes.AuthorizationPolicies:
authPoliciesChecker := checkers.AuthorizationPolicyChecker{AuthorizationPolicies: rbacDetails.AuthorizationPolicies,
Expand Down

0 comments on commit b179c84

Please sign in to comment.