Repository: dragonflyoss/Dragonfly2
Pull request #806 (2021-11-18 15:45:18)

feat: add security rule
<!--- Provide a general summary of your changes in the Title above -->

## Description
- Add security rule model and interface.
- optimize manager searcher.
<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## Screenshots (if appropriate):

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation Update (if none of the other choices apply)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [x] Code compiles correctly.
- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] I have read the **CONTRIBUTING** document.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.


codecov-commenter: # [Codecov](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/806?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) Report
> Merging [#806](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/806?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (5b3c555) into [main](https://codecov.io/gh/dragonflyoss/Dragonfly2/commit/08b8c2b9668092941872dbc97a0113fc87e4f05c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) (08b8c2b) will **decrease** coverage by `0.04%`.
> The diff coverage is `54.05%`.

> :exclamation: Current head 5b3c555 differs from pull request most recent head 9ab8ce0. Consider uploading reports for the commit 9ab8ce0 to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/806/graphs/tree.svg?width=650&height=150&src=pr&token=PUICJYSAT7&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/806?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss)

```diff
@@            Coverage Diff             @@
##             main     #806      +/-   ##
==========================================
- Coverage   41.06%   41.01%   -0.05%     
==========================================
  Files          98       98              
  Lines        7893     7904      +11     
==========================================
+ Hits         3241     3242       +1     
- Misses       4245     4257      +12     
+ Partials      407      405       -2     
```

| Flag | Coverage Δ | |
|---|---|---|
| Object-compatibility-e2etests | `?` | |
| e2etests | `?` | |
| unittests | `41.01% <54.05%> (-0.05%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/806?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss) | Coverage Δ | |
|---|---|---|
| [manager/searcher/searcher.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/806/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-bWFuYWdlci9zZWFyY2hlci9zZWFyY2hlci5nbw==) | `60.00% <54.05%> (-22.06%)` | :arrow_down: |
| [client/daemon/peer/peertask\_stream.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/806/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BlZXJ0YXNrX3N0cmVhbS5nbw==) | `54.12% <0.00%> (+0.30%)` | :arrow_up: |
| [client/daemon/peer/piece\_manager.go](https://codecov.io/gh/dragonflyoss/Dragonfly2/pull/806/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=dragonflyoss#diff-Y2xpZW50L2RhZW1vbi9wZWVyL3BpZWNlX21hbmFnZXIuZ28=) | `68.88% <0.00%> (+0.74%)` | :arrow_up: |


Patch:
diff --git a/api/manager/docs.go b/api/manager/docs.go
index 3a8c1119..6a70588e 100644
--- a/api/manager/docs.go
+++ b/api/manager/docs.go
@@ -2342,6 +2342,279 @@ var doc = `{
                 }
             }
         },
+        "/security-groups/{id}/security-rules/{security_rule_id}": {
+            "put": {
+                "description": "Add SecurityRule to SecurityGroup",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityGroup"
+                ],
+                "summary": "Add SecurityRule to SecurityGroup",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "string",
+                        "description": "security rule id",
+                        "name": "security_rule_id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": ""
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            },
+            "delete": {
+                "description": "Destroy SecurityRule to SecurityGroup",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityGroup"
+                ],
+                "summary": "Destroy SecurityRule to SecurityGroup",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "string",
+                        "description": "security rule id",
+                        "name": "security_rule_id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": ""
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            }
+        },
+        "/security-rules": {
+            "get": {
+                "description": "Get SecurityRules",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Get SecurityRules",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "default": 0,
+                        "description": "current page",
+                        "name": "page",
+                        "in": "query",
+                        "required": true
+                    },
+                    {
+                        "maximum": 50,
+                        "minimum": 2,
+                        "type": "integer",
+                        "default": 10,
+                        "description": "return max item count, default 10, max 50",
+                        "name": "per_page",
+                        "in": "query",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/model.SecurityRule"
+                            }
+                        }
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            },
+            "post": {
+                "description": "create by json config",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Create SecurityRule",
+                "parameters": [
+                    {
+                        "description": "SecurityRule",
+                        "name": "SecurityRule",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/types.CreateSecurityRuleRequest"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/model.SecurityRule"
+                        }
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            }
+        },
+        "/security-rules/{id}": {
+            "get": {
+                "description": "Get SecurityRule by id",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Get SecurityRule",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/model.SecurityRule"
+                        }
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            },
+            "patch": {
+                "description": "Update by json config",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Update SecurityRule",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "description": "SecurityRule",
+                        "name": "SecurityRule",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/types.UpdateSecurityRuleRequest"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/model.SecurityRule"
+                        }
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            }
+        },
         "/securityGroups/{id}": {
             "delete": {
                 "description": "Destroy by id",
@@ -2380,6 +2653,44 @@ var doc = `{
                 }
             }
         },
+        "/securityRules/{id}": {
+            "delete": {
+                "description": "Destroy by id",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Destroy SecurityRule",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": ""
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            }
+        },
         "/user/signin/{name}": {
             "get": {
                 "description": "oauth signin by json config",
@@ -3026,6 +3337,32 @@ var doc = `{
             }
         },
         "model.SecurityGroup": {
+            "type": "object",
+            "properties": {
+                "bio": {
+                    "type": "string"
+                },
+                "created_at": {
+                    "type": "string"
+                },
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "type": "string"
+                },
+                "security_rules": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.SecurityRule"
+                    }
+                },
+                "updated_at": {
+                    "type": "string"
+                }
+            }
+        },
+        "model.SecurityRule": {
             "type": "object",
             "properties": {
                 "bio": {
@@ -3046,6 +3383,12 @@ var doc = `{
                 "proxy_domain": {
                     "type": "string"
                 },
+                "security_groups": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.SecurityGroup"
+                    }
+                },
                 "updated_at": {
                     "type": "string"
                 }
@@ -3142,9 +3485,6 @@ var doc = `{
                 },
                 "name": {
                     "type": "string"
-                },
-                "security_group_domain": {
-                    "type": "string"
                 }
             }
         },
@@ -3312,9 +3652,6 @@ var doc = `{
                 },
                 "scopes": {
                     "$ref": "#/definitions/types.SchedulerClusterScopes"
-                },
-                "security_group_domain": {
-                    "type": "string"
                 }
             }
         },
@@ -3356,6 +3693,20 @@ var doc = `{
             }
         },
         "types.CreateSecurityGroupRequest": {
+            "type": "object",
+            "required": [
+                "name"
+            ],
+            "properties": {
+                "bio": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.CreateSecurityRuleRequest": {
             "type": "object",
             "required": [
                 "domain",
@@ -3511,9 +3862,6 @@ var doc = `{
                 },
                 "name": {
                     "type": "string"
-                },
-                "security_group_domain": {
-                    "type": "string"
                 }
             }
         },
@@ -3611,9 +3959,6 @@ var doc = `{
                 },
                 "scopes": {
                     "$ref": "#/definitions/types.SchedulerClusterScopes"
-                },
-                "security_group_domain": {
-                    "type": "string"
                 }
             }
         },
@@ -3648,6 +3993,17 @@ var doc = `{
             }
         },
         "types.UpdateSecurityGroupRequest": {
+            "type": "object",
+            "properties": {
+                "bio": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.UpdateSecurityRuleRequest": {
             "type": "object",
             "properties": {
                 "bio": {
diff --git a/api/manager/swagger.json b/api/manager/swagger.json
index 9e55e8f5..c885d652 100644
--- a/api/manager/swagger.json
+++ b/api/manager/swagger.json
@@ -2328,6 +2328,279 @@
                 }
             }
         },
+        "/security-groups/{id}/security-rules/{security_rule_id}": {
+            "put": {
+                "description": "Add SecurityRule to SecurityGroup",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityGroup"
+                ],
+                "summary": "Add SecurityRule to SecurityGroup",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "string",
+                        "description": "security rule id",
+                        "name": "security_rule_id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": ""
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            },
+            "delete": {
+                "description": "Destroy SecurityRule to SecurityGroup",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityGroup"
+                ],
+                "summary": "Destroy SecurityRule to SecurityGroup",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "type": "string",
+                        "description": "security rule id",
+                        "name": "security_rule_id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": ""
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            }
+        },
+        "/security-rules": {
+            "get": {
+                "description": "Get SecurityRules",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Get SecurityRules",
+                "parameters": [
+                    {
+                        "type": "integer",
+                        "default": 0,
+                        "description": "current page",
+                        "name": "page",
+                        "in": "query",
+                        "required": true
+                    },
+                    {
+                        "maximum": 50,
+                        "minimum": 2,
+                        "type": "integer",
+                        "default": 10,
+                        "description": "return max item count, default 10, max 50",
+                        "name": "per_page",
+                        "in": "query",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "type": "array",
+                            "items": {
+                                "$ref": "#/definitions/model.SecurityRule"
+                            }
+                        }
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            },
+            "post": {
+                "description": "create by json config",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Create SecurityRule",
+                "parameters": [
+                    {
+                        "description": "SecurityRule",
+                        "name": "SecurityRule",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/types.CreateSecurityRuleRequest"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/model.SecurityRule"
+                        }
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            }
+        },
+        "/security-rules/{id}": {
+            "get": {
+                "description": "Get SecurityRule by id",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Get SecurityRule",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/model.SecurityRule"
+                        }
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            },
+            "patch": {
+                "description": "Update by json config",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Update SecurityRule",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    },
+                    {
+                        "description": "SecurityRule",
+                        "name": "SecurityRule",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/types.UpdateSecurityRuleRequest"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/model.SecurityRule"
+                        }
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            }
+        },
         "/securityGroups/{id}": {
             "delete": {
                 "description": "Destroy by id",
@@ -2366,6 +2639,44 @@
                 }
             }
         },
+        "/securityRules/{id}": {
+            "delete": {
+                "description": "Destroy by id",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "SecurityRule"
+                ],
+                "summary": "Destroy SecurityRule",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "id",
+                        "name": "id",
+                        "in": "path",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": ""
+                    },
+                    "400": {
+                        "description": ""
+                    },
+                    "404": {
+                        "description": ""
+                    },
+                    "500": {
+                        "description": ""
+                    }
+                }
+            }
+        },
         "/user/signin/{name}": {
             "get": {
                 "description": "oauth signin by json config",
@@ -3012,6 +3323,32 @@
             }
         },
         "model.SecurityGroup": {
+            "type": "object",
+            "properties": {
+                "bio": {
+                    "type": "string"
+                },
+                "created_at": {
+                    "type": "string"
+                },
+                "id": {
+                    "type": "integer"
+                },
+                "name": {
+                    "type": "string"
+                },
+                "security_rules": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.SecurityRule"
+                    }
+                },
+                "updated_at": {
+                    "type": "string"
+                }
+            }
+        },
+        "model.SecurityRule": {
             "type": "object",
             "properties": {
                 "bio": {
@@ -3032,6 +3369,12 @@
                 "proxy_domain": {
                     "type": "string"
                 },
+                "security_groups": {
+                    "type": "array",
+                    "items": {
+                        "$ref": "#/definitions/model.SecurityGroup"
+                    }
+                },
                 "updated_at": {
                     "type": "string"
                 }
@@ -3128,9 +3471,6 @@
                 },
                 "name": {
                     "type": "string"
-                },
-                "security_group_domain": {
-                    "type": "string"
                 }
             }
         },
@@ -3298,9 +3638,6 @@
                 },
                 "scopes": {
                     "$ref": "#/definitions/types.SchedulerClusterScopes"
-                },
-                "security_group_domain": {
-                    "type": "string"
                 }
             }
         },
@@ -3342,6 +3679,20 @@
             }
         },
         "types.CreateSecurityGroupRequest": {
+            "type": "object",
+            "required": [
+                "name"
+            ],
+            "properties": {
+                "bio": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.CreateSecurityRuleRequest": {
             "type": "object",
             "required": [
                 "domain",
@@ -3497,9 +3848,6 @@
                 },
                 "name": {
                     "type": "string"
-                },
-                "security_group_domain": {
-                    "type": "string"
                 }
             }
         },
@@ -3597,9 +3945,6 @@
                 },
                 "scopes": {
                     "$ref": "#/definitions/types.SchedulerClusterScopes"
-                },
-                "security_group_domain": {
-                    "type": "string"
                 }
             }
         },
@@ -3634,6 +3979,17 @@
             }
         },
         "types.UpdateSecurityGroupRequest": {
+            "type": "object",
+            "properties": {
+                "bio": {
+                    "type": "string"
+                },
+                "name": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.UpdateSecurityRuleRequest": {
             "type": "object",
             "properties": {
                 "bio": {
diff --git a/api/manager/swagger.yaml b/api/manager/swagger.yaml
index f8402300..1ea6ddac 100644
--- a/api/manager/swagger.yaml
+++ b/api/manager/swagger.yaml
@@ -181,6 +181,23 @@ definitions:
         type: string
     type: object
   model.SecurityGroup:
+    properties:
+      bio:
+        type: string
+      created_at:
+        type: string
+      id:
+        type: integer
+      name:
+        type: string
+      security_rules:
+        items:
+          $ref: '#/definitions/model.SecurityRule'
+        type: array
+      updated_at:
+        type: string
+    type: object
+  model.SecurityRule:
     properties:
       bio:
         type: string
@@ -194,6 +211,10 @@ definitions:
         type: string
       proxy_domain:
         type: string
+      security_groups:
+        items:
+          $ref: '#/definitions/model.SecurityGroup'
+        type: array
       updated_at:
         type: string
     type: object
@@ -255,8 +276,6 @@ definitions:
         $ref: '#/definitions/types.CDNClusterConfig'
       name:
         type: string
-      security_group_domain:
-        type: string
     required:
     - config
     - name
@@ -370,8 +389,6 @@ definitions:
         type: string
       scopes:
         $ref: '#/definitions/types.SchedulerClusterScopes'
-      security_group_domain:
-        type: string
     required:
     - client_config
     - config
@@ -404,6 +421,15 @@ definitions:
     - scheduler_cluster_id
     type: object
   types.CreateSecurityGroupRequest:
+    properties:
+      bio:
+        type: string
+      name:
+        type: string
+    required:
+    - name
+    type: object
+  types.CreateSecurityRuleRequest:
     properties:
       bio:
         type: string
@@ -507,8 +533,6 @@ definitions:
         $ref: '#/definitions/types.CDNClusterConfig'
       name:
         type: string
-      security_group_domain:
-        type: string
     type: object
   types.UpdateCDNRequest:
     properties:
@@ -572,8 +596,6 @@ definitions:
         type: string
       scopes:
         $ref: '#/definitions/types.SchedulerClusterScopes'
-      security_group_domain:
-        type: string
     type: object
   types.UpdateSchedulerRequest:
     properties:
@@ -596,6 +618,13 @@ definitions:
         type: string
     type: object
   types.UpdateSecurityGroupRequest:
+    properties:
+      bio:
+        type: string
+      name:
+        type: string
+    type: object
+  types.UpdateSecurityRuleRequest:
     properties:
       bio:
         type: string
@@ -2161,6 +2190,189 @@ paths:
       summary: Add Scheduler to SecurityGroup
       tags:
       - SecurityGroup
+  /security-groups/{id}/security-rules/{security_rule_id}:
+    delete:
+      consumes:
+      - application/json
+      description: Destroy SecurityRule to SecurityGroup
+      parameters:
+      - description: id
+        in: path
+        name: id
+        required: true
+        type: string
+      - description: security rule id
+        in: path
+        name: security_rule_id
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: ""
+        "400":
+          description: ""
+        "404":
+          description: ""
+        "500":
+          description: ""
+      summary: Destroy SecurityRule to SecurityGroup
+      tags:
+      - SecurityGroup
+    put:
+      consumes:
+      - application/json
+      description: Add SecurityRule to SecurityGroup
+      parameters:
+      - description: id
+        in: path
+        name: id
+        required: true
+        type: string
+      - description: security rule id
+        in: path
+        name: security_rule_id
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: ""
+        "400":
+          description: ""
+        "404":
+          description: ""
+        "500":
+          description: ""
+      summary: Add SecurityRule to SecurityGroup
+      tags:
+      - SecurityGroup
+  /security-rules:
+    get:
+      consumes:
+      - application/json
+      description: Get SecurityRules
+      parameters:
+      - default: 0
+        description: current page
+        in: query
+        name: page
+        required: true
+        type: integer
+      - default: 10
+        description: return max item count, default 10, max 50
+        in: query
+        maximum: 50
+        minimum: 2
+        name: per_page
+        required: true
+        type: integer
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/model.SecurityRule'
+            type: array
+        "400":
+          description: ""
+        "404":
+          description: ""
+        "500":
+          description: ""
+      summary: Get SecurityRules
+      tags:
+      - SecurityRule
+    post:
+      consumes:
+      - application/json
+      description: create by json config
+      parameters:
+      - description: SecurityRule
+        in: body
+        name: SecurityRule
+        required: true
+        schema:
+          $ref: '#/definitions/types.CreateSecurityRuleRequest'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/model.SecurityRule'
+        "400":
+          description: ""
+        "404":
+          description: ""
+        "500":
+          description: ""
+      summary: Create SecurityRule
+      tags:
+      - SecurityRule
+  /security-rules/{id}:
+    get:
+      consumes:
+      - application/json
+      description: Get SecurityRule by id
+      parameters:
+      - description: id
+        in: path
+        name: id
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/model.SecurityRule'
+        "400":
+          description: ""
+        "404":
+          description: ""
+        "500":
+          description: ""
+      summary: Get SecurityRule
+      tags:
+      - SecurityRule
+    patch:
+      consumes:
+      - application/json
+      description: Update by json config
+      parameters:
+      - description: id
+        in: path
+        name: id
+        required: true
+        type: string
+      - description: SecurityRule
+        in: body
+        name: SecurityRule
+        required: true
+        schema:
+          $ref: '#/definitions/types.UpdateSecurityRuleRequest'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/model.SecurityRule'
+        "400":
+          description: ""
+        "404":
+          description: ""
+        "500":
+          description: ""
+      summary: Update SecurityRule
+      tags:
+      - SecurityRule
   /securityGroups/{id}:
     delete:
       consumes:
@@ -2186,6 +2398,31 @@ paths:
       summary: Destroy SecurityGroup
       tags:
       - SecurityGroup
+  /securityRules/{id}:
+    delete:
+      consumes:
+      - application/json
+      description: Destroy by id
+      parameters:
+      - description: id
+        in: path
+        name: id
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: ""
+        "400":
+          description: ""
+        "404":
+          description: ""
+        "500":
+          description: ""
+      summary: Destroy SecurityRule
+      tags:
+      - SecurityRule
   /user/signin/{name}:
     get:
       consumes:
diff --git a/docs/en/api-reference/api-reference.md b/docs/en/api-reference/api-reference.md
index 05382599..386a78f7 100644
--- a/docs/en/api-reference/api-reference.md
+++ b/docs/en/api-reference/api-reference.md
@@ -2337,6 +2337,268 @@ Add Scheduler to SecurityGroup
 * SecurityGroup
 
 
+<a name="api-v1-security-groups-id-security-rules-security_rule_id-put"></a>
+### Add SecurityRule to SecurityGroup
+```
+PUT /api/v1/security-groups/{id}/security-rules/{security_rule_id}
+```
+
+
+#### Description
+Add SecurityRule to SecurityGroup
+
+
+#### Parameters
+
+|Type|Name|Description|Schema|
+|---|---|---|---|
+|**Path**|**id**  <br>*required*|id|string|
+|**Path**|**security_rule_id**  <br>*required*|security rule id|string|
+
+
+#### Responses
+
+|HTTP Code|Schema|
+|---|---|
+|**200**|No Content|
+|**400**|No Content|
+|**404**|No Content|
+|**500**|No Content|
+
+
+#### Consumes
+
+* `application/json`
+
+
+#### Produces
+
+* `application/json`
+
+
+#### Tags
+
+* SecurityGroup
+
+
+<a name="api-v1-security-groups-id-security-rules-security_rule_id-delete"></a>
+### Destroy SecurityRule to SecurityGroup
+```
+DELETE /api/v1/security-groups/{id}/security-rules/{security_rule_id}
+```
+
+
+#### Description
+Destroy SecurityRule to SecurityGroup
+
+
+#### Parameters
+
+|Type|Name|Description|Schema|
+|---|---|---|---|
+|**Path**|**id**  <br>*required*|id|string|
+|**Path**|**security_rule_id**  <br>*required*|security rule id|string|
+
+
+#### Responses
+
+|HTTP Code|Schema|
+|---|---|
+|**200**|No Content|
+|**400**|No Content|
+|**404**|No Content|
+|**500**|No Content|
+
+
+#### Consumes
+
+* `application/json`
+
+
+#### Produces
+
+* `application/json`
+
+
+#### Tags
+
+* SecurityGroup
+
+
+<a name="api-v1-security-rules-post"></a>
+### Create SecurityRule
+```
+POST /api/v1/security-rules
+```
+
+
+#### Description
+create by json config
+
+
+#### Parameters
+
+|Type|Name|Description|Schema|
+|---|---|---|---|
+|**Body**|**SecurityRule**  <br>*required*|SecurityRule|[types.CreateSecurityRuleRequest](#types-createsecurityrulerequest)|
+
+
+#### Responses
+
+|HTTP Code|Description|Schema|
+|---|---|---|
+|**200**|OK|[model.SecurityRule](#model-securityrule)|
+|**400**||No Content|
+|**404**||No Content|
+|**500**||No Content|
+
+
+#### Consumes
+
+* `application/json`
+
+
+#### Produces
+
+* `application/json`
+
+
+#### Tags
+
+* SecurityRule
+
+
+<a name="api-v1-security-rules-get"></a>
+### Get SecurityRules
+```
+GET /api/v1/security-rules
+```
+
+
+#### Description
+Get SecurityRules
+
+
+#### Parameters
+
+|Type|Name|Description|Schema|Default|
+|---|---|---|---|---|
+|**Query**|**page**  <br>*required*|current page|integer|`0`|
+|**Query**|**per_page**  <br>*required*|return max item count, default 10, max 50|integer|`10`|
+
+
+#### Responses
+
+|HTTP Code|Description|Schema|
+|---|---|---|
+|**200**|OK|< [model.SecurityRule](#model-securityrule) > array|
+|**400**||No Content|
+|**404**||No Content|
+|**500**||No Content|
+
+
+#### Consumes
+
+* `application/json`
+
+
+#### Produces
+
+* `application/json`
+
+
+#### Tags
+
+* SecurityRule
+
+
+<a name="api-v1-security-rules-id-get"></a>
+### Get SecurityRule
+```
+GET /api/v1/security-rules/{id}
+```
+
+
+#### Description
+Get SecurityRule by id
+
+
+#### Parameters
+
+|Type|Name|Description|Schema|
+|---|---|---|---|
+|**Path**|**id**  <br>*required*|id|string|
+
+
+#### Responses
+
+|HTTP Code|Description|Schema|
+|---|---|---|
+|**200**|OK|[model.SecurityRule](#model-securityrule)|
+|**400**||No Content|
+|**404**||No Content|
+|**500**||No Content|
+
+
+#### Consumes
+
+* `application/json`
+
+
+#### Produces
+
+* `application/json`
+
+
+#### Tags
+
+* SecurityRule
+
+
+<a name="api-v1-security-rules-id-patch"></a>
+### Update SecurityRule
+```
+PATCH /api/v1/security-rules/{id}
+```
+
+
+#### Description
+Update by json config
+
+
+#### Parameters
+
+|Type|Name|Description|Schema|
+|---|---|---|---|
+|**Path**|**id**  <br>*required*|id|string|
+|**Body**|**SecurityRule**  <br>*required*|SecurityRule|[types.UpdateSecurityRuleRequest](#types-updatesecurityrulerequest)|
+
+
+#### Responses
+
+|HTTP Code|Description|Schema|
+|---|---|---|
+|**200**|OK|[model.SecurityRule](#model-securityrule)|
+|**400**||No Content|
+|**404**||No Content|
+|**500**||No Content|
+
+
+#### Consumes
+
+* `application/json`
+
+
+#### Produces
+
+* `application/json`
+
+
+#### Tags
+
+* SecurityRule
+
+
 <a name="api-v1-securitygroups-id-delete"></a>
 ### Destroy SecurityGroup
 ```
@@ -2380,6 +2642,49 @@ Destroy by id
 * SecurityGroup
 
 
+<a name="api-v1-securityrules-id-delete"></a>
+### Destroy SecurityRule
+```
+DELETE /api/v1/securityRules/{id}
+```
+
+
+#### Description
+Destroy by id
+
+
+#### Parameters
+
+|Type|Name|Description|Schema|
+|---|---|---|---|
+|**Path**|**id**  <br>*required*|id|string|
+
+
+#### Responses
+
+|HTTP Code|Schema|
+|---|---|
+|**200**|No Content|
+|**400**|No Content|
+|**404**|No Content|
+|**500**|No Content|
+
+
+#### Consumes
+
+* `application/json`
+
+
+#### Produces
+
+* `application/json`
+
+
+#### Tags
+
+* SecurityRule
+
+
 <a name="api-v1-user-signin-name-get"></a>
 ### Oauth Signin
 ```
@@ -2885,6 +3190,19 @@ Get User by id
 <a name="model-securitygroup"></a>
 ### model.SecurityGroup
 
+|Name|Schema|
+|---|---|
+|**bio**  <br>*optional*|string|
+|**created_at**  <br>*optional*|string|
+|**id**  <br>*optional*|integer|
+|**name**  <br>*optional*|string|
+|**security_rules**  <br>*optional*|< [model.SecurityRule](#model-securityrule) > array|
+|**updated_at**  <br>*optional*|string|
+
+
+<a name="model-securityrule"></a>
+### model.SecurityRule
+
 |Name|Schema|
 |---|---|
 |**bio**  <br>*optional*|string|
@@ -2893,6 +3211,7 @@ Get User by id
 |**id**  <br>*optional*|integer|
 |**name**  <br>*optional*|string|
 |**proxy_domain**  <br>*optional*|string|
+|**security_groups**  <br>*optional*|< [model.SecurityGroup](#model-securitygroup) > array|
 |**updated_at**  <br>*optional*|string|
 
 
@@ -2948,7 +3267,6 @@ Get User by id
 |**bio**  <br>*optional*|string|
 |**config**  <br>*required*|[types.CDNClusterConfig](#types-cdnclusterconfig)|
 |**name**  <br>*required*|string|
-|**security_group_domain**  <br>*optional*|string|
 
 
 <a name="types-createcdnrequest"></a>
@@ -3023,7 +3341,6 @@ Get User by id
 |**is_default**  <br>*optional*|boolean|
 |**name**  <br>*required*|string|
 |**scopes**  <br>*optional*|[types.SchedulerClusterScopes](#types-schedulerclusterscopes)|
-|**security_group_domain**  <br>*optional*|string|
 
 
 <a name="types-createschedulerrequest"></a>
@@ -3044,6 +3361,15 @@ Get User by id
 <a name="types-createsecuritygrouprequest"></a>
 ### types.CreateSecurityGroupRequest
 
+|Name|Schema|
+|---|---|
+|**bio**  <br>*optional*|string|
+|**name**  <br>*required*|string|
+
+
+<a name="types-createsecurityrulerequest"></a>
+### types.CreateSecurityRuleRequest
+
 |Name|Schema|
 |---|---|
 |**bio**  <br>*optional*|string|
@@ -3140,7 +3466,6 @@ Get User by id
 |**bio**  <br>*optional*|string|
 |**config**  <br>*optional*|[types.CDNClusterConfig](#types-cdnclusterconfig)|
 |**name**  <br>*optional*|string|
-|**security_group_domain**  <br>*optional*|string|
 
 
 <a name="types-updatecdnrequest"></a>
@@ -3200,7 +3525,6 @@ Get User by id
 |**is_default**  <br>*optional*|boolean|
 |**name**  <br>*optional*|string|
 |**scopes**  <br>*optional*|[types.SchedulerClusterScopes](#types-schedulerclusterscopes)|
-|**security_group_domain**  <br>*optional*|string|
 
 
 <a name="types-updateschedulerrequest"></a>
@@ -3221,6 +3545,15 @@ Get User by id
 <a name="types-updatesecuritygrouprequest"></a>
 ### types.UpdateSecurityGroupRequest
 
+|Name|Schema|
+|---|---|
+|**bio**  <br>*optional*|string|
+|**name**  <br>*optional*|string|
+
+
+<a name="types-updatesecurityrulerequest"></a>
+### types.UpdateSecurityRuleRequest
+
 |Name|Schema|
 |---|---|
 |**bio**  <br>*optional*|string|
diff --git a/docs/zh-CN/api-reference/api-reference.md b/docs/zh-CN/api-reference/api-reference.md
index 9e53def3..4b8b3aea 100644
--- a/docs/zh-CN/api-reference/api-reference.md
+++ b/docs/zh-CN/api-reference/api-reference.md
@@ -2337,6 +2337,268 @@ Add Scheduler to SecurityGroup
 * SecurityGroup
 
 
+<a name="api-v1-security-groups-id-security-rules-security_rule_id-put"></a>
+### Add SecurityRule to SecurityGroup
+```
+PUT /api/v1/security-groups/{id}/security-rules/{security_rule_id}
+```
+
+
+#### 说明
+Add SecurityRule to SecurityGroup
+
+
+#### 参数
+
+|类型|名称|说明|类型|
+|---|---|---|---|
+|**Path**|**id**  <br>*必填*|id|string|
+|**Path**|**security_rule_id**  <br>*必填*|security rule id|string|
+
+
+#### 响应
+
+|HTTP代码|类型|
+|---|---|
+|**200**|无内容|
+|**400**|无内容|
+|**404**|无内容|
+|**500**|无内容|
+
+
+#### 消耗
+
+* `application/json`
+
+
+#### 生成
+
+* `application/json`
+
+
+#### 标签
+
+* SecurityGroup
+
+
+<a name="api-v1-security-groups-id-security-rules-security_rule_id-delete"></a>
+### Destroy SecurityRule to SecurityGroup
+```
+DELETE /api/v1/security-groups/{id}/security-rules/{security_rule_id}
+```
+
+
+#### 说明
+Destroy SecurityRule to SecurityGroup
+
+
+#### 参数
+
+|类型|名称|说明|类型|
+|---|---|---|---|
+|**Path**|**id**  <br>*必填*|id|string|
+|**Path**|**security_rule_id**  <br>*必填*|security rule id|string|
+
+
+#### 响应
+
+|HTTP代码|类型|
+|---|---|
+|**200**|无内容|
+|**400**|无内容|
+|**404**|无内容|
+|**500**|无内容|
+
+
+#### 消耗
+
+* `application/json`
+
+
+#### 生成
+
+* `application/json`
+
+
+#### 标签
+
+* SecurityGroup
+
+
+<a name="api-v1-security-rules-post"></a>
+### Create SecurityRule
+```
+POST /api/v1/security-rules
+```
+
+
+#### 说明
+create by json config
+
+
+#### 参数
+
+|类型|名称|说明|类型|
+|---|---|---|---|
+|**Body**|**SecurityRule**  <br>*必填*|SecurityRule|[types.CreateSecurityRuleRequest](#types-createsecurityrulerequest)|
+
+
+#### 响应
+
+|HTTP代码|说明|类型|
+|---|---|---|
+|**200**|OK|[model.SecurityRule](#model-securityrule)|
+|**400**||无内容|
+|**404**||无内容|
+|**500**||无内容|
+
+
+#### 消耗
+
+* `application/json`
+
+
+#### 生成
+
+* `application/json`
+
+
+#### 标签
+
+* SecurityRule
+
+
+<a name="api-v1-security-rules-get"></a>
+### Get SecurityRules
+```
+GET /api/v1/security-rules
+```
+
+
+#### 说明
+Get SecurityRules
+
+
+#### 参数
+
+|类型|名称|说明|类型|默认值|
+|---|---|---|---|---|
+|**Query**|**page**  <br>*必填*|current page|integer|`0`|
+|**Query**|**per_page**  <br>*必填*|return max item count, default 10, max 50|integer|`10`|
+
+
+#### 响应
+
+|HTTP代码|说明|类型|
+|---|---|---|
+|**200**|OK|< [model.SecurityRule](#model-securityrule) > array|
+|**400**||无内容|
+|**404**||无内容|
+|**500**||无内容|
+
+
+#### 消耗
+
+* `application/json`
+
+
+#### 生成
+
+* `application/json`
+
+
+#### 标签
+
+* SecurityRule
+
+
+<a name="api-v1-security-rules-id-get"></a>
+### Get SecurityRule
+```
+GET /api/v1/security-rules/{id}
+```
+
+
+#### 说明
+Get SecurityRule by id
+
+
+#### 参数
+
+|类型|名称|说明|类型|
+|---|---|---|---|
+|**Path**|**id**  <br>*必填*|id|string|
+
+
+#### 响应
+
+|HTTP代码|说明|类型|
+|---|---|---|
+|**200**|OK|[model.SecurityRule](#model-securityrule)|
+|**400**||无内容|
+|**404**||无内容|
+|**500**||无内容|
+
+
+#### 消耗
+
+* `application/json`
+
+
+#### 生成
+
+* `application/json`
+
+
+#### 标签
+
+* SecurityRule
+
+
+<a name="api-v1-security-rules-id-patch"></a>
+### Update SecurityRule
+```
+PATCH /api/v1/security-rules/{id}
+```
+
+
+#### 说明
+Update by json config
+
+
+#### 参数
+
+|类型|名称|说明|类型|
+|---|---|---|---|
+|**Path**|**id**  <br>*必填*|id|string|
+|**Body**|**SecurityRule**  <br>*必填*|SecurityRule|[types.UpdateSecurityRuleRequest](#types-updatesecurityrulerequest)|
+
+
+#### 响应
+
+|HTTP代码|说明|类型|
+|---|---|---|
+|**200**|OK|[model.SecurityRule](#model-securityrule)|
+|**400**||无内容|
+|**404**||无内容|
+|**500**||无内容|
+
+
+#### 消耗
+
+* `application/json`
+
+
+#### 生成
+
+* `application/json`
+
+
+#### 标签
+
+* SecurityRule
+
+
 <a name="api-v1-securitygroups-id-delete"></a>
 ### Destroy SecurityGroup
 ```
@@ -2380,6 +2642,49 @@ Destroy by id
 * SecurityGroup
 
 
+<a name="api-v1-securityrules-id-delete"></a>
+### Destroy SecurityRule
+```
+DELETE /api/v1/securityRules/{id}
+```
+
+
+#### 说明
+Destroy by id
+
+
+#### 参数
+
+|类型|名称|说明|类型|
+|---|---|---|---|
+|**Path**|**id**  <br>*必填*|id|string|
+
+
+#### 响应
+
+|HTTP代码|类型|
+|---|---|
+|**200**|无内容|
+|**400**|无内容|
+|**404**|无内容|
+|**500**|无内容|
+
+
+#### 消耗
+
+* `application/json`
+
+
+#### 生成
+
+* `application/json`
+
+
+#### 标签
+
+* SecurityRule
+
+
 <a name="api-v1-user-signin-name-get"></a>
 ### Oauth Signin
 ```
@@ -2885,6 +3190,19 @@ Get User by id
 <a name="model-securitygroup"></a>
 ### model.SecurityGroup
 
+|名称|类型|
+|---|---|
+|**bio**  <br>*可选*|string|
+|**created_at**  <br>*可选*|string|
+|**id**  <br>*可选*|integer|
+|**name**  <br>*可选*|string|
+|**security_rules**  <br>*可选*|< [model.SecurityRule](#model-securityrule) > array|
+|**updated_at**  <br>*可选*|string|
+
+
+<a name="model-securityrule"></a>
+### model.SecurityRule
+
 |名称|类型|
 |---|---|
 |**bio**  <br>*可选*|string|
@@ -2893,6 +3211,7 @@ Get User by id
 |**id**  <br>*可选*|integer|
 |**name**  <br>*可选*|string|
 |**proxy_domain**  <br>*可选*|string|
+|**security_groups**  <br>*可选*|< [model.SecurityGroup](#model-securitygroup) > array|
 |**updated_at**  <br>*可选*|string|
 
 
@@ -2948,7 +3267,6 @@ Get User by id
 |**bio**  <br>*可选*|string|
 |**config**  <br>*必填*|[types.CDNClusterConfig](#types-cdnclusterconfig)|
 |**name**  <br>*必填*|string|
-|**security_group_domain**  <br>*可选*|string|
 
 
 <a name="types-createcdnrequest"></a>
@@ -3023,7 +3341,6 @@ Get User by id
 |**is_default**  <br>*可选*|boolean|
 |**name**  <br>*必填*|string|
 |**scopes**  <br>*可选*|[types.SchedulerClusterScopes](#types-schedulerclusterscopes)|
-|**security_group_domain**  <br>*可选*|string|
 
 
 <a name="types-createschedulerrequest"></a>
@@ -3044,6 +3361,15 @@ Get User by id
 <a name="types-createsecuritygrouprequest"></a>
 ### types.CreateSecurityGroupRequest
 
+|名称|类型|
+|---|---|
+|**bio**  <br>*可选*|string|
+|**name**  <br>*必填*|string|
+
+
+<a name="types-createsecurityrulerequest"></a>
+### types.CreateSecurityRuleRequest
+
 |名称|类型|
 |---|---|
 |**bio**  <br>*可选*|string|
@@ -3140,7 +3466,6 @@ Get User by id
 |**bio**  <br>*可选*|string|
 |**config**  <br>*可选*|[types.CDNClusterConfig](#types-cdnclusterconfig)|
 |**name**  <br>*可选*|string|
-|**security_group_domain**  <br>*可选*|string|
 
 
 <a name="types-updatecdnrequest"></a>
@@ -3200,7 +3525,6 @@ Get User by id
 |**is_default**  <br>*可选*|boolean|
 |**name**  <br>*可选*|string|
 |**scopes**  <br>*可选*|[types.SchedulerClusterScopes](#types-schedulerclusterscopes)|
-|**security_group_domain**  <br>*可选*|string|
 
 
 <a name="types-updateschedulerrequest"></a>
@@ -3221,6 +3545,15 @@ Get User by id
 <a name="types-updatesecuritygrouprequest"></a>
 ### types.UpdateSecurityGroupRequest
 
+|名称|类型|
+|---|---|
+|**bio**  <br>*可选*|string|
+|**name**  <br>*可选*|string|
+
+
+<a name="types-updatesecurityrulerequest"></a>
+### types.UpdateSecurityRuleRequest
+
 |名称|类型|
 |---|---|
 |**bio**  <br>*可选*|string|
diff --git a/manager/database/database.go b/manager/database/database.go
index f409af27..8c007227 100644
--- a/manager/database/database.go
+++ b/manager/database/database.go
@@ -112,6 +112,7 @@ func migrate(db *gorm.DB) error {
 		&model.CDN{},
 		&model.SchedulerCluster{},
 		&model.Scheduler{},
+		&model.SecurityRule{},
 		&model.SecurityGroup{},
 		&model.User{},
 		&model.Oauth{},
diff --git a/manager/handlers/cdn_cluster.go b/manager/handlers/cdn_cluster.go
index 45900324..222a3ee3 100644
--- a/manager/handlers/cdn_cluster.go
+++ b/manager/handlers/cdn_cluster.go
@@ -44,17 +44,6 @@ func (h *Handlers) CreateCDNCluster(ctx *gin.Context) {
 		return
 	}
 
-	if json.SecurityGroupDomain != "" {
-		cdn, err := h.service.CreateCDNClusterWithSecurityGroupDomain(ctx.Request.Context(), json)
-		if err != nil {
-			ctx.Error(err) // nolint: errcheck
-			return
-		}
-
-		ctx.JSON(http.StatusOK, cdn)
-		return
-	}
-
 	cdnCluster, err := h.service.CreateCDNCluster(ctx.Request.Context(), json)
 	if err != nil {
 		ctx.Error(err) // nolint: errcheck
@@ -115,17 +104,6 @@ func (h *Handlers) UpdateCDNCluster(ctx *gin.Context) {
 		return
 	}
 
-	if json.SecurityGroupDomain != "" {
-		cdn, err := h.service.UpdateCDNClusterWithSecurityGroupDomain(ctx.Request.Context(), params.ID, json)
-		if err != nil {
-			ctx.Error(err) // nolint: errcheck
-			return
-		}
-
-		ctx.JSON(http.StatusOK, cdn)
-		return
-	}
-
 	cdnCluster, err := h.service.UpdateCDNCluster(ctx.Request.Context(), params.ID, json)
 	if err != nil {
 		ctx.Error(err) // nolint: errcheck
diff --git a/manager/handlers/scheduler_cluster.go b/manager/handlers/scheduler_cluster.go
index d55829ce..c56c1efc 100644
--- a/manager/handlers/scheduler_cluster.go
+++ b/manager/handlers/scheduler_cluster.go
@@ -44,17 +44,6 @@ func (h *Handlers) CreateSchedulerCluster(ctx *gin.Context) {
 		return
 	}
 
-	if json.SecurityGroupDomain != "" {
-		scheduler, err := h.service.CreateSchedulerClusterWithSecurityGroupDomain(ctx.Request.Context(), json)
-		if err != nil {
-			ctx.Error(err) // nolint: errcheck
-			return
-		}
-
-		ctx.JSON(http.StatusOK, scheduler)
-		return
-	}
-
 	schedulerCluster, err := h.service.CreateSchedulerCluster(ctx.Request.Context(), json)
 	if err != nil {
 		ctx.Error(err) // nolint: errcheck
@@ -115,17 +104,6 @@ func (h *Handlers) UpdateSchedulerCluster(ctx *gin.Context) {
 		return
 	}
 
-	if json.SecurityGroupDomain != "" {
-		scheduler, err := h.service.UpdateSchedulerClusterWithSecurityGroupDomain(ctx.Request.Context(), params.ID, json)
-		if err != nil {
-			ctx.Error(err) // nolint: errcheck
-			return
-		}
-
-		ctx.JSON(http.StatusOK, scheduler)
-		return
-	}
-
 	schedulerCluster, err := h.service.UpdateSchedulerCluster(ctx.Request.Context(), params.ID, json)
 	if err != nil {
 		ctx.Error(err) // nolint: errcheck
diff --git a/manager/handlers/security_group.go b/manager/handlers/security_group.go
index 99db28a8..4daa3ced 100644
--- a/manager/handlers/security_group.go
+++ b/manager/handlers/security_group.go
@@ -225,3 +225,59 @@ func (h *Handlers) AddCDNClusterToSecurityGroup(ctx *gin.Context) {
 
 	ctx.Status(http.StatusOK)
 }
+
+// @Summary Add SecurityRule to SecurityGroup
+// @Description Add SecurityRule to SecurityGroup
+// @Tags SecurityGroup
+// @Accept json
+// @Produce json
+// @Param id path string true "id"
+// @Param security_rule_id path string true "security rule id"
+// @Success 200
+// @Failure 400
+// @Failure 404
+// @Failure 500
+// @Router /security-groups/{id}/security-rules/{security_rule_id} [put]
+func (h *Handlers) AddSecurityRuleToSecurityGroup(ctx *gin.Context) {
+	var params types.AddSecurityRuleToSecurityGroupParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	err := h.service.AddSecurityRuleToSecurityGroup(ctx.Request.Context(), params.ID, params.SecurityRuleID)
+	if err != nil {
+		ctx.Error(err) // nolint: errcheck
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Destroy SecurityRule to SecurityGroup
+// @Description Destroy SecurityRule to SecurityGroup
+// @Tags SecurityGroup
+// @Accept json
+// @Produce json
+// @Param id path string true "id"
+// @Param security_rule_id path string true "security rule id"
+// @Success 200
+// @Failure 400
+// @Failure 404
+// @Failure 500
+// @Router /security-groups/{id}/security-rules/{security_rule_id} [delete]
+func (h *Handlers) DestroySecurityRuleToSecurityGroup(ctx *gin.Context) {
+	var params types.AddSecurityRuleToSecurityGroupParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	err := h.service.DestroySecurityRuleToSecurityGroup(ctx.Request.Context(), params.ID, params.SecurityRuleID)
+	if err != nil {
+		ctx.Error(err) // nolint: errcheck
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
diff --git a/manager/handlers/security_rule.go b/manager/handlers/security_rule.go
new file mode 100644
index 00000000..706999de
--- /dev/null
+++ b/manager/handlers/security_rule.go
@@ -0,0 +1,171 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package handlers
+
+import (
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+
+	// nolint
+	_ "d7y.io/dragonfly/v2/manager/model"
+	"d7y.io/dragonfly/v2/manager/types"
+)
+
+// @Summary Create SecurityRule
+// @Description create by json config
+// @Tags SecurityRule
+// @Accept json
+// @Produce json
+// @Param SecurityRule body types.CreateSecurityRuleRequest true "SecurityRule"
+// @Success 200 {object} model.SecurityRule
+// @Failure 400
+// @Failure 404
+// @Failure 500
+// @Router /security-rules [post]
+func (h *Handlers) CreateSecurityRule(ctx *gin.Context) {
+	var json types.CreateSecurityRuleRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	securityRule, err := h.service.CreateSecurityRule(ctx.Request.Context(), json)
+	if err != nil {
+		ctx.Error(err) // nolint: errcheck
+		return
+	}
+
+	ctx.JSON(http.StatusOK, securityRule)
+}
+
+// @Summary Destroy SecurityRule
+// @Description Destroy by id
+// @Tags SecurityRule
+// @Accept json
+// @Produce json
+// @Param id path string true "id"
+// @Success 200
+// @Failure 400
+// @Failure 404
+// @Failure 500
+// @Router /securityRules/{id} [delete]
+func (h *Handlers) DestroySecurityRule(ctx *gin.Context) {
+	var params types.SecurityRuleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	if err := h.service.DestroySecurityRule(ctx.Request.Context(), params.ID); err != nil {
+		ctx.Error(err) // nolint: errcheck
+		return
+	}
+
+	ctx.Status(http.StatusOK)
+}
+
+// @Summary Update SecurityRule
+// @Description Update by json config
+// @Tags SecurityRule
+// @Accept json
+// @Produce json
+// @Param id path string true "id"
+// @Param SecurityRule body types.UpdateSecurityRuleRequest true "SecurityRule"
+// @Success 200 {object} model.SecurityRule
+// @Failure 400
+// @Failure 404
+// @Failure 500
+// @Router /security-rules/{id} [patch]
+func (h *Handlers) UpdateSecurityRule(ctx *gin.Context) {
+	var params types.SecurityRuleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.Error(err) // nolint: errcheck
+		return
+	}
+
+	var json types.UpdateSecurityRuleRequest
+	if err := ctx.ShouldBindJSON(&json); err != nil {
+		ctx.Error(err) // nolint: errcheck
+		return
+	}
+
+	securityRule, err := h.service.UpdateSecurityRule(ctx.Request.Context(), params.ID, json)
+	if err != nil {
+		ctx.Error(err) // nolint: errcheck
+		return
+	}
+
+	ctx.JSON(http.StatusOK, securityRule)
+}
+
+// @Summary Get SecurityRule
+// @Description Get SecurityRule by id
+// @Tags SecurityRule
+// @Accept json
+// @Produce json
+// @Param id path string true "id"
+// @Success 200 {object} model.SecurityRule
+// @Failure 400
+// @Failure 404
+// @Failure 500
+// @Router /security-rules/{id} [get]
+func (h *Handlers) GetSecurityRule(ctx *gin.Context) {
+	var params types.SecurityRuleParams
+	if err := ctx.ShouldBindUri(&params); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	securityRule, err := h.service.GetSecurityRule(ctx.Request.Context(), params.ID)
+	if err != nil {
+		ctx.Error(err) // nolint: errcheck
+		return
+	}
+
+	ctx.JSON(http.StatusOK, securityRule)
+}
+
+// @Summary Get SecurityRules
+// @Description Get SecurityRules
+// @Tags SecurityRule
+// @Accept json
+// @Produce json
+// @Param page query int true "current page" default(0)
+// @Param per_page query int true "return max item count, default 10, max 50" default(10) minimum(2) maximum(50)
+// @Success 200 {object} []model.SecurityRule
+// @Failure 400
+// @Failure 404
+// @Failure 500
+// @Router /security-rules [get]
+func (h *Handlers) GetSecurityRules(ctx *gin.Context) {
+	var query types.GetSecurityRulesQuery
+	if err := ctx.ShouldBindQuery(&query); err != nil {
+		ctx.JSON(http.StatusUnprocessableEntity, gin.H{"errors": err.Error()})
+		return
+	}
+
+	h.setPaginationDefault(&query.Page, &query.PerPage)
+	securityRules, count, err := h.service.GetSecurityRules(ctx.Request.Context(), query)
+	if err != nil {
+		ctx.Error(err) // nolint: errcheck
+		return
+	}
+
+	h.setPaginationLinkHeader(ctx, query.Page, query.PerPage, int(count))
+	ctx.JSON(http.StatusOK, securityRules)
+}
diff --git a/manager/model/security_group.go b/manager/model/security_group.go
index fb926947..bca3d383 100644
--- a/manager/model/security_group.go
+++ b/manager/model/security_group.go
@@ -20,8 +20,7 @@ type SecurityGroup struct {
 	Model
 	Name              string             `gorm:"column:name;type:varchar(256);index:uk_security_group_name,unique;not null;comment:name" json:"name"`
 	BIO               string             `gorm:"column:bio;type:varchar(1024);comment:biography" json:"bio"`
-	Domain            string             `gorm:"column:domain;type:varchar(256);index:uk_security_group_domain,unique;not null;comment:domain" json:"domain"`
-	ProxyDomain       string             `gorm:"column:proxy_domain;type:varchar(1024);comment:proxy domain" json:"proxy_domain"`
+	SecurityRules     []SecurityRule     `gorm:"many2many:security_group_security_rule;" json:"security_rules"`
 	SchedulerClusters []SchedulerCluster `json:"-"`
 	CDNClusters       []CDNCluster       `json:"-"`
 }
diff --git a/manager/model/security_rule.go b/manager/model/security_rule.go
new file mode 100644
index 00000000..256d46a3
--- /dev/null
+++ b/manager/model/security_rule.go
@@ -0,0 +1,26 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+type SecurityRule struct {
+	Model
+	Name           string          `gorm:"column:name;type:varchar(256);index:uk_security_rule_name,unique;not null;comment:name" json:"name"`
+	BIO            string          `gorm:"column:bio;type:varchar(1024);comment:biography" json:"bio"`
+	Domain         string          `gorm:"column:domain;type:varchar(256);index:uk_security_rule_domain,unique;not null;comment:domain" json:"domain"`
+	ProxyDomain    string          `gorm:"column:proxy_domain;type:varchar(1024);comment:proxy domain" json:"proxy_domain"`
+	SecurityGroups []SecurityGroup `gorm:"many2many:security_group_security_rule;" json:"security_groups"`
+}
diff --git a/manager/router/router.go b/manager/router/router.go
index 12d506bf..7f273aea 100644
--- a/manager/router/router.go
+++ b/manager/router/router.go
@@ -165,6 +165,14 @@ func Init(cfg *config.Config, service service.REST, enforcer *casbin.Enforcer) (
 	c.GET(":id", h.GetCDN)
 	c.GET("", h.GetCDNs)
 
+	// Security Rule
+	sr := apiv1.Group("/security-rules", jwt.MiddlewareFunc(), rbac)
+	sr.POST("", h.CreateSecurityRule)
+	sr.DELETE(":id", h.DestroySecurityRule)
+	sr.PATCH(":id", h.UpdateSecurityRule)
+	sr.GET(":id", h.GetSecurityRule)
+	sr.GET("", h.GetSecurityRules)
+
 	// Security Group
 	sg := apiv1.Group("/security-groups", jwt.MiddlewareFunc(), rbac)
 	sg.POST("", h.CreateSecurityGroup)
@@ -174,6 +182,8 @@ func Init(cfg *config.Config, service service.REST, enforcer *casbin.Enforcer) (
 	sg.GET("", h.GetSecurityGroups)
 	sg.PUT(":id/scheduler-clusters/:scheduler_cluster_id", h.AddSchedulerClusterToSecurityGroup)
 	sg.PUT(":id/cdn-clusters/:cdn_cluster_id", h.AddCDNClusterToSecurityGroup)
+	sg.PUT(":id/security-rules/:security_rule_id", h.AddSecurityRuleToSecurityGroup)
+	sg.DELETE(":id/security-rules/:security_rule_id", h.DestroySecurityRuleToSecurityGroup)
 
 	// Config
 	config := apiv1.Group("/configs")
diff --git a/manager/searcher/searcher.go b/manager/searcher/searcher.go
index 8deeec52..50e95454 100644
--- a/manager/searcher/searcher.go
+++ b/manager/searcher/searcher.go
@@ -17,28 +17,56 @@
 package searcher
 
 import (
-	"sort"
+	"strings"
 
 	"github.com/mitchellh/mapstructure"
-	"gonum.org/v1/gonum/stat"
 
 	"d7y.io/dragonfly/v2/manager/model"
+	"d7y.io/dragonfly/v2/pkg/util/mathutils"
 )
 
 const (
+	// Condition IDC key
+	conditionIDC = "idc"
+
+	// Condition location key
+	conditionLocation = "location"
+
+	// Condition netTopology key
+	conditionNetTopology = "net_topology"
+
+	// Condition security domain key
 	conditionSecurityDomain = "security_domain"
-	conditionLocation       = "location"
-	conditionIDC            = "idc"
 )
 
 const (
-	conditionLocationWeight = 0.7
-	conditionIDCWeight      = 0.3
+	// IDC affinity weight
+	idcAffinityWeight float64 = 0.5
+
+	// NetTopology affinity weight
+	netTopologyAffinityWeight = 0.3
+
+	// Location affinity weight
+	locationAffinityWeight = 0.2
+)
+
+const (
+	// Maximum score
+	maxScore float64 = 1.0
+
+	// Minimum score
+	minScore = 0
+)
+
+const (
+	// Maximum number of elements
+	maxElementLen = 5
 )
 
 type Scopes struct {
-	Location []string `mapstructure:"location"`
-	IDC      []string `mapstructure:"idc"`
+	IDC         string `mapstructure:"idc"`
+	Location    string `mapstructure:"location"`
+	NetTopology string `mapstructure:"net_topology"`
 }
 
 type Searcher interface {
@@ -62,63 +90,96 @@ func (s *searcher) FindSchedulerCluster(schedulerClusters []model.SchedulerClust
 	}
 
 	// If there are security domain conditions, match clusters of the same security domain.
-	// If the security domain condition does not exist, it matches clusters that does not have a security domain.
+	// If the security domain condition does not exist, it will match all scheduler security domains.
 	// Then use clusters sets to score according to scopes.
-	securityDomain := conditions[conditionSecurityDomain]
 	var clusters []model.SchedulerCluster
-	for _, v := range schedulerClusters {
-		if v.SecurityGroup.Domain == securityDomain {
-			clusters = append(clusters, v)
+	securityDomain := conditions[conditionSecurityDomain]
+	for _, schedulerCluster := range schedulerClusters {
+		if len(schedulerCluster.Schedulers) > 0 {
+			if securityDomain == "" {
+				clusters = append(clusters, schedulerCluster)
+			} else {
+				for _, securityRule := range schedulerCluster.SecurityGroup.SecurityRules {
+					if strings.Compare(securityRule.Domain, securityDomain) == 0 {
+						clusters = append(clusters, schedulerCluster)
+					}
+				}
+			}
 		}
 	}
 
 	switch len(clusters) {
 	case 0:
+		// If the security domain does not match, there is no cluster available
 		return model.SchedulerCluster{}, false
 	case 1:
+		// If only one cluster matches the security domain, return the cluster directly
 		return clusters[0], true
 	default:
-		var maxMean float64 = 0
-		cluster := clusters[0]
-		for _, v := range clusters {
-			mean := calculateSchedulerClusterMean(conditions, v.Scopes)
-			if mean > maxMean {
-				maxMean = mean
-				cluster = v
+		// If there are multiple clusters matching the security domain,
+		// select the schuelder cluster with a higher score
+		var maxScore float64 = 0
+		result := clusters[0]
+		for _, cluster := range clusters {
+			var scopes Scopes
+			if err := mapstructure.Decode(cluster.Scopes, &scopes); err != nil {
+				// Scopes parse failed to skip this evaluation
+				continue
+			}
+
+			score := evaluate(conditions, scopes)
+			if score > maxScore {
+				maxScore = score
+				result = cluster
 			}
 		}
-		return cluster, true
+		return result, true
 	}
 }
 
-func calculateSchedulerClusterMean(conditions map[string]string, rawScopes map[string]interface{}) float64 {
-	var scopes Scopes
-	if err := mapstructure.Decode(rawScopes, &scopes); err != nil {
-		return 0
-	}
-
-	location := conditions[conditionLocation]
-	lx := calculateConditionScore(location, scopes.Location)
+// Evaluate the degree of matching between scheduler cluster and dfdaemon
+func evaluate(conditions map[string]string, scopes Scopes) float64 {
+	return idcAffinityWeight*calculateIDCAffinityScore(conditions[conditionIDC], scopes.IDC) +
+		locationAffinityWeight*calculateMultiElementAffinityScore(conditions[conditionLocation], scopes.Location) +
+		netTopologyAffinityWeight*calculateMultiElementAffinityScore(conditions[conditionNetTopology], scopes.NetTopology)
+}
 
-	idc := conditions[conditionIDC]
-	ix := calculateConditionScore(idc, scopes.IDC)
+// calculateIDCAffinityScore 0.0~1.0 larger and better
+func calculateIDCAffinityScore(dst, src string) float64 {
+	if dst != "" && src != "" && strings.Compare(dst, src) == 0 {
+		return maxScore
+	}
 
-	return stat.Mean([]float64{lx, ix}, []float64{conditionLocationWeight, conditionIDCWeight})
+	return minScore
 }
 
-func calculateConditionScore(value string, scope []string) float64 {
-	if value == "" {
-		return 0
+// calculateMultiElementAffinityScore 0.0~1.0 larger and better
+func calculateMultiElementAffinityScore(dst, src string) float64 {
+	if dst == "" || src == "" {
+		return minScore
+	}
+
+	if strings.Compare(dst, src) == 0 {
+		return maxScore
 	}
 
-	if len(scope) <= 0 {
-		return 0
+	// Calculate the number of multi-element matches divided by "|"
+	var score, elementLen int
+	dstElements := strings.Split(dst, "|")
+	srcElements := strings.Split(src, "|")
+	elementLen = mathutils.MaxInt(len(dstElements), len(srcElements))
+
+	// Maximum element length is 5
+	if elementLen > maxElementLen {
+		elementLen = maxElementLen
 	}
 
-	i := sort.SearchStrings(scope, value)
-	if i < 0 {
-		return 0
+	for i := 0; i < elementLen; i++ {
+		if strings.Compare(dstElements[i], srcElements[i]) != 0 {
+			break
+		}
+		score++
 	}
 
-	return 1
+	return float64(score) / float64(maxElementLen)
 }
diff --git a/manager/service/cdn_cluster.go b/manager/service/cdn_cluster.go
index 87f80452..596475e0 100644
--- a/manager/service/cdn_cluster.go
+++ b/manager/service/cdn_cluster.go
@@ -56,33 +56,6 @@ func (s *rest) DestroyCDNCluster(ctx context.Context, id uint) error {
 	return nil
 }
 
-func (s *rest) CreateCDNClusterWithSecurityGroupDomain(ctx context.Context, json types.CreateCDNClusterRequest) (*model.CDNCluster, error) {
-	securityGroup := model.SecurityGroup{
-		Domain: json.SecurityGroupDomain,
-	}
-	if err := s.db.WithContext(ctx).First(&securityGroup).Error; err != nil {
-		return s.CreateCDNCluster(ctx, json)
-	}
-
-	config, err := structutils.StructToMap(json.Config)
-	if err != nil {
-		return nil, err
-	}
-
-	cdnCluster := model.CDNCluster{
-		Name:   json.Name,
-		BIO:    json.BIO,
-		Config: config,
-	}
-
-	if err := s.db.WithContext(ctx).Model(&securityGroup).Association("CDNClusters").Append(&cdnCluster); err != nil {
-		return nil, err
-
-	}
-
-	return &cdnCluster, nil
-}
-
 func (s *rest) UpdateCDNCluster(ctx context.Context, id uint, json types.UpdateCDNClusterRequest) (*model.CDNCluster, error) {
 	config, err := structutils.StructToMap(json.Config)
 	if err != nil {
@@ -101,32 +74,6 @@ func (s *rest) UpdateCDNCluster(ctx context.Context, id uint, json types.UpdateC
 	return &cdnCluster, nil
 }
 
-func (s *rest) UpdateCDNClusterWithSecurityGroupDomain(ctx context.Context, id uint, json types.UpdateCDNClusterRequest) (*model.CDNCluster, error) {
-	securityGroup := model.SecurityGroup{
-		Domain: json.SecurityGroupDomain,
-	}
-	if err := s.db.WithContext(ctx).First(&securityGroup).Error; err != nil {
-		return s.UpdateCDNCluster(ctx, id, json)
-	}
-
-	config, err := structutils.StructToMap(json.Config)
-	if err != nil {
-		return nil, err
-	}
-
-	cdnCluster := model.CDNCluster{
-		Name:   json.Name,
-		BIO:    json.BIO,
-		Config: config,
-	}
-
-	if err := s.db.WithContext(ctx).Model(&securityGroup).Association("CDNClusters").Append(&cdnCluster); err != nil {
-		return nil, err
-	}
-
-	return &cdnCluster, nil
-}
-
 func (s *rest) GetCDNCluster(ctx context.Context, id uint) (*model.CDNCluster, error) {
 	cdnCluster := model.CDNCluster{}
 	if err := s.db.WithContext(ctx).First(&cdnCluster, id).Error; err != nil {
diff --git a/manager/service/scheduler_cluster.go b/manager/service/scheduler_cluster.go
index a77fe374..247ff31e 100644
--- a/manager/service/scheduler_cluster.go
+++ b/manager/service/scheduler_cluster.go
@@ -62,51 +62,6 @@ func (s *rest) CreateSchedulerCluster(ctx context.Context, json types.CreateSche
 	return &schedulerCluster, nil
 }
 
-func (s *rest) CreateSchedulerClusterWithSecurityGroupDomain(ctx context.Context, json types.CreateSchedulerClusterRequest) (*model.SchedulerCluster, error) {
-	securityGroup := model.SecurityGroup{
-		Domain: json.SecurityGroupDomain,
-	}
-	if err := s.db.WithContext(ctx).First(&securityGroup).Error; err != nil {
-		return s.CreateSchedulerCluster(ctx, json)
-	}
-
-	config, err := structutils.StructToMap(json.Config)
-	if err != nil {
-		return nil, err
-	}
-
-	clientConfig, err := structutils.StructToMap(json.ClientConfig)
-	if err != nil {
-		return nil, err
-	}
-
-	scopes, err := structutils.StructToMap(json.Scopes)
-	if err != nil {
-		return nil, err
-	}
-
-	schedulerCluster := model.SchedulerCluster{
-		Name:         json.Name,
-		BIO:          json.BIO,
-		Config:       config,
-		ClientConfig: clientConfig,
-		Scopes:       scopes,
-		IsDefault:    json.IsDefault,
-	}
-
-	if err := s.db.WithContext(ctx).Model(&securityGroup).Association("SchedulerClusters").Append(&schedulerCluster); err != nil {
-		return nil, err
-	}
-
-	if json.CDNClusterID > 0 {
-		if err := s.AddSchedulerClusterToCDNCluster(ctx, json.CDNClusterID, schedulerCluster.ID); err != nil {
-			return nil, err
-		}
-	}
-
-	return &schedulerCluster, nil
-}
-
 func (s *rest) DestroySchedulerCluster(ctx context.Context, id uint) error {
 	schedulerCluster := model.SchedulerCluster{}
 	if err := s.db.WithContext(ctx).First(&schedulerCluster, id).Error; err != nil {
@@ -157,51 +112,6 @@ func (s *rest) UpdateSchedulerCluster(ctx context.Context, id uint, json types.U
 	return &schedulerCluster, nil
 }
 
-func (s *rest) UpdateSchedulerClusterWithSecurityGroupDomain(ctx context.Context, id uint, json types.UpdateSchedulerClusterRequest) (*model.SchedulerCluster, error) {
-	securityGroup := model.SecurityGroup{
-		Domain: json.SecurityGroupDomain,
-	}
-	if err := s.db.WithContext(ctx).First(&securityGroup).Error; err != nil {
-		return s.UpdateSchedulerCluster(ctx, id, json)
-	}
-
-	config, err := structutils.StructToMap(json.Config)
-	if err != nil {
-		return nil, err
-	}
-
-	clientConfig, err := structutils.StructToMap(json.ClientConfig)
-	if err != nil {
-		return nil, err
-	}
-
-	scopes, err := structutils.StructToMap(json.Scopes)
-	if err != nil {
-		return nil, err
-	}
-
-	schedulerCluster := model.SchedulerCluster{
-		Name:         json.Name,
-		BIO:          json.BIO,
-		Config:       config,
-		ClientConfig: clientConfig,
-		Scopes:       scopes,
-		IsDefault:    json.IsDefault,
-	}
-
-	if err := s.db.WithContext(ctx).Model(&securityGroup).Association("SchedulerClusters").Append(&schedulerCluster); err != nil {
-		return nil, err
-	}
-
-	if json.CDNClusterID > 0 {
-		if err := s.AddSchedulerClusterToCDNCluster(ctx, json.CDNClusterID, schedulerCluster.ID); err != nil {
-			return nil, err
-		}
-	}
-
-	return &schedulerCluster, nil
-}
-
 func (s *rest) GetSchedulerCluster(ctx context.Context, id uint) (*model.SchedulerCluster, error) {
 	schedulerCluster := model.SchedulerCluster{}
 	if err := s.db.WithContext(ctx).Preload("CDNClusters").First(&schedulerCluster, id).Error; err != nil {
diff --git a/manager/service/security_group.go b/manager/service/security_group.go
index 635438a7..f0788b1e 100644
--- a/manager/service/security_group.go
+++ b/manager/service/security_group.go
@@ -25,10 +25,8 @@ import (
 
 func (s *rest) CreateSecurityGroup(ctx context.Context, json types.CreateSecurityGroupRequest) (*model.SecurityGroup, error) {
 	securityGroup := model.SecurityGroup{
-		Name:        json.Name,
-		BIO:         json.BIO,
-		Domain:      json.Domain,
-		ProxyDomain: json.ProxyDomain,
+		Name: json.Name,
+		BIO:  json.BIO,
 	}
 
 	if err := s.db.WithContext(ctx).Create(&securityGroup).Error; err != nil {
@@ -54,10 +52,8 @@ func (s *rest) DestroySecurityGroup(ctx context.Context, id uint) error {
 func (s *rest) UpdateSecurityGroup(ctx context.Context, id uint, json types.UpdateSecurityGroupRequest) (*model.SecurityGroup, error) {
 	securityGroup := model.SecurityGroup{}
 	if err := s.db.WithContext(ctx).First(&securityGroup, id).Updates(model.SecurityGroup{
-		Name:        json.Name,
-		BIO:         json.BIO,
-		Domain:      json.Domain,
-		ProxyDomain: json.ProxyDomain,
+		Name: json.Name,
+		BIO:  json.BIO,
 	}).Error; err != nil {
 		return nil, err
 	}
@@ -67,7 +63,7 @@ func (s *rest) UpdateSecurityGroup(ctx context.Context, id uint, json types.Upda
 
 func (s *rest) GetSecurityGroup(ctx context.Context, id uint) (*model.SecurityGroup, error) {
 	securityGroup := model.SecurityGroup{}
-	if err := s.db.WithContext(ctx).First(&securityGroup, id).Error; err != nil {
+	if err := s.db.WithContext(ctx).Preload("SecurityRules").First(&securityGroup, id).Error; err != nil {
 		return nil, err
 	}
 
@@ -78,9 +74,8 @@ func (s *rest) GetSecurityGroups(ctx context.Context, q types.GetSecurityGroupsQ
 	var count int64
 	var securityGroups []model.SecurityGroup
 	if err := s.db.WithContext(ctx).Scopes(model.Paginate(q.Page, q.PerPage)).Where(&model.SecurityGroup{
-		Name:   q.Name,
-		Domain: q.Domain,
-	}).Find(&securityGroups).Count(&count).Error; err != nil {
+		Name: q.Name,
+	}).Preload("SecurityRules").Find(&securityGroups).Count(&count).Error; err != nil {
 		return nil, 0, err
 	}
 
@@ -122,3 +117,39 @@ func (s *rest) AddCDNClusterToSecurityGroup(ctx context.Context, id, cdnClusterI
 
 	return nil
 }
+
+func (s *rest) AddSecurityRuleToSecurityGroup(ctx context.Context, id, securityRuleID uint) error {
+	securityGroup := model.SecurityGroup{}
+	if err := s.db.WithContext(ctx).First(&securityGroup, id).Error; err != nil {
+		return err
+	}
+
+	securityRule := model.SecurityRule{}
+	if err := s.db.WithContext(ctx).First(&securityRule, securityRuleID).Error; err != nil {
+		return err
+	}
+
+	if err := s.db.WithContext(ctx).Model(&securityGroup).Association("SecurityRules").Append(&securityRule); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *rest) DestroySecurityRuleToSecurityGroup(ctx context.Context, id, securityRuleID uint) error {
+	securityGroup := model.SecurityGroup{}
+	if err := s.db.WithContext(ctx).First(&securityGroup, id).Error; err != nil {
+		return err
+	}
+
+	securityRule := model.SecurityRule{}
+	if err := s.db.WithContext(ctx).First(&securityRule, securityRuleID).Error; err != nil {
+		return err
+	}
+
+	if err := s.db.WithContext(ctx).Model(&securityGroup).Association("SecurityRules").Delete(&securityRule); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/manager/service/security_rule.go b/manager/service/security_rule.go
new file mode 100644
index 00000000..0cf8a6a1
--- /dev/null
+++ b/manager/service/security_rule.go
@@ -0,0 +1,87 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package service
+
+import (
+	"context"
+
+	"d7y.io/dragonfly/v2/manager/model"
+	"d7y.io/dragonfly/v2/manager/types"
+)
+
+func (s *rest) CreateSecurityRule(ctx context.Context, json types.CreateSecurityRuleRequest) (*model.SecurityRule, error) {
+	securityRule := model.SecurityRule{
+		Name:        json.Name,
+		BIO:         json.BIO,
+		Domain:      json.Domain,
+		ProxyDomain: json.ProxyDomain,
+	}
+
+	if err := s.db.WithContext(ctx).Create(&securityRule).Error; err != nil {
+		return nil, err
+	}
+
+	return &securityRule, nil
+}
+
+func (s *rest) DestroySecurityRule(ctx context.Context, id uint) error {
+	securityRule := model.SecurityRule{}
+	if err := s.db.WithContext(ctx).First(&securityRule, id).Error; err != nil {
+		return err
+	}
+
+	if err := s.db.WithContext(ctx).Unscoped().Delete(&model.SecurityRule{}, id).Error; err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (s *rest) UpdateSecurityRule(ctx context.Context, id uint, json types.UpdateSecurityRuleRequest) (*model.SecurityRule, error) {
+	securityRule := model.SecurityRule{}
+	if err := s.db.WithContext(ctx).First(&securityRule, id).Updates(model.SecurityRule{
+		Name:        json.Name,
+		BIO:         json.BIO,
+		Domain:      json.Domain,
+		ProxyDomain: json.ProxyDomain,
+	}).Error; err != nil {
+		return nil, err
+	}
+
+	return &securityRule, nil
+}
+
+func (s *rest) GetSecurityRule(ctx context.Context, id uint) (*model.SecurityRule, error) {
+	securityRule := model.SecurityRule{}
+	if err := s.db.WithContext(ctx).First(&securityRule, id).Error; err != nil {
+		return nil, err
+	}
+
+	return &securityRule, nil
+}
+
+func (s *rest) GetSecurityRules(ctx context.Context, q types.GetSecurityRulesQuery) (*[]model.SecurityRule, int64, error) {
+	var count int64
+	var securityRules []model.SecurityRule
+	if err := s.db.WithContext(ctx).Scopes(model.Paginate(q.Page, q.PerPage)).Where(&model.SecurityRule{
+		Name: q.Name,
+	}).Find(&securityRules).Count(&count).Error; err != nil {
+		return nil, 0, err
+	}
+
+	return &securityRules, count, nil
+}
diff --git a/manager/service/service.go b/manager/service/service.go
index c1dfaa18..63b8c190 100644
--- a/manager/service/service.go
+++ b/manager/service/service.go
@@ -60,10 +60,8 @@ type REST interface {
 	GetOauths(context.Context, types.GetOauthsQuery) (*[]model.Oauth, int64, error)
 
 	CreateCDNCluster(context.Context, types.CreateCDNClusterRequest) (*model.CDNCluster, error)
-	CreateCDNClusterWithSecurityGroupDomain(context.Context, types.CreateCDNClusterRequest) (*model.CDNCluster, error)
 	DestroyCDNCluster(context.Context, uint) error
 	UpdateCDNCluster(context.Context, uint, types.UpdateCDNClusterRequest) (*model.CDNCluster, error)
-	UpdateCDNClusterWithSecurityGroupDomain(context.Context, uint, types.UpdateCDNClusterRequest) (*model.CDNCluster, error)
 	GetCDNCluster(context.Context, uint) (*model.CDNCluster, error)
 	GetCDNClusters(context.Context, types.GetCDNClustersQuery) (*[]model.CDNCluster, int64, error)
 	AddCDNToCDNCluster(context.Context, uint, uint) error
@@ -76,10 +74,8 @@ type REST interface {
 	GetCDNs(context.Context, types.GetCDNsQuery) (*[]model.CDN, int64, error)
 
 	CreateSchedulerCluster(context.Context, types.CreateSchedulerClusterRequest) (*model.SchedulerCluster, error)
-	CreateSchedulerClusterWithSecurityGroupDomain(context.Context, types.CreateSchedulerClusterRequest) (*model.SchedulerCluster, error)
 	DestroySchedulerCluster(context.Context, uint) error
 	UpdateSchedulerCluster(context.Context, uint, types.UpdateSchedulerClusterRequest) (*model.SchedulerCluster, error)
-	UpdateSchedulerClusterWithSecurityGroupDomain(context.Context, uint, types.UpdateSchedulerClusterRequest) (*model.SchedulerCluster, error)
 	GetSchedulerCluster(context.Context, uint) (*model.SchedulerCluster, error)
 	GetSchedulerClusters(context.Context, types.GetSchedulerClustersQuery) (*[]model.SchedulerCluster, int64, error)
 	AddSchedulerToSchedulerCluster(context.Context, uint, uint) error
@@ -90,6 +86,12 @@ type REST interface {
 	GetScheduler(context.Context, uint) (*model.Scheduler, error)
 	GetSchedulers(context.Context, types.GetSchedulersQuery) (*[]model.Scheduler, int64, error)
 
+	CreateSecurityRule(context.Context, types.CreateSecurityRuleRequest) (*model.SecurityRule, error)
+	DestroySecurityRule(context.Context, uint) error
+	UpdateSecurityRule(context.Context, uint, types.UpdateSecurityRuleRequest) (*model.SecurityRule, error)
+	GetSecurityRule(context.Context, uint) (*model.SecurityRule, error)
+	GetSecurityRules(context.Context, types.GetSecurityRulesQuery) (*[]model.SecurityRule, int64, error)
+
 	CreateSecurityGroup(context.Context, types.CreateSecurityGroupRequest) (*model.SecurityGroup, error)
 	DestroySecurityGroup(context.Context, uint) error
 	UpdateSecurityGroup(context.Context, uint, types.UpdateSecurityGroupRequest) (*model.SecurityGroup, error)
@@ -97,6 +99,8 @@ type REST interface {
 	GetSecurityGroups(context.Context, types.GetSecurityGroupsQuery) (*[]model.SecurityGroup, int64, error)
 	AddSchedulerClusterToSecurityGroup(context.Context, uint, uint) error
 	AddCDNClusterToSecurityGroup(context.Context, uint, uint) error
+	AddSecurityRuleToSecurityGroup(context.Context, uint, uint) error
+	DestroySecurityRuleToSecurityGroup(context.Context, uint, uint) error
 
 	CreateConfig(context.Context, types.CreateConfigRequest) (*model.Config, error)
 	DestroyConfig(context.Context, uint) error
diff --git a/manager/service/service_grpc.go b/manager/service/service_grpc.go
index d09e739d..28b94be5 100644
--- a/manager/service/service_grpc.go
+++ b/manager/service/service_grpc.go
@@ -66,7 +66,7 @@ func (s *GRPC) GetCDN(ctx context.Context, req *manager.GetCDNRequest) (*manager
 	// Cache Miss
 	logger.Infof("%s cache miss", cacheKey)
 	cdn := model.CDN{}
-	if err := s.db.WithContext(ctx).Preload("CDNCluster.SecurityGroup").First(&cdn, &model.CDN{
+	if err := s.db.WithContext(ctx).Preload("CDNCluster").First(&cdn, &model.CDN{
 		HostName:     req.HostName,
 		CDNClusterID: uint(req.CdnClusterId),
 	}).Error; err != nil {
@@ -93,13 +93,6 @@ func (s *GRPC) GetCDN(ctx context.Context, req *manager.GetCDNRequest) (*manager
 			Name:   cdn.CDNCluster.Name,
 			Bio:    cdn.CDNCluster.BIO,
 			Config: config,
-			SecurityGroup: &manager.SecurityGroup{
-				Id:          uint64(cdn.CDNCluster.SecurityGroup.ID),
-				Name:        cdn.CDNCluster.SecurityGroup.Name,
-				Bio:         cdn.CDNCluster.SecurityGroup.BIO,
-				Domain:      cdn.CDNCluster.SecurityGroup.Domain,
-				ProxyDomain: cdn.CDNCluster.SecurityGroup.ProxyDomain,
-			},
 		},
 	}
 
@@ -197,7 +190,7 @@ func (s *GRPC) GetScheduler(ctx context.Context, req *manager.GetSchedulerReques
 	// Cache Miss
 	logger.Infof("%s cache miss", cacheKey)
 	scheduler := model.Scheduler{}
-	if err := s.db.WithContext(ctx).Preload("SchedulerCluster.SecurityGroup").Preload("SchedulerCluster.CDNClusters.CDNs", &model.CDN{
+	if err := s.db.WithContext(ctx).Preload("SchedulerCluster").Preload("SchedulerCluster.CDNClusters.CDNs", &model.CDN{
 		Status: model.CDNStatusActive,
 	}).First(&scheduler, &model.Scheduler{
 		HostName:           req.HostName,
@@ -266,13 +259,6 @@ func (s *GRPC) GetScheduler(ctx context.Context, req *manager.GetSchedulerReques
 			Bio:          scheduler.SchedulerCluster.BIO,
 			Config:       schedulerClusterConfig,
 			ClientConfig: schedulerClusterClientConfig,
-			SecurityGroup: &manager.SecurityGroup{
-				Id:          uint64(scheduler.SchedulerCluster.SecurityGroup.ID),
-				Name:        scheduler.SchedulerCluster.SecurityGroup.Name,
-				Bio:         scheduler.SchedulerCluster.SecurityGroup.BIO,
-				Domain:      scheduler.SchedulerCluster.SecurityGroup.Domain,
-				ProxyDomain: scheduler.SchedulerCluster.SecurityGroup.ProxyDomain,
-			},
 		},
 		Cdns: pbCDNs,
 	}
@@ -391,7 +377,7 @@ func (s *GRPC) ListSchedulers(ctx context.Context, req *manager.ListSchedulersRe
 	// Cache Miss
 	logger.Infof("%s cache miss", cacheKey)
 	var schedulerClusters []model.SchedulerCluster
-	if err := s.db.WithContext(ctx).Preload("SecurityGroup").Find(&schedulerClusters).Error; err != nil {
+	if err := s.db.WithContext(ctx).Preload("SecurityGroup.SecurityRules").Preload("Schedulers", "status = ?", "active").Find(&schedulerClusters).Error; err != nil {
 		return nil, status.Error(codes.Unknown, err.Error())
 	}
 
diff --git a/manager/types/cdn_cluster.go b/manager/types/cdn_cluster.go
index 33c9379b..07ce8e6e 100644
--- a/manager/types/cdn_cluster.go
+++ b/manager/types/cdn_cluster.go
@@ -31,17 +31,15 @@ type AddSchedulerClusterToCDNClusterParams struct {
 }
 
 type CreateCDNClusterRequest struct {
-	Name                string            `json:"name" binding:"required"`
-	BIO                 string            `json:"bio" binding:"omitempty"`
-	Config              *CDNClusterConfig `json:"config" binding:"required"`
-	SecurityGroupDomain string            `json:"security_group_domain" binding:"omitempty"`
+	Name   string            `json:"name" binding:"required"`
+	BIO    string            `json:"bio" binding:"omitempty"`
+	Config *CDNClusterConfig `json:"config" binding:"required"`
 }
 
 type UpdateCDNClusterRequest struct {
-	Name                string            `json:"name" binding:"omitempty"`
-	BIO                 string            `json:"bio" binding:"omitempty"`
-	Config              *CDNClusterConfig `json:"config" binding:"omitempty"`
-	SecurityGroupDomain string            `json:"security_group_domain" binding:"omitempty"`
+	Name   string            `json:"name" binding:"omitempty"`
+	BIO    string            `json:"bio" binding:"omitempty"`
+	Config *CDNClusterConfig `json:"config" binding:"omitempty"`
 }
 
 type GetCDNClustersQuery struct {
diff --git a/manager/types/scheduler_cluster.go b/manager/types/scheduler_cluster.go
index 58da0b35..931222f4 100644
--- a/manager/types/scheduler_cluster.go
+++ b/manager/types/scheduler_cluster.go
@@ -26,25 +26,23 @@ type AddSchedulerToSchedulerClusterParams struct {
 }
 
 type CreateSchedulerClusterRequest struct {
-	Name                string                        `json:"name" binding:"required"`
-	BIO                 string                        `json:"bio" binding:"omitempty"`
-	Config              *SchedulerClusterConfig       `json:"config" binding:"required"`
-	ClientConfig        *SchedulerClusterClientConfig `json:"client_config" binding:"required"`
-	Scopes              *SchedulerClusterScopes       `json:"scopes" binding:"omitempty"`
-	IsDefault           bool                          `json:"is_default" binding:"omitempty"`
-	CDNClusterID        uint                          `json:"cdn_cluster_id" binding:"omitempty"`
-	SecurityGroupDomain string                        `json:"security_group_domain" binding:"omitempty"`
+	Name         string                        `json:"name" binding:"required"`
+	BIO          string                        `json:"bio" binding:"omitempty"`
+	Config       *SchedulerClusterConfig       `json:"config" binding:"required"`
+	ClientConfig *SchedulerClusterClientConfig `json:"client_config" binding:"required"`
+	Scopes       *SchedulerClusterScopes       `json:"scopes" binding:"omitempty"`
+	IsDefault    bool                          `json:"is_default" binding:"omitempty"`
+	CDNClusterID uint                          `json:"cdn_cluster_id" binding:"omitempty"`
 }
 
 type UpdateSchedulerClusterRequest struct {
-	Name                string                        `json:"name" binding:"omitempty"`
-	BIO                 string                        `json:"bio" binding:"omitempty"`
-	Config              *SchedulerClusterConfig       `json:"config" binding:"omitempty"`
-	ClientConfig        *SchedulerClusterClientConfig `json:"client_config" binding:"omitempty"`
-	Scopes              *SchedulerClusterScopes       `json:"scopes" binding:"omitempty"`
-	IsDefault           bool                          `json:"is_default" binding:"omitempty"`
-	CDNClusterID        uint                          `json:"cdn_cluster_id" binding:"omitempty"`
-	SecurityGroupDomain string                        `json:"security_group_domain" binding:"omitempty"`
+	Name         string                        `json:"name" binding:"omitempty"`
+	BIO          string                        `json:"bio" binding:"omitempty"`
+	Config       *SchedulerClusterConfig       `json:"config" binding:"omitempty"`
+	ClientConfig *SchedulerClusterClientConfig `json:"client_config" binding:"omitempty"`
+	Scopes       *SchedulerClusterScopes       `json:"scopes" binding:"omitempty"`
+	IsDefault    bool                          `json:"is_default" binding:"omitempty"`
+	CDNClusterID uint                          `json:"cdn_cluster_id" binding:"omitempty"`
 }
 
 type GetSchedulerClustersQuery struct {
diff --git a/manager/types/security_group.go b/manager/types/security_group.go
index e01c4e0d..4c9fdbcf 100644
--- a/manager/types/security_group.go
+++ b/manager/types/security_group.go
@@ -30,23 +30,23 @@ type AddCDNClusterToSecurityGroupParams struct {
 	CDNClusterID uint `uri:"cdn_cluster_id" binding:"required"`
 }
 
+type AddSecurityRuleToSecurityGroupParams struct {
+	ID             uint `uri:"id" binding:"required"`
+	SecurityRuleID uint `uri:"security_rule_id" binding:"required"`
+}
+
 type CreateSecurityGroupRequest struct {
-	Name        string `json:"name" binding:"required"`
-	BIO         string `json:"bio" binding:"omitempty"`
-	Domain      string `json:"domain" binding:"required"`
-	ProxyDomain string `json:"proxy_domain" binding:"omitempty"`
+	Name string `json:"name" binding:"required"`
+	BIO  string `json:"bio" binding:"omitempty"`
 }
 
 type UpdateSecurityGroupRequest struct {
-	Name        string `json:"name" binding:"omitempty"`
-	BIO         string `json:"bio" binding:"omitempty"`
-	Domain      string `json:"domain" binding:"omitempty"`
-	ProxyDomain string `json:"proxy_domain" binding:"omitempty"`
+	Name string `json:"name" binding:"omitempty"`
+	BIO  string `json:"bio" binding:"omitempty"`
 }
 
 type GetSecurityGroupsQuery struct {
+	Name    string `form:"name" binding:"omitempty"`
 	Page    int    `form:"page" binding:"omitempty,gte=1"`
 	PerPage int    `form:"per_page" binding:"omitempty,gte=1,lte=50"`
-	Name    string `form:"name" binding:"omitempty"`
-	Domain  string `form:"domain" binding:"omitempty"`
 }
diff --git a/manager/types/security_rule.go b/manager/types/security_rule.go
new file mode 100644
index 00000000..4351578f
--- /dev/null
+++ b/manager/types/security_rule.go
@@ -0,0 +1,43 @@
+/*
+ *     Copyright 2020 The Dragonfly Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package types
+
+type SecurityRuleParams struct {
+	ID uint `uri:"id" binding:"required"`
+}
+
+type CreateSecurityRuleRequest struct {
+	Name        string `json:"name" binding:"required"`
+	BIO         string `json:"bio" binding:"omitempty"`
+	Domain      string `json:"domain" binding:"required"`
+	ProxyDomain string `json:"proxy_domain" binding:"omitempty"`
+}
+
+type UpdateSecurityRuleRequest struct {
+	Name        string `json:"name" binding:"omitempty"`
+	BIO         string `json:"bio" binding:"omitempty"`
+	Domain      string `json:"domain" binding:"omitempty"`
+	ProxyDomain string `json:"proxy_domain" binding:"omitempty"`
+}
+
+type GetSecurityRulesQuery struct {
+	Page        int    `form:"page" binding:"omitempty,gte=1"`
+	PerPage     int    `form:"per_page" binding:"omitempty,gte=1,lte=50"`
+	Name        string `form:"name" binding:"omitempty"`
+	Domain      string `form:"domain" binding:"omitempty"`
+	ProxyDomain string `form:"proxy_domain" binding:"omitempty"`
+}


Test patch:
diff --git a/manager/searcher/searcher_test.go b/manager/searcher/searcher_test.go
index a4c9e070..b9c67e43 100644
--- a/manager/searcher/searcher_test.go
+++ b/manager/searcher/searcher_test.go
@@ -55,7 +55,17 @@ func TestSchedulerCluster(t *testing.T) {
 				{
 					Name: "foo",
 					SecurityGroup: model.SecurityGroup{
-						Domain: "domain-1",
+						SecurityRules: []model.SecurityRule{
+							{
+								Domain: "domain-1",
+							},
+						},
+					},
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "foo",
+							Status:   "active",
+						},
 					},
 				},
 				{
@@ -77,9 +87,21 @@ func TestSchedulerCluster(t *testing.T) {
 					Scopes: map[string]interface{}{
 						"location": []string{"location-1"},
 					},
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "foo",
+							Status:   "active",
+						},
+					},
 				},
 				{
 					Name: "bar",
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "bar",
+							Status:   "active",
+						},
+					},
 				},
 			},
 			conditions: map[string]string{"location": "location-1"},
@@ -97,9 +119,21 @@ func TestSchedulerCluster(t *testing.T) {
 					Scopes: map[string]interface{}{
 						"idc": []string{"idc-1"},
 					},
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "foo",
+							Status:   "active",
+						},
+					},
 				},
 				{
 					Name: "bar",
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "bar",
+							Status:   "active",
+						},
+					},
 				},
 			},
 			conditions: map[string]string{"idc": "idc-1"},
@@ -109,6 +143,38 @@ func TestSchedulerCluster(t *testing.T) {
 				assert.Equal(ok, true)
 			},
 		},
+		{
+			name: "match according to net topology condition",
+			schedulerClusters: []model.SchedulerCluster{
+				{
+					Name: "foo",
+					Scopes: map[string]interface{}{
+						"net_topology": []string{"net-topology-1"},
+					},
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "foo",
+							Status:   "active",
+						},
+					},
+				},
+				{
+					Name: "bar",
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "bar",
+							Status:   "active",
+						},
+					},
+				},
+			},
+			conditions: map[string]string{"net_topology": "net-topology-1"},
+			expect: func(t *testing.T, data model.SchedulerCluster, ok bool) {
+				assert := assert.New(t)
+				assert.Equal(data.Name, "foo")
+				assert.Equal(ok, true)
+			},
+		},
 		{
 			name: "match according to location and idc condition",
 			schedulerClusters: []model.SchedulerCluster{
@@ -118,9 +184,21 @@ func TestSchedulerCluster(t *testing.T) {
 						"location": []string{"location-1"},
 						"idc":      []string{"idc-1"},
 					},
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "foo",
+							Status:   "active",
+						},
+					},
 				},
 				{
 					Name: "bar",
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "bar",
+							Status:   "active",
+						},
+					},
 				},
 			},
 			conditions: map[string]string{
@@ -142,11 +220,27 @@ func TestSchedulerCluster(t *testing.T) {
 						"location": []string{"location-1"},
 					},
 					SecurityGroup: model.SecurityGroup{
-						Domain: "domain-1",
+						SecurityRules: []model.SecurityRule{
+							{
+								Domain: "domain-1",
+							},
+						},
+					},
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "foo",
+							Status:   "active",
+						},
 					},
 				},
 				{
 					Name: "bar",
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "bar",
+							Status:   "active",
+						},
+					},
 				},
 			},
 			conditions: map[string]string{
@@ -168,11 +262,27 @@ func TestSchedulerCluster(t *testing.T) {
 						"idc": []string{"idc-1"},
 					},
 					SecurityGroup: model.SecurityGroup{
-						Domain: "domain-1",
+						SecurityRules: []model.SecurityRule{
+							{
+								Domain: "domain-1",
+							},
+						},
+					},
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "foo",
+							Status:   "active",
+						},
 					},
 				},
 				{
 					Name: "bar",
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "bar",
+							Status:   "active",
+						},
+					},
 				},
 			},
 			conditions: map[string]string{
@@ -195,11 +305,27 @@ func TestSchedulerCluster(t *testing.T) {
 						"location": []string{"location-1"},
 					},
 					SecurityGroup: model.SecurityGroup{
-						Domain: "domain-1",
+						SecurityRules: []model.SecurityRule{
+							{
+								Domain: "domain-1",
+							},
+						},
+					},
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "foo",
+							Status:   "active",
+						},
 					},
 				},
 				{
 					Name: "bar",
+					Schedulers: []model.Scheduler{
+						{
+							HostName: "bar",
+							Status:   "active",
+						},
+					},
 				},
 			},
 			conditions: map[string]string{
@@ -223,127 +349,3 @@ func TestSchedulerCluster(t *testing.T) {
 		})
 	}
 }
-
-func TestCalculateSchedulerClusterMean(t *testing.T) {
-	tests := []struct {
-		name       string
-		conditions map[string]string
-		rawScopes  map[string]interface{}
-		expect     func(t *testing.T, mean float64)
-	}{
-		{
-			name:       "conditions and rawScopes is empty",
-			conditions: map[string]string{},
-			rawScopes:  map[string]interface{}{},
-			expect: func(t *testing.T, mean float64) {
-				assert := assert.New(t)
-				assert.Equal(mean, float64(0))
-			},
-		},
-		{
-			name: "missed matches",
-			conditions: map[string]string{
-				"location": "location-1",
-			},
-			rawScopes: map[string]interface{}{
-				"idc": []string{"idc-1"},
-			},
-			expect: func(t *testing.T, mean float64) {
-				assert := assert.New(t)
-				assert.Equal(mean, float64(0))
-			},
-		},
-		{
-			name: "match according to location",
-			conditions: map[string]string{
-				"location": "location-1",
-			},
-			rawScopes: map[string]interface{}{
-				"location": []string{"location-1"},
-			},
-			expect: func(t *testing.T, mean float64) {
-				assert := assert.New(t)
-				assert.Equal(mean, float64(conditionLocationWeight))
-			},
-		},
-		{
-			name: "match according to idc",
-			conditions: map[string]string{
-				"idc": "idc-1",
-			},
-			rawScopes: map[string]interface{}{
-				"idc": []string{"idc-1"},
-			},
-			expect: func(t *testing.T, mean float64) {
-				assert := assert.New(t)
-				assert.Equal(mean, float64(conditionIDCWeight))
-			},
-		},
-		{
-			name: "match according to location and idc",
-			conditions: map[string]string{
-				"location": "location-1",
-				"idc":      "idc-1",
-			},
-			rawScopes: map[string]interface{}{
-				"location": []string{"location-1"},
-				"idc":      []string{"idc-1"},
-			},
-			expect: func(t *testing.T, mean float64) {
-				assert := assert.New(t)
-				assert.Equal(mean, float64(conditionLocationWeight+conditionIDCWeight))
-			},
-		},
-	}
-
-	for _, tc := range tests {
-		t.Run(tc.name, func(t *testing.T) {
-			mean := calculateSchedulerClusterMean(tc.conditions, tc.rawScopes)
-			tc.expect(t, mean)
-		})
-	}
-}
-
-func TestCalculateConditionScore(t *testing.T) {
-	tests := []struct {
-		name   string
-		value  string
-		scope  []string
-		expect func(t *testing.T, score float64)
-	}{
-		{
-			name:  "value is empty",
-			value: "",
-			scope: []string{"foo"},
-			expect: func(t *testing.T, score float64) {
-				assert := assert.New(t)
-				assert.Equal(score, float64(0))
-			},
-		},
-		{
-			name:  "scope is empty",
-			value: "foo",
-			scope: []string{},
-			expect: func(t *testing.T, score float64) {
-				assert := assert.New(t)
-				assert.Equal(score, float64(0))
-			},
-		},
-		{
-			name:  "match according to value",
-			value: "foo",
-			scope: []string{"foo"},
-			expect: func(t *testing.T, score float64) {
-				assert := assert.New(t)
-				assert.Equal(score, float64(1))
-			},
-		},
-	}
-
-	for _, tc := range tests {
-		t.Run(tc.name, func(t *testing.T) {
-			score := calculateConditionScore(tc.value, tc.scope)
-			tc.expect(t, score)
-		})
-	}
-}