From db598485803960865670ee35f92007678e3b9e97 Mon Sep 17 00:00:00 2001 From: Chen Keinan Date: Sat, 23 Oct 2021 17:32:00 +0300 Subject: [PATCH] feat: add ingress gateway connection limit spec --- README.md | 5 +++ internal/cli/cli_test.go | 21 +++++++----- internal/cli/commands/mesh-check.go | 4 +-- internal/common/globalconsts.go | 6 ++++ ...onfigure_limit_downstream_connections .yml | 33 +++++++++++++++++++ ...nstream_connection_limit_config_map.policy | 12 +++++++ ...patched_downstream_connection_limit.policy | 14 ++++++++ internal/startup/templates.go | 20 ++++++++++- internal/startup/templates_test.go | 19 ++++++----- ui/banners.go | 1 + 10 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 internal/security/mesh/istio/7_configure_limit_downstream_connections .yml create mode 100644 internal/security/mesh/istio/downstream_connection_limit_config_map.policy create mode 100644 internal/security/mesh/istio/ingress_gateway_patched_downstream_connection_limit.policy diff --git a/README.md b/README.md index ebca3e5..458a1a4 100644 --- a/README.md +++ b/README.md @@ -100,4 +100,9 @@ Execute tests and generate failure tests report and it remediation's restrict creation of Gateway resources to trusted cluster administrators may cause creation of gateway by untrusted users + + Configure a limit on downstream connections + Update global_downstream_max_connections in the config map according to the number of concurrent connections needed by individual gateway instances in your deployment. Once the limit is reached, Envoy will start rejecting tcp connections + no limit on the number of downstream connections can cause exploited by a malicious actor + diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index ef14345..db33a06 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -25,21 +25,24 @@ func Test_StartCli(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, len(files), 14) + assert.Equal(t, len(files), 17) assert.Equal(t, files[0].Name, common.IstioMutualmTLS) assert.Equal(t, files[1].Name, common.SaferAuthorizationPolicyPatterns) assert.Equal(t, files[2].Name, common.TLSOriginationForEgressTraffic) assert.Equal(t, files[3].Name, common.ProtocolDetection) assert.Equal(t, files[4].Name, common.Cni) assert.Equal(t, files[5].Name, common.Gateway) - assert.Equal(t, files[6].Name, common.AllowMtlsPermissiveMode) - assert.Equal(t, files[7].Name, common.AvoidOverlyBroadHostsConfigurations) - assert.Equal(t, files[8].Name, common.DestinationRulePerformTLSOrigination) - assert.Equal(t, files[9].Name, common.DetectByProtocol) - assert.Equal(t, files[10].Name, common.PathNormalizationInAuthorization) - assert.Equal(t, files[11].Name, common.PodCapabilitiesExist) - assert.Equal(t, files[12].Name, common.RestrictGatewayCreationPrivileges) - assert.Equal(t, files[13].Name, common.SaferAuthorizationPolicyPatternsPolicy) + assert.Equal(t, files[6].Name, common.ConfigureLimitDownstreamConnections) + assert.Equal(t, files[7].Name, common.AllowMtlsPermissiveMode) + assert.Equal(t, files[8].Name, common.AvoidOverlyBroadHostsConfigurations) + assert.Equal(t, files[9].Name, common.DestinationRulePerformTLSOrigination) + assert.Equal(t, files[10].Name, common.DetectByProtocol) + assert.Equal(t, files[11].Name, common.DownstreamConnectionLimitConfigMap) + assert.Equal(t, files[12].Name, common.IngressGatewayPatchedDownstreamConnectionLimit) + assert.Equal(t, files[13].Name, common.PathNormalizationInAuthorization) + assert.Equal(t, files[14].Name, common.PodCapabilitiesExist) + assert.Equal(t, files[15].Name, common.RestrictGatewayCreationPrivileges) + assert.Equal(t, files[16].Name, common.SaferAuthorizationPolicyPatternsPolicy) } func Test_ArgsSanitizer(t *testing.T) { diff --git a/internal/cli/commands/mesh-check.go b/internal/cli/commands/mesh-check.go index 775e939..b05f25d 100644 --- a/internal/cli/commands/mesh-check.go +++ b/internal/cli/commands/mesh-check.go @@ -90,9 +90,9 @@ func calculateFinalTotal(granTotal []models.CheckTotals) models.CheckTotals { var ReportOutputGenerator ui.OutputGenerator = func(at []*models.SubCategory, log *logger.MeshKridikLogger) { log.Console((ui.RemediationReport)) s := spinner.New(spinner.CharSets[9], 100*time.Millisecond) // Build our new spinner - s.Prefix = fmt.Sprintf("[Generating Remediation Report] ") + s.Prefix = "[Generating Remediation Report] " s.Start() - time.Sleep(time.Second *4) + time.Sleep(time.Second * 4) s.Stop() log.Console("\n") for _, a := range at { diff --git a/internal/common/globalconsts.go b/internal/common/globalconsts.go index 71d613d..7408d24 100644 --- a/internal/common/globalconsts.go +++ b/internal/common/globalconsts.go @@ -23,12 +23,18 @@ const ( PodCapabilitiesExist = "pod_capabilities_exist.policy" //Gateway spec Gateway = "6_gateway.yml" + //ConfigureLimitDownstreamConnections spec + ConfigureLimitDownstreamConnections = "7_configure_limit_downstream_connections .yml" //AvoidOverlyBroadHostsConfigurations policy name AvoidOverlyBroadHostsConfigurations = "avoid_overly_broad_hosts_configurations.policy" //RestrictGatewayCreationPrivileges policy name RestrictGatewayCreationPrivileges = "restrict_gateway_creation_privileges.policy" //PathNormalizationInAuthorization policy name PathNormalizationInAuthorization = "path_normalization_in_authorization.policy" + //DownstreamConnectionLimitConfigMap policy name + DownstreamConnectionLimitConfigMap = "downstream_connection_limit_config_map.policy" + //IngressGatewayPatchedDownstreamConnectionLimit policy name + IngressGatewayPatchedDownstreamConnectionLimit = "ingress_gateway_patched_downstream_connection_limit.policy" //Report arg Report = "r" //ReportFull arg diff --git a/internal/security/mesh/istio/7_configure_limit_downstream_connections .yml b/internal/security/mesh/istio/7_configure_limit_downstream_connections .yml new file mode 100644 index 0000000..6c8fa6d --- /dev/null +++ b/internal/security/mesh/istio/7_configure_limit_downstream_connections .yml @@ -0,0 +1,33 @@ +--- +benchmark_type: mesh +categories: + - + name: istio Security Checks + sub_category: + name: Downstream Connections + security_checks: + - + name: 'make sure config map with downstream Connections created' + description: 'Update global_downstream_max_connections in the config map according to the number of concurrent connections needed by individual gateway instances in your deployment. Once the limit is reached, Envoy will start rejecting tcp connections.' + check_command: + - 'kubectl get configmap istio-custom-bootstrap-config -n istio-system -o json 2> /dev/null' + remediation: 'create config map with downstream Connections created' + check_type: multi_param + impact: not al net traffic will not be capture + eval_expr: "[${0} MATCH downstream_connection_limit_config_map.policy QUERY istio.policy_eval RETURN match];" + default_value: 'By default, Istio (and Envoy) have no limit on the number of downstream connections' + eval_message: 'config map istio-custom-bootstrap-config is exist on namespace istio-system' + references: + - https://istio.io/latest/docs/ops/best-practices/security/#configure-a-limit-on-downstream-connections + - name: 'make ingress gateway deployment is patched with downstream Connections limit config' + description: 'Patch ingress gateway with downstream Connections limit config Once the limit is reached, Envoy will start rejecting tcp connections.' + check_command: + - 'kubectl get deployment istio-ingressgateway -n istio-system -o json 2> /dev/null' + remediation: 'Patch the ingress gateway deployment to use the above configuration. Download gateway-patch.yaml and apply it using the following command.' + check_type: multi_param + impact: not al net traffic will not be capture + eval_expr: "[${0} MATCH ingress_gateway_patched_downstream_connection_limit.policy QUERY istio.policy_eval RETURN match];" + default_value: 'By default, Istio (and Envoy) have no limit on the number of downstream connections' + eval_message: 'deployment istio-ingressgateway is patched with istio-custom-bootstrap-config config map' + references: + - https://istio.io/latest/docs/ops/best-practices/security/#configure-a-limit-on-downstream-connections diff --git a/internal/security/mesh/istio/downstream_connection_limit_config_map.policy b/internal/security/mesh/istio/downstream_connection_limit_config_map.policy new file mode 100644 index 0000000..8dcd199 --- /dev/null +++ b/internal/security/mesh/istio/downstream_connection_limit_config_map.policy @@ -0,0 +1,12 @@ +package istio + +policy_eval = {"match":allow_policy} { + allow_policy = downstream_connections_config_map_exist + } + + default downstream_connections_config_map_exist = false + downstream_connections_config_map_exist { + input.kind == "ConfigMap" + input.metadata.name == "istio-custom-bootstrap-config" + input.data["custom_bootstrap.json"] + } diff --git a/internal/security/mesh/istio/ingress_gateway_patched_downstream_connection_limit.policy b/internal/security/mesh/istio/ingress_gateway_patched_downstream_connection_limit.policy new file mode 100644 index 0000000..7c88b77 --- /dev/null +++ b/internal/security/mesh/istio/ingress_gateway_patched_downstream_connection_limit.policy @@ -0,0 +1,14 @@ +package istio + +policy_eval = {"match":allow_policy} { + allow_policy = downstream_connections_exist + } + + default downstream_connections_exist = false + downstream_connections_exist { + input.kind == "Deployment" + input.metadata.name == "istio-ingressgateway" + some i + input.spec.template.spec.volumes[i].configMap.name == "istio-custom-bootstrap-config" + } + diff --git a/internal/startup/templates.go b/internal/startup/templates.go index c48a71a..206f093 100644 --- a/internal/startup/templates.go +++ b/internal/startup/templates.go @@ -92,12 +92,30 @@ func GenerateMeshSecurityFiles() ([]utils.FilesInfo, error) { return []utils.FilesInfo{}, fmt.Errorf("faild to load security checks %s %s", common.RestrictGatewayCreationPrivileges, err.Error()) } fileInfo = append(fileInfo, utils.FilesInfo{Name: common.RestrictGatewayCreationPrivileges, Data: rgcp}) - //12 + //13 pnla, err := box.FindString(common.PathNormalizationInAuthorization) if err != nil { return []utils.FilesInfo{}, fmt.Errorf("faild to load security checks %s %s", common.PathNormalizationInAuthorization, err.Error()) } fileInfo = append(fileInfo, utils.FilesInfo{Name: common.PathNormalizationInAuthorization, Data: pnla}) + //14 + cldc, err := box.FindString(common.ConfigureLimitDownstreamConnections) + if err != nil { + return []utils.FilesInfo{}, fmt.Errorf("faild to load security checks %s %s", common.ConfigureLimitDownstreamConnections, err.Error()) + } + fileInfo = append(fileInfo, utils.FilesInfo{Name: common.ConfigureLimitDownstreamConnections, Data: cldc}) + //15 + dclc, err := box.FindString(common.DownstreamConnectionLimitConfigMap) + if err != nil { + return []utils.FilesInfo{}, fmt.Errorf("faild to load security checks %s %s", common.DownstreamConnectionLimitConfigMap, err.Error()) + } + fileInfo = append(fileInfo, utils.FilesInfo{Name: common.DownstreamConnectionLimitConfigMap, Data: dclc}) + //16 + ipcl, err := box.FindString(common.IngressGatewayPatchedDownstreamConnectionLimit) + if err != nil { + return []utils.FilesInfo{}, fmt.Errorf("faild to load security checks %s %s", common.IngressGatewayPatchedDownstreamConnectionLimit, err.Error()) + } + fileInfo = append(fileInfo, utils.FilesInfo{Name: common.IngressGatewayPatchedDownstreamConnectionLimit, Data: ipcl}) return fileInfo, nil } diff --git a/internal/startup/templates_test.go b/internal/startup/templates_test.go index f0ee53f..e258cc2 100644 --- a/internal/startup/templates_test.go +++ b/internal/startup/templates_test.go @@ -30,14 +30,17 @@ func Test_CreateMeshSecurityFilesIfNotExist(t *testing.T) { assert.Equal(t, bFiles[3].Name, common.ProtocolDetection) assert.Equal(t, bFiles[4].Name, common.Cni) assert.Equal(t, bFiles[5].Name, common.Gateway) - assert.Equal(t, bFiles[6].Name, common.AllowMtlsPermissiveMode) - assert.Equal(t, bFiles[7].Name, common.AvoidOverlyBroadHostsConfigurations) - assert.Equal(t, bFiles[8].Name, common.DestinationRulePerformTLSOrigination) - assert.Equal(t, bFiles[9].Name, common.DetectByProtocol) - assert.Equal(t, bFiles[10].Name, common.PathNormalizationInAuthorization) - assert.Equal(t, bFiles[11].Name, common.PodCapabilitiesExist) - assert.Equal(t, bFiles[12].Name, common.RestrictGatewayCreationPrivileges) - assert.Equal(t, bFiles[13].Name, common.SaferAuthorizationPolicyPatternsPolicy) + assert.Equal(t, bFiles[6].Name, common.ConfigureLimitDownstreamConnections) + assert.Equal(t, bFiles[7].Name, common.AllowMtlsPermissiveMode) + assert.Equal(t, bFiles[8].Name, common.AvoidOverlyBroadHostsConfigurations) + assert.Equal(t, bFiles[9].Name, common.DestinationRulePerformTLSOrigination) + assert.Equal(t, bFiles[10].Name, common.DetectByProtocol) + assert.Equal(t, bFiles[11].Name, common.DownstreamConnectionLimitConfigMap) + assert.Equal(t, bFiles[12].Name, common.IngressGatewayPatchedDownstreamConnectionLimit) + assert.Equal(t, bFiles[13].Name, common.PathNormalizationInAuthorization) + assert.Equal(t, bFiles[14].Name, common.PodCapabilitiesExist) + assert.Equal(t, bFiles[15].Name, common.RestrictGatewayCreationPrivileges) + assert.Equal(t, bFiles[16].Name, common.SaferAuthorizationPolicyPatternsPolicy) assert.NoError(t, err) err = os.RemoveAll(utils.GetHomeFolder()) diff --git a/ui/banners.go b/ui/banners.go index 9e1f112..d5ee415 100644 --- a/ui/banners.go +++ b/ui/banners.go @@ -20,6 +20,7 @@ const EmptyLines = ` ` + //RemediationReport banner const RemediationReport = `