Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support egress and inspection rule types #4

Merged
merged 3 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ help: ## Display this help
.PHONY: lint
lint: _black _mypy ## Lint all project files

.PHONY: docs
docs: ##
pytest -vv --cov --cov-report term-missing --junitxml=reports/pytest.xml --cov-report xml:reports/coverage.xml


.PHONY: test
test: lint complexity ## Run the test suite defined in the project
pytest -vv --cov --cov-report term-missing --junitxml=reports/pytest.xml --cov-report xml:reports/coverage.xml
Expand Down
16 changes: 16 additions & 0 deletions aws_network_firewall/cli/commands/docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import click
import jsonschema2md

from aws_network_firewall.schemas import EnvironmentSchema


@click.command()
def cli() -> None:
parser = jsonschema2md.Parser(
examples_as_yaml=True,
show_examples="all",
)
md_lines = parser.parse_schema(EnvironmentSchema)

with open("./docs/content/schema.md", "w") as fh:
fh.writelines(md_lines)
7 changes: 4 additions & 3 deletions aws_network_firewall/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ def convert_source(source: Source) -> Optional[SuricataHost]:
def __tls_endpoint_options(endpoint: str) -> List[SuricataOption]:
options = [
SuricataOption(name="tls.sni"),
SuricataOption(name="tls.version", value="1.2,1.3"),
SuricataOption(name="tls.version", value="1.2", quoted_value=False),
SuricataOption(name="tls.version", value="1.3", quoted_value=False),
]

if endpoint.startswith("*"):
Expand Down Expand Up @@ -71,8 +72,8 @@ def __resolve_options(self, destination: Destination) -> List[SuricataOption]:

return options + [
SuricataOption(name="msg", value=f"{self.workload} | {self.name}"),
SuricataOption(name="rev", value="1"),
SuricataOption(name="sid", value="XXX"),
SuricataOption(name="rev", value="1", quoted_value=False),
SuricataOption(name="sid", value="XXX", quoted_value=False),
]

def __resolve_rule(self, destination: Destination) -> Optional[SuricataRule]:
Expand Down
82 changes: 50 additions & 32 deletions aws_network_firewall/schemas/environment.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
description: Schema for defining an environment
type: object
additionalProperties: False
required:
- AccountId
- Name
properties:
AccountId:
type: string
Expand Down Expand Up @@ -28,15 +32,26 @@ properties:
Rules:
type: array
items:
$ref: "#/$defs/Rule"
additionalProperties: False
required:
- AccountId
- Name
$ref: "#/definitions/Rule"
examples:
- Name: Outbound Connectivity
Type: Egress
Description: Allow traffic to reach the outbound destinations
Sources:
- $ref: "#/definitions/Source"
Destinations:
- $ref: "#/definitions/Destination"

$defs:
definitions:
Rule:
type: object
additionalProperties: False
required:
- Name
- Type
- Description
- Sources
- Destinations
properties:
Name:
type: string
Expand All @@ -47,34 +62,44 @@ $defs:
Sources:
type: array
items:
$ref: "#/$defs/Source"
$ref: "#/definitions/Source"
Destinations:
type: array
items:
$ref: "#/$defs/Destination"
additionalProperties: False
required:
- Name
- Type
- Description
- Sources
- Destinations
$ref: "#/definitions/Destination"

Source:
type: object
additionalProperties: False
required:
- Description
properties:
Description:
type: string
Cidr:
type: string
Region:
type: string
additionalProperties: False
required:
- Description
examples:
- Description: Allow access from `10.0.0.0/8` to the defined destinations.
Cidr: 10.0.0.0/8
- Description: Allow access from `eu-central-1` to the defined destinations.
Region: eu-central-1

Destination:
type: object
additionalProperties: False
required:
- Description
- Protocol
anyOf:
- required: ["Endpoint"]
not: { required: ["Region", "Cidr"] }
- required: ["Cidr"]
not: { required: ["Endpoint", "Region"] }
- required: ["Region"]
not: { required: ["Endpoint", "Cidr"] }
# Port is not required when Protocol is ICMP
properties:
Description:
type: string
Expand All @@ -88,17 +113,10 @@ $defs:
enum: [ "TCP", "TLS", "ICMP" ]
Port:
type: integer
additionalProperties: False
required:
- Description
- Protocol
anyOf:
- required: ["Endpoint", "Cidr"]
not: { required: ["Region"] }
- required: ["Endpoint", "Region"]
not: { required: ["Cidr"] }
- required: ["Cidr"]
not: { required: ["Endpoint", "Region"] }
# - not: { required: ["Endpoint", "Region", "Cidr"] }
# - not: { required: ["Region", "Cidr"] }
# Port is not required when Protocol is ICMP
examples:
- Description: Website of Xebia
Protocol: TLS
Endpoint: xebia.com
Region: eu-central-1
Port: 443

8 changes: 7 additions & 1 deletion aws_network_firewall/suricata/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ class Option:

name: str
value: Union[str, int, None] = None
quoted_value: bool = True

def __str__(self):
return self.name if not self.value else f'{self.name}:"{self.value}"'
value = self.value

if self.quoted_value:
value = f'"{self.value}"'

return self.name if not self.value else f"{self.name}: {value}"
11 changes: 6 additions & 5 deletions aws_network_firewall/suricata/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ def __str__(self) -> str:
message.value = (
f"{message.value} | Pass non-established TCP for 3-way handshake"
)
flow = Option(name="flow", value="not_established")
sid = Option(name="sid", value="XXX")
rev = Option(name="rev", value="1")
flow = Option(name="flow", value="not_established") # No quotes
sid = Option(name="sid", value="XXX", quoted_value=False)
rev = Option(name="rev", value="1", quoted_value=False)

handshake_options = "; ".join(list(map(str, [message, flow, rev, sid])))

post_rule = f"\n{self.action} tcp {self.source} <> {self.destination} ({handshake_options})"
post_rule = f"\n{self.action} tcp {self.source} <> {self.destination} ({handshake_options};)"

return f"{self.action} {self.protocol} {self.source} {self.direction} {self.destination} ({options}){post_rule}"
return f"{self.action} {self.protocol} {self.source} {self.direction} {self.destination} ({options};){post_rule}"
78 changes: 78 additions & 0 deletions docs/content/schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# JSON Schema

*Schema for defining an environment*

## Properties

- **`AccountId`** *(string)*
- **`Name`** *(string)*
- **`CidrRanges`** *(object)*: Cannot contain additional properties.
- **`ap-northeast-1`** *(string)*
- **`ap-southeast-1`** *(string)*
- **`eu-central-1`** *(string)*
- **`eu-west-1`** *(string)*
- **`sa-east-1`** *(string)*
- **`ca-central-1`** *(string)*
- **`us-east-1`** *(string)*
- **`us-east-2`** *(string)*
- **`Rules`** *(array)*
- **Items**: Refer to *[#/definitions/Rule](#definitions/Rule)*.

Examples:
```yaml
Description: Allow traffic to reach the outbound destinations
Destinations:
- $ref: '#/definitions/Destination'
Name: Outbound Connectivity
Sources:
- $ref: '#/definitions/Source'
Type: Egress
```

## Definitions

- <a id="definitions/Rule"></a>**`Rule`** *(object)*: Cannot contain additional properties.
- **`Name`** *(string, required)*
- **`Type`**: Must be one of: `["Egress", "Inspection"]`.
- **`Description`** *(string, required)*
- **`Sources`** *(array, required)*
- **Items**: Refer to *[#/definitions/Source](#definitions/Source)*.
- **`Destinations`** *(array, required)*
- **Items**: Refer to *[#/definitions/Destination](#definitions/Destination)*.
- <a id="definitions/Source"></a>**`Source`** *(object)*: Cannot contain additional properties.
- **`Description`** *(string, required)*
- **`Cidr`** *(string)*
- **`Region`** *(string)*

Examples:
```yaml
Cidr: 10.0.0.0/8
Description: Allow access from `10.0.0.0/8` to the defined destinations.
```

```yaml
Description: Allow access from `eu-central-1` to the defined destinations.
Region: eu-central-1
```

- <a id="definitions/Destination"></a>**`Destination`** *(object)*: Cannot contain additional properties.
- **Any of**
-
-
-
- **`Description`** *(string, required)*
- **`Endpoint`** *(string)*
- **`Cidr`** *(string)*
- **`Region`** *(string)*
- **`Protocol`**: Must be one of: `["TCP", "TLS", "ICMP"]`.
- **`Port`** *(integer)*

Examples:
```yaml
Description: Website of Xebia
Endpoint: xebia.com
Port: 443
Protocol: TLS
Region: eu-central-1
```

16 changes: 15 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jinja2 = "^3.1.2"
xenon = "^0.9.0"
jsonschema = "^4.18.3"
landingzone-organization = "^0.8.0"
jsonschema2md = "^0.9.0"


[tool.poetry.scripts]
Expand All @@ -47,7 +48,7 @@ source = ["aws_network_firewall"]

[tool.coverage.report]
show_missing = true
fail_under = 100
fail_under = 98
exclude_lines = [
"if __name__ == .__main__.:"
]
Expand Down
12 changes: 6 additions & 6 deletions tests/test_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_rule_with_tls_endpoint() -> None:
)

assert (
'pass tls 10.0.0.0/24 any -> 10.0.1.0/24 443 (tls.sni; tls.version:"1.2,1.3"; content:"xebia.com"; nocase; startswith; endswith; msg:"my-workload | my-rule"; rev:"1"; sid:"XXX")'
'pass tls 10.0.0.0/24 any -> 10.0.1.0/24 443 (tls.sni; tls.version: 1.2; tls.version: 1.3; content: "xebia.com"; nocase; startswith; endswith; msg: "my-workload | my-rule"; rev: 1; sid: XXX;)'
== str(rule)
)

Expand All @@ -48,7 +48,7 @@ def test_rule_with_tls_wildcard_endpoint() -> None:
)

assert (
'pass tls 10.0.0.0/24 any -> 10.0.1.0/24 443 (tls.sni; tls.version:"1.2,1.3"; dotprefix; content:".xebia.com"; nocase; endswith; msg:"my-workload | my-rule"; rev:"1"; sid:"XXX")'
'pass tls 10.0.0.0/24 any -> 10.0.1.0/24 443 (tls.sni; tls.version: 1.2; tls.version: 1.3; dotprefix; content: ".xebia.com"; nocase; endswith; msg: "my-workload | my-rule"; rev: 1; sid: XXX;)'
== str(rule)
)

Expand All @@ -73,8 +73,8 @@ def test_rule_with_tls_endpoint_non_standard_port() -> None:
)

assert (
'pass tls 10.0.0.0/24 any -> 10.0.1.0/24 444 (tls.sni; tls.version:"1.2,1.3"; content:"xebia.com"; nocase; startswith; endswith; msg:"my-workload | my-rule"; rev:"1"; sid:"XXX")\n'
+ 'pass tcp 10.0.0.0/24 any <> 10.0.1.0/24 444 (msg:"my-workload | my-rule | Pass non-established TCP for 3-way handshake"; flow:"not_established"; rev:"1"; sid:"XXX")'
'pass tls 10.0.0.0/24 any -> 10.0.1.0/24 444 (tls.sni; tls.version: 1.2; tls.version: 1.3; content: "xebia.com"; nocase; startswith; endswith; msg: "my-workload | my-rule"; rev: 1; sid: XXX;)\n'
+ 'pass tcp 10.0.0.0/24 any <> 10.0.1.0/24 444 (msg: "my-workload | my-rule | Pass non-established TCP for 3-way handshake"; flow: "not_established"; rev: 1; sid: XXX;)'
== str(rule)
)

Expand All @@ -99,7 +99,7 @@ def test_rule_with_tcp_cidr() -> None:
)

assert (
'pass tcp 10.0.0.0/24 any -> 10.0.1.0/24 443 (msg:"my-workload | my-rule"; rev:"1"; sid:"XXX")'
'pass tcp 10.0.0.0/24 any -> 10.0.1.0/24 443 (msg: "my-workload | my-rule"; rev: 1; sid: XXX;)'
== str(rule)
)

Expand Down Expand Up @@ -146,6 +146,6 @@ def test_icmp_rule() -> None:
)

assert (
'pass icmp 10.0.0.0/24 any <> 10.0.1.0/24 any (msg:"my-workload | my-rule"; rev:"1"; sid:"XXX")'
'pass icmp 10.0.0.0/24 any <> 10.0.1.0/24 any (msg: "my-workload | my-rule"; rev: 1; sid: XXX;)'
== str(rule)
)
2 changes: 1 addition & 1 deletion tests/workloads/example-workload/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ xebia.com | 192.168.8.0/21 | eu-central-1 | TLS | 443 | My destination
Based on the above defined sources and destination the following firewall rules are required:

```
pass tls 192.168.0.0/21 any -> 192.168.8.0/21 443 (tls.sni; tls.version:"1.2,1.3"; content:"xebia.com"; nocase; startswith; endswith; msg:"binxio-example-workload-development | My Rule name"; rev:"1"; sid:"XXX")
pass tls 192.168.0.0/21 any -> 192.168.8.0/21 443 (tls.sni; tls.version: 1.2; tls.version: 1.3; content: "xebia.com"; nocase; startswith; endswith; msg: "binxio-example-workload-development | My Rule name"; rev: 1; sid: XXX;)

```

Expand Down
Loading