├── .github
├── FALCO_VERSIONS
├── PULL_REQUEST_TEMPLATE.md
├── compare-rule-files.sh
├── dependabot.yml
├── scripts
│ ├── .python-version
│ ├── pyproject.toml
│ ├── rules_overview_generator.py
│ └── uv.lock
└── workflows
│ ├── .yamllint
│ ├── create-comment.yaml
│ ├── pages.yaml
│ ├── registry.yaml
│ ├── release.yaml
│ ├── rules.yaml
│ └── yaml-lint.yaml
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── OWNERS
├── README.md
├── RELEASE.md
├── archive
├── README.md
├── application_rules.yaml
└── falco-deprecated_rules.yaml
├── build
├── checker
│ ├── .gitignore
│ ├── cmd
│ │ ├── common.go
│ │ ├── common_test.go
│ │ ├── compare.go
│ │ ├── compare_test.go
│ │ ├── root.go
│ │ └── validate.go
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── mitre_attack_checker
│ ├── .python-version
│ ├── README.md
│ ├── build.sh
│ ├── falco_mitre_attack_checker
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ ├── api
│ │ │ ├── __init__.py
│ │ │ └── core.py
│ │ ├── cli
│ │ │ ├── __init__.py
│ │ │ └── core.py
│ │ ├── engine
│ │ │ ├── __init__.py
│ │ │ └── mitre_checker.py
│ │ ├── exceptions
│ │ │ ├── __init__.py
│ │ │ └── rules_exceptions.py
│ │ ├── models
│ │ │ ├── __init__.py
│ │ │ ├── falco_mitre_errors.py
│ │ │ └── falco_mitre_relations.py
│ │ ├── parsers
│ │ │ ├── __init__.py
│ │ │ ├── falco_rules.py
│ │ │ └── mitre_stix.py
│ │ ├── tests
│ │ │ ├── __init__.py
│ │ │ ├── engine
│ │ │ │ ├── __init__.py
│ │ │ │ └── test_mitre_checker.py
│ │ │ ├── parsers
│ │ │ │ ├── __init__.py
│ │ │ │ ├── test_falco_rules.py
│ │ │ │ └── test_mitre_stix.py
│ │ │ ├── resources
│ │ │ │ ├── falco_rules_test.yaml
│ │ │ │ └── not_falco_rules_test.yaml
│ │ │ └── test_common.py
│ │ └── utils
│ │ │ ├── __init__.py
│ │ │ ├── file.py
│ │ │ └── logger.py
│ ├── install.sh
│ ├── poetry.lock
│ └── pyproject.toml
└── registry
│ ├── cmd.go
│ ├── config_layer.go
│ ├── go.mod
│ ├── go.sum
│ ├── index.go
│ ├── index_test.go
│ ├── oci.go
│ ├── registry.go
│ ├── requirements.go
│ ├── requirements_test.go
│ ├── s3.go
│ ├── tag.go
│ ├── tag_test.go
│ ├── targz.go
│ └── testdata
│ ├── index1.yaml
│ ├── index2.yaml
│ ├── index_expected1.yaml
│ ├── registry.yaml
│ ├── rules-failed-req.yaml
│ ├── rules-numeric-req.yaml
│ └── rules-semver-req.yaml
├── docs
└── images
│ ├── announce.png
│ ├── arrow.png
│ ├── cross.png
│ ├── insight.png
│ ├── setting.png
│ └── start.png
├── mkdocs.yml
├── proposals
└── 20230605-rules-adoption-management-maturity-framework.md
├── registry.yaml
└── rules
├── OWNERS
├── falco-incubating_rules.yaml
├── falco-sandbox_rules.yaml
└── falco_rules.yaml
/.github/FALCO_VERSIONS:
--------------------------------------------------------------------------------
1 | master
2 | 0.42.0
3 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | **What type of PR is this?**
10 |
11 | > Uncomment one (or more) `/kind <>` lines:
12 |
13 | > /kind feature
14 |
15 | > /kind bug
16 |
17 | > /kind cleanup
18 |
19 | > /kind design
20 |
21 | > /kind documentation
22 |
23 | > /kind failing-test
24 |
25 |
26 |
29 |
30 | **Any specific area of the project related to this PR?**
31 |
32 | > Uncomment one (or more) `/area <>` lines:
33 |
34 | > /area rules
35 |
36 | > /area registry
37 |
38 | > /area build
39 |
40 | > /area documentation
41 |
42 |
45 |
46 | **Proposed rule [maturity level](https://github.com/falcosecurity/rules/blob/main/CONTRIBUTING.md#maturity-levels)**
47 |
48 | > Uncomment one (or more) `/area <>` lines (only for PRs that add or modify rules):
49 |
50 | > /area maturity-stable
51 |
52 | > /area maturity-incubating
53 |
54 | > /area maturity-sandbox
55 |
56 | > /area maturity-deprecated
57 |
58 |
61 |
62 | **What this PR does / why we need it**:
63 |
64 | **Which issue(s) this PR fixes**:
65 |
66 |
71 |
72 | Fixes #
73 |
74 | **Special notes for your reviewer**:
75 |
--------------------------------------------------------------------------------
/.github/compare-rule-files.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | RULES_FILE=$1
4 | RESULT_FILE=$2
5 | CHECKER_TOOL=$3
6 | FALCO_DOCKER_IMAGE=$4
7 |
8 | set -e pipefail
9 |
10 | rm -f $RESULT_FILE
11 | touch $RESULT_FILE
12 |
13 | cur_branch=`git rev-parse HEAD`
14 | echo Current branch is \"$cur_branch\"
15 | echo Checking version for rules file \"$RULES_FILE\"...
16 | cp $RULES_FILE tmp_rule_file.yaml
17 |
18 | rules_name=`echo $RULES_FILE | sed -re 's/rules\/(.*)_rules\.yaml/\1/'`
19 | echo Searching tag with prefix prefix \"$rules_name-rules-\"...
20 | git_rev=`git rev-list --tags="$rules_name-rules-*.*.*" --max-count=1`
21 |
22 | if [ -z "$git_rev" ]
23 | then
24 | echo Not previous tag has been found
25 | exit 0
26 | fi
27 |
28 | latest_tag=`git describe --match="$rules_name-rules-*.*.*" --exclude="$rules_name-rules-*.*.*-*" --abbrev=0 --tags ${git_rev} || echo ""`
29 |
30 | if [ -z "$latest_tag" ]
31 | then
32 | echo Not previous tag has been found
33 | exit 0
34 | else
35 | echo Most recent tag found is \"$latest_tag\"
36 | fi
37 |
38 | git checkout tags/$latest_tag
39 | chmod +x $CHECKER_TOOL
40 | $CHECKER_TOOL \
41 | compare \
42 | --falco-image=$FALCO_DOCKER_IMAGE \
43 | -l $RULES_FILE \
44 | -r tmp_rule_file.yaml \
45 | 1>tmp_res.txt
46 | git switch --detach $cur_branch
47 |
48 | echo '##' $(basename $RULES_FILE) >> $RESULT_FILE
49 | echo Comparing \`$cur_branch\` with latest tag \`$latest_tag\` >> $RESULT_FILE
50 | echo "" >> $RESULT_FILE
51 | if [ -s tmp_res.txt ]
52 | then
53 | cat tmp_res.txt >> $RESULT_FILE
54 | else
55 | echo "No changes detected" >> $RESULT_FILE
56 | fi
57 | echo "" >> $RESULT_FILE
58 |
59 | if $(git branch --show-current | grep -q "release/"); then
60 | if $(cat $RESULT_FILE | grep -q "\*\*Minor\*\* changes") || $(cat $RESULT_FILE | grep -q "\*\*Major\*\* changes"); then
61 | echo "**Notes**:" >> $RESULT_FILE
62 | echo "" >> $RESULT_FILE
63 | echo "This PR proposes merging major or minor changes into a release branch. Please make sure this is intentional. cc @falcosecurity/rules-maintainers" >> $RESULT_FILE
64 | echo "" >> $RESULT_FILE
65 | echo "/hold" >> $RESULT_FILE
66 | echo "" >> $RESULT_FILE
67 | fi
68 | fi
69 |
70 | rm -f tmp_rule_file.yaml
71 | rm -f tmp_res.txt
72 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | open-pull-requests-limit: 10
8 | groups:
9 | actions:
10 | update-types:
11 | - "minor"
12 | - "patch"
13 |
--------------------------------------------------------------------------------
/.github/scripts/.python-version:
--------------------------------------------------------------------------------
1 | 3.12
2 |
--------------------------------------------------------------------------------
/.github/scripts/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "scripts"
3 | version = "0.1.0"
4 | description = "GHA scripts to publish pages"
5 | readme = ""
6 | requires-python = ">=3.12"
7 | dependencies = [
8 | "pandas>=2.2.3",
9 | "pyyaml>=6.0.2",
10 | "tabulate>=0.9.0",
11 | ]
12 |
--------------------------------------------------------------------------------
/.github/scripts/rules_overview_generator.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import yaml
3 | import argparse
4 | import datetime
5 | import os
6 | import sys
7 | import re
8 |
9 | """
10 | Usage:
11 | pip install -r .github/scripts/requirements.txt
12 | python .github/scripts/rules_overview_generator.py --rules_dir=rules > docs/index.md
13 | """
14 |
15 | BASE_MITRE_URL_TECHNIQUE="https://attack.mitre.org/techniques/"
16 | BASE_MITRE_URL_TACTIC="https://attack.mitre.org/tactics/"
17 | BASE_PCI_DSS="https://docs-prv.pcisecuritystandards.org/PCI%20DSS/Standard/PCI-DSS-v4_0.pdf"
18 | BASE_NIST="https://csf.tools/reference/nist-sp-800-53/r5/"
19 | COLUMNS=['maturity', 'rule', 'desc', 'workload', 'mitre_phase', 'mitre_ttp', 'extra_tags', 'compliance_pci_dss', 'compliance_nist', 'extra_tags_list', 'mitre_phase_list', 'compliance_pci_dss_list', 'compliance_nist_list', 'enabled']
20 |
21 | def arg_parser():
22 | parser = argparse.ArgumentParser()
23 | parser.add_argument('--rules_dir', help='Path to falco rules directory containing all rules yaml files.')
24 | return parser.parse_args()
25 |
26 | def rules_to_df(rules_dir):
27 | l = []
28 | for rules_filename in os.listdir(rules_dir):
29 | if not 'falco' in rules_filename:
30 | continue
31 | with open(os.path.join(rules_dir, rules_filename), 'r') as f:
32 | items = yaml.safe_load(f)
33 | for item in items:
34 | if 'rule' in item and 'tags' in item:
35 | if len(item['tags']) > 0:
36 | item['maturity'], item['workload'], item['mitre_phase'], item['mitre_ttp'], item['compliance_pci_dss'], item['compliance_nist'], item['extra_tags'] = [], [], [], [], [], [], []
37 | for i in item['tags']:
38 | if i.startswith('maturity_'):
39 | item['maturity'].append(i) # should be just one per rule, be resilient and treat as list as well
40 | elif i.startswith('PCI_DSS_'):
41 | item['compliance_pci_dss'].append('[{}]({})'.format(i, BASE_PCI_DSS))
42 | elif i.startswith('NIST_800-53_'):
43 | # NIST links: revisit in the future, could be fragile
44 | item['compliance_nist'].append('[{}]({}{}/{})'.format(i, BASE_NIST, re.search('NIST_800-53_(.*)-', i, re.IGNORECASE).group(1).lower(), \
45 | i.replace('NIST_800-53_', '').lower()))
46 | elif i in ['host', 'container']:
47 | item['workload'].append(i)
48 | elif i.startswith('mitre_'):
49 | item['mitre_phase'].append(i)
50 | elif i.startswith('T'):
51 | if i.startswith('TA'):
52 | item['mitre_ttp'].append('[{}]({}{})'.format(i, BASE_MITRE_URL_TACTIC, i.replace('.', '/')))
53 | else:
54 | item['mitre_ttp'].append('[{}]({}{})'.format(i, BASE_MITRE_URL_TECHNIQUE, i.replace('.', '/')))
55 | else:
56 | item['extra_tags'].append(i)
57 | item['workload'].sort()
58 | item['mitre_phase'].sort()
59 | item['mitre_ttp'].sort()
60 | item['compliance_pci_dss'].sort()
61 | item['compliance_nist'].sort()
62 | item['mitre_phase_list'] = item['mitre_phase']
63 | item['extra_tags_list'] = item['extra_tags']
64 | item['compliance_pci_dss_list'] = item['compliance_pci_dss']
65 | item['compliance_nist_list'] = item['compliance_nist']
66 | item['enabled'] = (item['enabled'] if 'enabled' in item else True)
67 | l.append([', '.join(item[x]) if x in ['maturity', 'workload', 'mitre_phase', 'mitre_ttp', 'compliance_pci_dss', 'compliance_nist', 'extra_tags'] else item[x] for x in COLUMNS])
68 |
69 | if not l:
70 | sys.exit('No valid rules in any of the falco_rules.* files in the rules_dir or no falco_rules.* files in the rules_dir in the first place, exiting ...')
71 | df = pd.DataFrame.from_records(l, columns=COLUMNS)
72 | return df.sort_values(by=['maturity','rule'], inplace=False)
73 |
74 | def print_markdown(df):
75 | n_rules=len(df)
76 | df_overview = df.drop(['extra_tags_list', 'mitre_phase_list', 'compliance_pci_dss_list', 'compliance_nist_list'], axis=1)
77 | maturity_col_name = '
maturity
'
78 | df_overview.rename(columns={ \
79 | 'maturity': maturity_col_name, \
80 | 'rule': 'rule
', \
81 | 'desc': 'desc
', \
82 | 'workload': 'workload
', \
83 | 'mitre_phase': 'mitre_phase
', \
84 | 'mitre_ttp': 'mitre_ttp
', \
85 | 'extra_tags': 'extra_tags
', \
86 | 'compliance_pci_dss': 'compliance_pci_dss
', \
87 | 'compliance_nist': 'compliance_nist
', \
88 | 'enabled': 'enabled
', \
89 | }, inplace=True)
90 |
91 | df_stable = df_overview[(df_overview[maturity_col_name] == 'maturity_stable')]
92 | df_incubating = df_overview[(df_overview[maturity_col_name] == 'maturity_incubating')]
93 | df_sandbox = df_overview[(df_overview[maturity_col_name] == 'maturity_sandbox')]
94 | df_deprecated = df_overview[(df_overview[maturity_col_name] == 'maturity_deprecated')]
95 |
96 | print('# Falco Rules Overview\n')
97 | print('Last Updated: {}\n'.format(datetime.date.today()))
98 | print('This auto-generated document is derived from the `falco*_rules.yaml` files within the [rules](https://github.com/falcosecurity/rules/blob/main/rules/) directory of the main branch in the official Falco [rules repository](https://github.com/falcosecurity/rules/tree/main).\n')
99 | print('The Falco Project manages a total of {} [rules](https://github.com/falcosecurity/rules/blob/main/rules/), of which {} rules are included in the Falco release package and labeled with [maturity_stable](https://github.com/falcosecurity/rules/blob/main/CONTRIBUTING.md#rules-maturity-framework). Rules at the remaining maturity levels require explicit installation and may need extra customization to ensure effective adoption. Lastly, certain rules are intentionally disabled by default, irrespective of their maturity level.\n'.format(n_rules, len(df_stable)))
100 | print('This document provides an extensive overview of community-contributed syscall and container event-based rules. It offers resources for learning about these rules, promoting successful adoption, and driving future enhancements.\n')
101 | print('\n[Stable Falco Rules](#stable-falco-rules) | [Incubating Falco Rules](#incubating-falco-rules) | [Sandbox Falco Rules](#sandbox-falco-rules) | [Deprecated Falco Rules](#deprecated-falco-rules) | [Falco Rules Stats](#falco-rules-stats)\n')
102 | print('\nThe tables below can be scrolled to the right.\n')
103 |
104 | print('\n## Stable Falco Rules\n')
105 | print('\n{} stable Falco rules ({:.2f}% of rules) are included in the Falco release package:\n'.format(len(df_stable), (100.0 * len(df_stable) / n_rules)))
106 | print(df_stable.to_markdown(index=False))
107 |
108 | print('\n## Incubating Falco Rules\n')
109 | print('\n{} incubating Falco rules ({:.2f}% of rules):\n'.format(len(df_incubating), (100.0 * len(df_incubating) / n_rules)))
110 | print(df_incubating.to_markdown(index=False))
111 |
112 | print('\n## Sandbox Falco Rules\n')
113 | print('\n{} sandbox Falco rules ({:.2f}% of rules):\n'.format(len(df_sandbox), (100.0 * len(df_sandbox) / n_rules)))
114 | print(df_sandbox.to_markdown(index=False))
115 |
116 | print('\n## Deprecated Falco Rules\n')
117 | print('\n{} deprecated Falco rules ({:.2f}% of rules):\n'.format(len(df_deprecated), (100.0 * len(df_deprecated) / n_rules)))
118 | print(df_deprecated.to_markdown(index=False))
119 |
120 | print('\n# Falco Rules Stats\n')
121 | print('\n### Falco rules per workload type:\n')
122 | df1 = df.groupby('workload').agg(rule_count=('workload', 'count'))
123 | df1['percentage'] = round(100.0 * df1['rule_count'] / df1['rule_count'].sum(), 2).astype(str) + '%'
124 | print(df1.to_markdown(index=True))
125 |
126 | print('\n### Falco rules per [Mitre Attack](https://attack.mitre.org/) phase:\n')
127 | df2 = df[['rule', 'maturity', 'mitre_phase_list']].explode('mitre_phase_list')
128 | df2.rename(columns={'mitre_phase_list':'mitre_phase'}, inplace=True)
129 | df2.sort_values(by=['mitre_phase','rule'], inplace=True)
130 | df2['rule'] = df[['maturity', 'rule']].agg(': '.join, axis=1)
131 | mitre_phase_col_name = 'mitre_phase
'
132 | df2.rename(columns={'mitre_phase': mitre_phase_col_name, \
133 | }, inplace=True)
134 | df2 = df2.groupby(mitre_phase_col_name).agg({'rule': lambda x: ['\n'.join(list(x)), len(list(x))]})
135 | df2['rules
'] = df2['rule'].apply(lambda x: x[0])
136 | df2['percentage
'] = df2['rule'].apply(lambda x: round((100.0 * x[1] / n_rules), 2)).astype(str) + '%'
137 | print(df2.drop('rule', axis=1).to_markdown(index=True))
138 |
139 | print('\n### Compliance-related Falco rules:\n')
140 | df3 = df
141 | df3['compliance_tag'] = df['compliance_pci_dss_list'] + df['compliance_nist_list']
142 | df3.sort_values(by=['rule'], inplace=True)
143 | compliance_tag_col_name = 'compliance_tag
'
144 | df3.rename(columns={'compliance_tag': compliance_tag_col_name, \
145 | }, inplace=True)
146 | df3 = df3[['rule', compliance_tag_col_name, 'maturity']].explode(compliance_tag_col_name)
147 | df3 = df3.groupby(compliance_tag_col_name).agg({'rule': lambda x: ['\n'.join(list(x)), len(list(x))]})
148 | df3['rules
'] = df3['rule'].apply(lambda x: x[0])
149 | # df3['percentage'] = df3['rule'].apply(lambda x: round((100.0 * x[1] / n_rules), 2)).astype(str) + '%'
150 | print(df3.drop('rule', axis=1).to_markdown(index=True))
151 |
152 |
153 | if __name__ == '__main__':
154 | args_parsed = arg_parser()
155 | rules_dir = args_parsed.rules_dir
156 |
157 | if not rules_dir or not os.path.isdir(rules_dir):
158 | sys.exit('No valid rules directory provided via --rules_dir arg, exiting ...')
159 |
160 | print_markdown(rules_to_df(rules_dir))
161 |
--------------------------------------------------------------------------------
/.github/workflows/.yamllint:
--------------------------------------------------------------------------------
1 | extends: default
2 |
3 | rules:
4 | line-length:
5 | max: 130
6 | level: error
7 |
--------------------------------------------------------------------------------
/.github/workflows/create-comment.yaml:
--------------------------------------------------------------------------------
1 | # NOTE: This has read-write repo token and access to secrets, so this must
2 | # not run any untrusted code.
3 | # see: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
4 | name: Comment on the pull request
5 |
6 | on:
7 | workflow_run:
8 | workflows: ["Rules"]
9 | types:
10 | - completed
11 |
12 | jobs:
13 | upload:
14 | runs-on: ubuntu-latest
15 | if: github.event.workflow_run.event == 'pull_request'
16 | steps:
17 | - name: 'Download artifact'
18 | uses: actions/github-script@v7.0.1
19 | with:
20 | script: |
21 | var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
22 | owner: context.repo.owner,
23 | repo: context.repo.repo,
24 | run_id: ${{github.event.workflow_run.id }},
25 | });
26 | var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
27 | return artifact.name == "pr"
28 | })[0];
29 | var download = await github.rest.actions.downloadArtifact({
30 | owner: context.repo.owner,
31 | repo: context.repo.repo,
32 | artifact_id: matchArtifact.id,
33 | archive_format: 'zip',
34 | });
35 | var fs = require('fs');
36 | fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
37 |
38 | - name: 'Unpack artifact'
39 | run: unzip pr.zip
40 |
41 | - name: 'Comment on PR'
42 | uses: actions/github-script@v7.0.1
43 | with:
44 | github-token: ${{ secrets.GITHUB_TOKEN }}
45 | script: |
46 | var fs = require('fs');
47 | var issue_number = Number(fs.readFileSync('./NR'));
48 | var comment_body = fs.readFileSync('./COMMENT');
49 | await github.rest.issues.createComment({
50 | owner: context.repo.owner,
51 | repo: context.repo.repo,
52 | issue_number: issue_number,
53 | body: comment_body.toString('utf8')
54 | });
55 |
--------------------------------------------------------------------------------
/.github/workflows/pages.yaml:
--------------------------------------------------------------------------------
1 | name: Deploy Github Pages
2 | on:
3 | push:
4 | branches: [main]
5 |
6 | permissions:
7 | contents: read
8 | pages: write
9 | id-token: write
10 |
11 | concurrency:
12 | group: "pages"
13 | cancel-in-progress: true
14 |
15 | jobs:
16 | deploy-pages:
17 | environment:
18 | name: github-pages
19 | url: ${{ steps.deployment.outputs.page_url }}
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
23 |
24 | - name: Install uv
25 | uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v5
26 |
27 | - name: Generate updated inventory
28 | run: |
29 | cd .github/scripts/
30 | uv run rules_overview_generator.py --rules_dir=../../rules > ../../docs/index.md
31 |
32 | - name: Disable Table Of Content for overview
33 | run: |
34 | sed -i '1s/^/---\nhide:\n- toc\n---\n\n/' docs/index.md
35 |
36 | - run: uvx --with mkdocs-material mkdocs build
37 |
38 | - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
39 | with:
40 | path: 'site'
41 |
42 | - id: deployment
43 | uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
44 |
--------------------------------------------------------------------------------
/.github/workflows/registry.yaml:
--------------------------------------------------------------------------------
1 | name: Registry
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | validate:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout Rules
14 | uses: actions/checkout@v4
15 |
16 | - name: Setup Golang
17 | uses: actions/setup-go@v5
18 | with:
19 | go-version: '^1.19'
20 |
21 | - name: Build registry artifact tool
22 | working-directory: build/registry
23 | run: go build -o rules-registry ./...
24 |
25 | - name: Test registry artifact tool
26 | working-directory: build/registry
27 | run: go test ./... -cover
28 |
29 | - name: Check Registry validity
30 | run: build/registry/rules-registry check registry.yaml
31 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release Rulesfile
2 |
3 | on:
4 | push:
5 | tags:
6 | # potential semver
7 | - '[a-z]*[0-9]+.[0-9]+.[0-9]+*'
8 |
9 | jobs:
10 | release-rulesfile:
11 | runs-on: ubuntu-latest
12 |
13 | env:
14 | OCI_REGISTRY: ghcr.io
15 |
16 | AWS_S3_BUCKET: falco-distribution
17 | AWS_S3_PREFIX: rules
18 | AWS_S3_REGION: eu-west-1
19 |
20 | permissions:
21 | # These permissions are needed to interact with GitHub's OIDC Token endpoint.
22 | id-token: write
23 | contents: read
24 | packages: write
25 |
26 | steps:
27 |
28 | # Get rules repository
29 | - name: Checkout Rules
30 | uses: actions/checkout@v4
31 |
32 | # Get registry artifact tool
33 | - name: Setup Golang
34 | uses: actions/setup-go@v5
35 | with:
36 | go-version-file: build/registry/go.mod
37 |
38 | - name: Build registry artifact tool
39 | working-directory: build/registry
40 | run: go build -o rules-registry ./...
41 |
42 | - name: Get lowercase OCI repo prefix
43 | run: |
44 | echo "OCI_REPO_PREFIX=ghcr.io/${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV}
45 |
46 | - name: Upload OCI artifacts to GitHub packages
47 | id: oci_build
48 | env:
49 | REGISTRY_USER: ${{ github.repository_owner }}
50 | REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 | GITHUB_REPO_URL: ${{ github.server_url }}/${{ github.repository }}.git
52 |
53 | # uses OCI_REPO_PREFIX environment variable
54 | run: >-
55 | echo "ARTIFACT_REPO_DIGEST=$(
56 | build/registry/rules-registry push-to-oci registry.yaml ${{ github.ref_name }}
57 | )" >> $GITHUB_OUTPUT
58 |
59 | # Create a signature of the rules artifact as OCI artifact
60 | - name: Install Cosign
61 | uses: sigstore/cosign-installer@v3.10.0
62 |
63 | - name: Login with cosign
64 | run: cosign login $OCI_REGISTRY --username ${{ github.repository_owner }} --password ${{ secrets.GITHUB_TOKEN }}
65 |
66 | - name: Sign the images with GitHub OIDC Token
67 | run: cosign sign --yes ${{ steps.oci_build.outputs.ARTIFACT_REPO_DIGEST }}
68 |
69 | - name: Configure AWS credentials
70 | uses: aws-actions/configure-aws-credentials@v4
71 | with:
72 | role-to-assume: "arn:aws:iam::292999226676:role/terraform-20230120142903096000000002"
73 | aws-region: ${{ env.AWS_S3_REGION }}
74 |
75 | - name: Upload files to S3
76 | # uses AWS_S3_BUCKET, AWS_S3_PREFIX, AWS_S3_REGION environment variables
77 | run: build/registry/rules-registry upload-to-s3 registry.yaml ${{ github.ref_name }}
78 |
--------------------------------------------------------------------------------
/.github/workflows/rules.yaml:
--------------------------------------------------------------------------------
1 | name: Rules
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - main
7 | - release/*
8 | push:
9 | branches:
10 | - main
11 |
12 | jobs:
13 | # retrieves the changed rules files and the Falco versions to be used
14 | get-values:
15 | runs-on: ubuntu-latest
16 | outputs:
17 | changed-files: ${{ steps.set-changed-files.outputs.changed-files }}
18 | falco-versions: ${{ steps.set-falco-versions.outputs.versions }}
19 | steps:
20 | - name: Checkout rules
21 | uses: actions/checkout@v4
22 |
23 | - name: Get changed files
24 | id: changed-files
25 | if: github.event_name == 'pull_request'
26 | uses: Ana06/get-changed-files@v2.3.0
27 | with:
28 | format: space-delimited
29 | token: ${{ secrets.GITHUB_TOKEN }}
30 |
31 | - name: Find changed rules files
32 | id: set-changed-files
33 | run: |
34 | # Find any changed file located under the /rules folder that matches the naming convention _rules.yaml.
35 | # See https://github.com/falcosecurity/rules/blob/main/README.md#naming-convention for details.
36 | # Additionally, if we skip changed-files because we're not in a pull request,
37 | # then we consider all the rules contained in the repository.
38 | all_files="${{ steps.changed-files.outputs.all }}"
39 | values=""
40 | if [ -z "$all_files" ]; then
41 | values=$(ls rules/*_rules.yaml)
42 | else
43 | for changed_file in $all_files; do
44 | if [[ "${changed_file}" =~ ^rules/[^/]*_rules\.yaml$ ]]; then
45 | values=${values}${changed_file}$'\n'
46 | fi
47 | done
48 | fi
49 | echo "changed-files=$(echo "${values}" | jq -R -s -c 'split("\n")' | jq -c 'map(select(length > 0))')" >> $GITHUB_OUTPUT
50 |
51 | - name: Read Falco versions
52 | id: set-falco-versions
53 | run: |
54 | values=""
55 | while read -r line
56 | do
57 | values="${values}${line}"$'\n'
58 | done < "./.github/FALCO_VERSIONS"
59 | echo "versions=$(echo "${values}" | jq -R -s -c 'split("\n")' | jq -c 'map(select(length > 0))')" >> $GITHUB_OUTPUT
60 |
61 | validate:
62 | if: needs.get-values.outputs.changed-files != '[]' && needs.get-values.outputs.changed-files != ''
63 | needs: get-values
64 | strategy:
65 | fail-fast: false
66 | matrix:
67 | rules-file: ${{ fromJson(needs.get-values.outputs.changed-files) }}
68 | falco-version: ${{ fromJson(needs.get-values.outputs.falco-versions) }}
69 | runs-on: ubuntu-latest
70 | steps:
71 | - name: Setup Golang
72 | uses: actions/setup-go@v5
73 | with:
74 | go-version: "1.19.0"
75 |
76 | - name: Checkout rules
77 | uses: actions/checkout@v4
78 |
79 | - name: Build checker tool
80 | working-directory: build/checker
81 | run: go build -o rules-check
82 |
83 | - name: Test checker tool
84 | working-directory: build/checker
85 | run: go test ./... -cover
86 |
87 | - name: Validate rules file
88 | run: |
89 | build/checker/rules-check \
90 | validate \
91 | --falco-image="falcosecurity/falco:${{ matrix.falco-version }}" \
92 | -r ${{ matrix.rules-file }}
93 |
94 | check-version:
95 | if: github.event_name == 'pull_request' && needs.get-values.outputs.changed-files != '[]' && needs.get-values.outputs.changed-files != ''
96 | needs: get-values
97 | env:
98 | # note(jasondellaluce): using the most recent targeted Falco version
99 | FALCO_VERSION: ${{ fromJson(needs.get-values.outputs.falco-versions)[0] }}
100 | strategy:
101 | fail-fast: false
102 | matrix:
103 | rules-file: ${{ fromJson(needs.get-values.outputs.changed-files) }}
104 | runs-on: ubuntu-latest
105 | steps:
106 | - name: Setup Golang
107 | uses: actions/setup-go@v5
108 | with:
109 | go-version: "1.19.0"
110 |
111 | - name: Checkout rules
112 | uses: actions/checkout@v4
113 |
114 | - name: Get all git tags
115 | run: git fetch --tags origin
116 |
117 | - name: Get changed files
118 | uses: Ana06/get-changed-files@v2.3.0
119 | id: changed
120 | with:
121 | format: space-delimited
122 | token: ${{ secrets.GITHUB_TOKEN }}
123 |
124 | - name: Build checker tool
125 | working-directory: build/checker
126 | run: go build -o rules-check
127 |
128 | - name: Test checker tool
129 | working-directory: build/checker
130 | run: go test ./... -cover
131 |
132 | - name: Compare changed files with previous versions
133 | id: compare
134 | run: |
135 | ./.github/compare-rule-files.sh \
136 | "${{ matrix.rules-file }}" \
137 | result.txt \
138 | build/checker/rules-check \
139 | "falcosecurity/falco:$FALCO_VERSION"
140 | if [ -s result.txt ]; then
141 | echo "comment_file=result.txt" >> $GITHUB_OUTPUT
142 | fi
143 |
144 | - name: Save PR info
145 | if: steps.compare.outputs.comment_file != ''
146 | run: |
147 | mkdir -p ./pr
148 | cp ${{ steps.compare.outputs.comment_file }} ./pr/COMMENT-${{ strategy.job-index }}
149 |
150 | - name: Upload PR info as artifact
151 | uses: actions/upload-artifact@v4
152 | if: steps.compare.outputs.comment_file != ''
153 | with:
154 | name: pr-${{ strategy.job-index }}
155 | path: pr/
156 | retention-days: 1
157 |
158 | upload-pr-info:
159 | needs: [get-values, check-version]
160 | if: ${{ !cancelled() && github.event_name == 'pull_request' && needs.get-values.outputs.changed-files != '[]' && needs.get-values.outputs.changed-files != '' }}
161 | runs-on: ubuntu-latest
162 | steps:
163 | - name: Download PR infos
164 | uses: actions/download-artifact@v4
165 | with:
166 | path: tmp-artifacts
167 |
168 | - name: Save PR info
169 | run: |
170 | if [ ! -d "./tmp-artifacts/" ]; then
171 | echo "No PR info found. Skipping."
172 | exit 0
173 | fi
174 | mkdir -p ./pr
175 | echo ${{ github.event.number }} > ./pr/NR
176 | touch ./pr/COMMENT
177 | echo "# Rules files suggestions" >> ./pr/COMMENT
178 | echo "" >> ./pr/COMMENT
179 | files=$(find ./tmp-artifacts/)
180 | for file in $files; do
181 | if [[ "$file" =~ "COMMENT" ]]; then
182 | cat "$file" >> ./pr/COMMENT
183 | fi
184 | done
185 | echo Uploading PR info...
186 | cat ./pr/COMMENT
187 | echo ""
188 |
189 | - name: Upload PR info as artifact
190 | uses: actions/upload-artifact@v4
191 | with:
192 | name: pr
193 | path: pr/
194 | retention-days: 1
195 | if-no-files-found: warn
196 |
--------------------------------------------------------------------------------
/.github/workflows/yaml-lint.yaml:
--------------------------------------------------------------------------------
1 | name: Yamllint Github Actions
2 | on:
3 | pull_request:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | lintFalcoRules:
9 | name: Yamllint
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout repo
13 | uses: actions/checkout@v4
14 | - name: yaml-lint
15 | uses: ibiqlik/action-yamllint@v3
16 | with:
17 | file_or_dir: rules/*.yaml
18 | env:
19 | YAMLLINT_CONFIG_FILE: .github/workflows/.yamllint
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Specific project files
2 | build/mitre_attack_checker/build
3 | build/mitre_attack_checker/reports
4 | **/falco_rules_mitre_errors.json
5 | **/application_rules_errors.json
6 |
7 | # IntelliJ project files
8 | .idea
9 | *.iml
10 | out
11 | gen
12 | ### Go template
13 | # If you prefer the allow list template instead of the deny list, see community template:
14 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
15 | #
16 | # Binaries for programs and plugins
17 | *.exe
18 | *.exe~
19 | *.dll
20 | *.so
21 | *.dylib
22 |
23 | # Test binary, built with `go test -c`
24 | *.test
25 |
26 | # Output of the go coverage tool, specifically when used with LiteIDE
27 | *.out
28 |
29 | # Dependency directories (remove the comment below to include it)
30 | # vendor/
31 |
32 | # Go workspace file
33 | go.work
34 |
35 | ### Python template
36 | # Byte-compiled / optimized / DLL files
37 | __pycache__/
38 | *.py[cod]
39 | *$py.class
40 |
41 | # C extensions
42 | *.so
43 |
44 | # Distribution / packaging
45 | .Python
46 | develop-eggs/
47 | dist/
48 | downloads/
49 | eggs/
50 | .eggs/
51 | lib/
52 | lib64/
53 | parts/
54 | sdist/
55 | var/
56 | wheels/
57 | share/python-wheels/
58 | *.egg-info/
59 | .installed.cfg
60 | *.egg
61 | MANIFEST
62 |
63 | # PyInstaller
64 | # Usually these files are written by a python script from a template
65 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
66 | *.manifest
67 | *.spec
68 |
69 | # Installer logs
70 | pip-log.txt
71 | pip-delete-this-directory.txt
72 |
73 | # Unit test / coverage reports
74 | htmlcov/
75 | .tox/
76 | .nox/
77 | .coverage
78 | .coverage.*
79 | .cache
80 | nosetests.xml
81 | coverage.xml
82 | *.cover
83 | *.py,cover
84 | .hypothesis/
85 | .pytest_cache/
86 | cover/
87 |
88 | # Translations
89 | *.mo
90 | *.pot
91 |
92 | # Django stuff:
93 | *.log
94 | local_settings.py
95 | db.sqlite3
96 | db.sqlite3-journal
97 |
98 | # Flask stuff:
99 | instance/
100 | .webassets-cache
101 |
102 | # Scrapy stuff:
103 | .scrapy
104 |
105 | # Sphinx documentation
106 | docs/_build/
107 |
108 | # PyBuilder
109 | .pybuilder/
110 | target/
111 |
112 | # Jupyter Notebook
113 | .ipynb_checkpoints
114 |
115 | # IPython
116 | profile_default/
117 | ipython_config.py
118 |
119 | # pyenv
120 | # For a library or package, you might want to ignore these files since the code is
121 | # intended to run in multiple environments; otherwise, check them in:
122 | # .python-version
123 |
124 | # pipenv
125 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
126 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
127 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
128 | # install all needed dependencies.
129 | #Pipfile.lock
130 |
131 | # poetry
132 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
133 | # This is especially recommended for binary packages to ensure reproducibility, and is more
134 | # commonly ignored for libraries.
135 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
136 | #poetry.lock
137 |
138 | # pdm
139 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
140 | #pdm.lock
141 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
142 | # in version control.
143 | # https://pdm.fming.dev/#use-with-ide
144 | .pdm.toml
145 |
146 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
147 | __pypackages__/
148 |
149 | # Celery stuff
150 | celerybeat-schedule
151 | celerybeat.pid
152 |
153 | # SageMath parsed files
154 | *.sage.py
155 |
156 | # Environments
157 | .env
158 | .venv
159 | env/
160 | venv/
161 | ENV/
162 | env.bak/
163 | venv.bak/
164 |
165 | # Spyder project settings
166 | .spyderproject
167 | .spyproject
168 |
169 | # Rope project settings
170 | .ropeproject
171 |
172 | # mkdocs documentation
173 | /site
174 |
175 | # mypy
176 | .mypy_cache/
177 | .dmypy.json
178 | dmypy.json
179 |
180 | # Pyre type checker
181 | .pyre/
182 |
183 | # pytype static type analyzer
184 | .pytype/
185 |
186 | # Cython debug symbols
187 | cython_debug/
188 |
189 | # PyCharm
190 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
191 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
192 | # and can be added to the global gitignore or merged into this file. For a more nuclear
193 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
194 | .idea/
195 | .run/
196 |
197 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for your interest in contributing to Falco's rules!
4 |
5 | This repository includes a dedicated guide for contributing rules, outlining the definitions of the rules maturity framework and the criteria for rule acceptance. This guide inherits from the general [contributing](https://github.com/falcosecurity/.github/blob/main/CONTRIBUTING.md) guide.
6 |
7 | All rules must be licensed under the [Apache 2.0 License](./LICENSE).
8 |
9 |
10 | **Table of Contents**
11 |
12 | * [Rules Maturity Framework](#rules-maturity-framework)
13 | * [Rules Acceptance Criteria](#rules-acceptance-criteria)
14 |
15 |
16 | # Rules Maturity Framework
17 |
18 | The rules maturity framework was established following this [proposal](proposals/20230605-rules-adoption-management-maturity-framework.md).
19 |
20 | At a high level, The Falco Project maintains community-contributed syscall and container event-based [rules](https://github.com/falcosecurity/rules/blob/main/rules/), with `maturity_stable` tagged rules being included in the Falco release package. Other maturity-level rules are released separately, requiring explicit installation and possible customization for effective. In essence, there are now dedicated rule files for each maturity level.
21 |
22 | The next sections will dive deeper into how the framework works and offer guidance on selecting a maturity level for specific rules.
23 |
24 | ## Overall Guidelines
25 |
26 | As specified in the tags section of the [Style Guide of Falco Rules](https://falco.org/docs/rules/style-guide/#tags), every rule upstreamed to The Falco Project must include a maturity level as its first tag.
27 |
28 | A new rule typically starts as `maturity_sandbox` and, in some cases, as `maturity_incubating`. However, it cannot immediately be at the `maturity_stable` level.
29 |
30 | Only rules at the `maturity_stable` level are distributed with the Falco release package and live in the established `falco_rules.yaml` file. All rules at the remaining maturity levels can be found in the Falco rules file according to their respective levels, and they need to be installed separately. They are made available to the adopter through the same means as the `falco_rules.yaml` file, either by directly retrieving them from this repository or by fetching them via `falcoctl`. Adopters have the flexibility to choose how they install and customize the upstream rules to suit their needs.
31 |
32 | Rules files:
33 |
34 | ```
35 | falco_rules.yaml
36 | falco-incubating_rules.yaml
37 | falco-sandbox_rules.yaml
38 | falco-deprecated_rules.yaml
39 | ```
40 |
41 | Falco offers configurability through the [falco.yaml](https://github.com/falcosecurity/falco/blob/master/falco.yaml) file, enabling support for the unique use cases of adopters. This configurability allows adopters to determine which rules should be loaded based on tags and other properties of the rules. With Falco 0.36 and beyond, it's now possible to apply multiple rules that match the same event, eliminating concerns about rule prioritization based on the "first match wins" principle.
42 |
43 | Special note regarding *plugins* rules: The rules for different Falco [plugins](https://github.com/falcosecurity/plugins) are currently not integrated into this rules maturity framework.
44 |
45 |
46 | ## Maturity Levels
47 |
48 | The levels:
49 |
50 | - **maturity_stable** indicates that the rule has undergone thorough evaluation by experts with hands-on production experience. These experts have determined that the rules embody best practices and exhibit optimal robustness, making it more difficult for attackers to bypass Falco. These rules are highly relevant for addressing broader threats and are recommended for customization to specific environments if necessary. They primarily focus on universal system-level detections, such as generic reverse shells or container escapes, which establish a solid baseline for threat detection across diverse industries. This inherent bias against including more application-specific detections is due to their potential lack of broad relevance or applicability. However, to mitigate this bias, the maintainers reserve the right to make judgments on a case-by-case basis.
51 | - **maturity_incubating** indicates that the rules address relevant threats, provide a certain level of robustness guarantee, and adhere to best practices in rule writing. Furthermore, it signifies that the rules have been identified by experts as catering to more specific use cases, which may or may not be relevant for each adopter. This category is expected to include a larger number of application-specific rules.
52 | - **maturity_sandbox** indicates that the rule is in an experimental stage. The potential for broader usefulness and relevance of "sandbox" rules is currently being assessed. These rules can serve as inspiration and adhere to the minimum acceptance criteria for rules.
53 | - **maturity_deprecated** indicates that, upon re-assessment, the rule was deemed less applicable to the broader community. Each adopter needs to determine the relevance of these rules on their own. They are kept as examples but are no longer actively supported or tuned by The Falco Project.
54 |
55 | In summary, the rules maturity tag reflects the robustness, relevance, applicability, and stability of each predefined rule in the [falcosecurity/rules](https://github.com/falcosecurity/rules/blob/main/rules/) repository. It serves as general guidance to determine which rules may provide the highest return on investment.
56 |
57 | ## Justification of Rules Maturity Framework for Falco Adoption
58 |
59 | A rules maturity framework has been introduced for Falco users to facilitate the adoption of non-custom rules more effectively. This framework ensures a smooth transition for adopters, whether they use rules generically or for specific use cases. A smooth adoption process is defined by making it easy for adopters to understand each rule and also gain an understanding of not just what the rule is doing, but also how beneficial it can be under various circumstances.
60 | Additionally, due to this framework, adopters should find themselves with a clearer understanding of which rules can likely be adopted as-is versus which rules may require significant engineering efforts to evaluate and adopt.
61 |
62 | The rules maturity framework aligns with the [status](https://github.com/falcosecurity/evolution/blob/main/REPOSITORIES.md#status) levels used within The Falco Project repositories, namely "Stable", "Incubating", "Sandbox" and "Deprecated".
63 |
64 | Not every rule has the potential to evolve and reach the "stable" level. This is because "stable" rules should address a broader range of attacks rather than being overly specific—such as detecting a single narrow CVE for a less common type of application, which could be easily bypassed. However, this does not mean that very specific rules do not provide value; on the contrary, they can serve a very specific purpose. These more specific rules may be better suited for custom adoption rather than integration into the upstream Falco rules.
65 |
66 | The new framework aims to help adopters easily identify the nature of a rule, whether it's more behavioral or signature-based. This is accomplished by providing clearer descriptions. You can explore this in more detail in the [Rules Overview Document](https://falcosecurity.github.io/rules/).
67 |
68 | The maturity level of the rules, however, does not directly reflect their potential for generating noise in the adopters' environment. This is due to the unique and constantly changing nature of each environment, especially in cloud environments, making it challenging to accurately predict the impact of rules.
69 |
70 | Newcomers to Falco are encouraged to start by configuring their setup with introductory rules labeled as `maturity_stable`. These rules, which are currently based on syscall and container events live in the established [falco_rules.yaml](https://github.com/falcosecurity/rules/blob/main/rules/falco_rules.yaml) file.
71 |
72 | As users become more familiar with Falco and better understand their unique environment, they can gradually fine-tune the rules to meet their specific requirements. Tuning rules goes hand in hand with assessing the performance overhead and adjusting Falco's [configuration](https://github.com/falcosecurity/falco/blob/master/falco.yaml) accordingly. This consideration is important to keep in mind as there are usually limitations to the budget allocated for security monitoring.
73 |
74 | Once adopters have integrated the stable default rules with low False Positives and acceptable performance overhead consistently, they can add a next set of rules. This set may include rules with `maturity_incubating` or `maturity_sandbox`, offering more specific detections and/or broader monitoring, depending on the rule.
75 |
76 | # Rules Acceptance Criteria
77 |
78 | The [maintainers](OWNERS) of this repository kindly reserve the right to make case-by-case decisions regarding rules acceptance and initial maturity leveling.
79 |
80 | The high-level principles that guide the review process for contributors and reviewers are as follows:
81 |
82 | - Each rule aligns with the project's best interests as per our [governance](https://github.com/falcosecurity/evolution/blob/main/GOVERNANCE.md).
83 | - Each rule conforms to the [Style Guide of Falco Rules](https://falco.org/docs/rules/style-guide/).
84 | - In particular, the [Rules Maturity Framework](#rules-maturity-framework) is honored.
85 |
86 | > Note: Any rule that would require using the `-A` flag (enabling high-volume syscalls) cannot be accepted beyond `maturity_sandbox` and `enabled: false` due to performance impact reasons. At the moment, we discourage upstream rules based on high-volume syscalls. However, this assessment may change as Falco evolves.
87 |
88 | *Correctness*
89 |
90 | As part of the review process, the following aspects will be thoroughly checked to effectively enforce the [Style Guide of Falco Rules](https://falco.org/docs/rules/style-guide/).
91 |
92 | - Correctness of the expression language, both syntactically and grammatically.
93 | - Consistency with the name/description.
94 | - If any tests are present, they must pass. During the initial review process and subsequent changes, manual testing should also be conducted to verify that the rule is capable of detecting the cyber threat(s) it aims to detect. In some cases, conducting more realistic tests, like deploying the rules on actual servers before acceptance, will be necessary.
95 |
96 | *Robustness*
97 |
98 | To enhance the effectiveness of detection, priority is given to behavioral detections, as opposed to string matching on process command arguments or other fields. This preference is based on the ease with which the latter can be circumvented. The same principle applies when selecting the most robust system call for detecting a specific threat. However, there is a place and purpose for more signature-based detections. The existing rules tagged with `maturity_stable` serve as a good starting point to explore a variety of useful rules that cover various attack vectors and employ both signature and behavior-based detection styles. Lastly, The Falco Project favors broader rules over narrow ones addressing a single, less common CVE for an application.
99 |
100 | *Relevance*
101 |
102 | Determining relevance is often the most subjective criterion, as it requires expertise in offensive security, cyber defense, and real-world production settings for accurate assessment. Questions such as whether these threats are a priority for most organizations or if we can provide enough context for a security analyst to appropriately act on the alerts as part of their incident response workflows are top of mind when assessing the overall relevance. Relevance is a key factor that indirectly reflects both robustness and significance, but more importantly, it indicates whether a particular security threat is significant to most adopters and, consequently, beneficial to broadly detect.
103 |
104 | Here are some aspects that can be discussed during the review process in order to decide if a rule has the potential to be effectively operationalized by most adopters:
105 |
106 | - Cover relevant attack vectors across various industries.
107 | - Emphasize behavior-detection style + profiling over pure signatures (exceptions to this guideline apply).
108 | - Evaluate the rule's effectiveness across diverse workloads (e.g. nodes serving web applications, databases, transactional processing, general compute or CI jobs).
109 | - Guidance and templates to assist with tuning can be provided given Falco's current capabilities.
110 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2022 The Falco Authors
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
--------------------------------------------------------------------------------
/OWNERS:
--------------------------------------------------------------------------------
1 | approvers:
2 | - mstemm
3 | - leogr
4 | - jasondellaluce
5 | - fededp
6 | - andreagit97
7 | - lucaguerra
8 | - ekoops
9 | reviewers:
10 | - leodido
11 | - kaizhe
12 | - darryk10
13 | - loresuso
14 | emeritus_approvers:
15 | - kaizhe
16 | - incertum
17 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release Process
2 |
3 | Official Falcosecurity rules releases are automated using GitHub Actions. Each ruleset is released individually and each version is tied to a specific git tag.
4 |
5 | ## Releasing a ruleset
6 |
7 | In this repo, each ruleset is a standalone YAML file in the `/rules` directory (e.g. `falco_rules.yaml`, `application_rules.yaml`, ...). Each ruleset is released and versioned individually. When we release a ruleset, we do the following process:
8 |
9 | 1. Make sure that the `./github/FALCO_VERSIONS` file contains the most recent versions of Falco with which the given ruleset validates successfully.
10 | - When releasing a ruleset, the versions must be explicit stable Falco releases (e.g. not using `latest` or `master`), so that the new tag will provide an history of the Falco versions on which the ruleset was tested
11 | - If the ruleset does not validate with a stable Falco release (it can happen close to the time of a Falco release), it's ok to use a `master` version, which however needs to be patched with an additional commit [on the ruleset's release branch](#patching-a-ruleset) once the next stable Falco release gets published.
12 | 2. Determine a new version for the given ruleset (see the [section below](#versioning-a-ruleset)).
13 | 3. Create a new Git tag with the name convention `*name*-rules-*version*` (e.g. `falco-rules-0.1.0`, `application-rules-0.1.0`, ...). The naming convention is required due to this repository being a [monorepo](https://en.wikipedia.org/wiki/Monorepo) and in order to be machine-readable.
14 | 4. A GitHub action will validate the repository [registry](./registry.yaml) and release the new OCI artifact in the packages of this repository.
15 |
16 | ## Patching a ruleset
17 |
18 | Patches on an already-released ruleset can anytime on a per-need basis. Assuming a release Git tag in the form of `*name*-rules-*version*`, with version being in the form of `X.Y.Z` (e.g. `falco-rules-0.1.0`), the process is as follows:
19 |
20 | 1. If not already created, create a release branch starting from the first tag of the released ruleset.
21 | a. Checkout the first ruleset release tag: e.g. `git checkout falco-rules-0.1.0`
22 | b. Create a new branch with the naming convention `release/*name*-rules-X.Y.x`, starting from the tag: e.g. `git checkout -b release/falco-rules-0.1.x`
23 | c. As for step #1 of the [*Releasing a ruleset section*](#releasing-a-ruleset), make sure that `./github/FALCO_VERSIONS` contains all the **stable** Falco versions with which the ruleset validates successfully, by adding an extra commit to the release branch in case changes are required.
24 | d. Open a [PR in falcosecurity/test-infra](https://github.com/falcosecurity/test-infra/edit/master/config/config.yaml) to add `release/*name*-rules-X.Y.x` as protected branch to the `prow/config.yaml`, for example:
25 | ```yaml
26 | rules:
27 | branches:
28 | main:
29 | protect: true
30 | ...
31 | "release/falco-rules-0.1.x":
32 | protect: true
33 | ```
34 | 2. Open one or more PRs agains the release branch of the released ruleset.
35 | a. Wait for all the CI checks to pass.
36 | b. Check the automatic versioning suggestions provided by the CI (see [an example](https://github.com/falcosecurity/rules/pull/74#issuecomment-1571867580)), and make sure that **only patch changes** are present.
37 | c. Considering the automatic versioning suggestions, and at discretion of the domain knowledge of the repository maintainers, decide whether the PR should be merged or not.
38 | 3. Bump the ruleset version **patch** number by 1 and create a new Git tag from the release branch (e.g. `falco-rules-0.1.1`).
39 | 4. A GitHub action will validate the repository [registry](./registry.yaml) and release the new OCI artifact in the packages of this repository.
40 |
41 | ## Versioning a ruleset
42 |
43 | The version of the official Falco rulesets is compatible with [SemVer](https://semver.org/) and must be meaningful towards the changes in its structure and contents. To define a new version `x.y.z` for a given ruleset, consider the following guidelines.
44 |
45 | Our automation will detect most of the following criteria and suggest a summary with all the changes relative to each of the three versioning categories (patch, minor, major). This provides a changelog and valuable suggestion on the next version to be assigned to each ruleset. However, be mindful that the versioning process cannot totally automated and always requires human attention (e.g. we can't automatically detect subtle semantic changes in rules). The automated jobs will use the versions of Falco defined in `./github/FALCO_VERSIONS` for validating and checking rulesets. The versions must be line-separated and ordered from the most recent to the least recent. Any [published container image tag](https://hub.docker.com/r/falcosecurity/falco/tags) is a valid Falco version entry, including `master`, `latest`, and any other stable release tag (e.g. `0.35.0`). `master` indicates the most recent dev version of Falco built from mainline, and can be used for using a not-yet-published version of Falco in case we want to run the CI with a new in-development feature.
46 |
47 | **NOTE:** *The versioning guidelines also apply to any versioned ruleset not maintained in this repository (such as the ones in [falcosecurity/plugins](https://github.com/falcosecurity/plugins)), including the ones distributed by third parties. These are best practices that guarantee the correct behavior of Falco when updating a given ruleset to a new version.*
48 |
49 | - `z` _(patch number)_ is incremented when you make backward-compatible changes. In this case, the ruleset can be updated in a given Falco without needing to update Falco, its plugins, or its configuration. Examples:
50 | - Decrementing `required_engine_version`
51 | - Decrementing plugin version requirement in `required_plugin_versions`
52 | - Adding alternatives entries to an already-existing plugin version requirement in `required_plugin_versions`
53 | - Removing a plugin version requirement with all its alternatives in `required_plugin_versions`
54 | - Enabling at default one or more rules that used to be disabled
55 | - Adding or removing items for one or more lists
56 | - Adding one or more tags to a rule
57 | - Increasing the priority of a rule
58 | - Changing the output fields of a rule (without increasing `required_engine_version`)
59 | - Adding or removing exceptions for one or more Falco rules (without increasing `required_engine_version`)
60 | - Changing the condition for one or more rules by still preserving their logical security scope (e.g. making them less noisy, matching more events than before)
61 | - `y` _(minor number)_ is incremented when you add functionality in a backward-compatible manner. In order to be accepted, the new ruleset may mandate updating the version of Falco, changing its configuration, or updating/installing one or more plugins. Examples:
62 | - Incrementing `required_engine_version`
63 | - Incrementing the `required_plugin_versions` version requirement for one or more plugin
64 | - Adding a new plugin version requirement (with or without `alternatives`) in `required_plugin_versions`
65 | - Adding one or more lists, macros, or rules
66 | - Adapting the ruleset to a Falco engine version introducing *backward-compatible* changes in the expected ruleset language definitions or file format. For now, this can't happen without also bumping `required_engine_version` since it is a simple progressive number. However, this may change in the future if we consider adopting a sem-ver-like version scheme.
67 | - `x` _(major number)_ is incremented when you make incompatible content changes which change the expected behavior and outcome of the ruleset. Incompatibilities may arise when relying on the ruleset from other rulesets (e.g. appending conditions or overriding the definition of a list, macro, or rule). Examples:
68 | - Removing a plugin version requirement alternative (without removing the whole dependency) in `required_plugin_versions`
69 | - Renaming or removing a list, macro, or rule
70 | - Changing the event source of a rule
71 | - Disabling at default one or more rules that used to be enabled
72 | - Removing one or more tags from a rule
73 | - Decreasing the priority of a rule
74 | - Changing in any way the set of matched events matched by a macro
75 | - Changing the logical security scope of one or more macros or rules (e.g. a rule starts serving a substantially different purpose, or matches less events than before)
76 | - Adapting the ruleset to a Falco engine version introducing *backward-incompatible* changes in the expected ruleset language definitions or file format
77 |
78 | When more than one version numbers need to be incremented, the most dominant takes precedence. For example, incrementing the `z` patch number would be enough when minorly chaning a rule's condition to make it less noisy, however you must increment the `y` minor number in case the new condition uses a new field or operator that requires increasing the `required_engine_version`.
79 |
--------------------------------------------------------------------------------
/archive/README.md:
--------------------------------------------------------------------------------
1 | # Archived Rules Files
2 |
3 | This directory hosts archived rules files. While they are preserved for historical purposes or as examples, please note the following:
4 |
5 | - These files are **no longer maintained**.
6 | - There's a possibility that they may **not function correctly**.
7 | - They are **not recommended for use in production environments**.
8 |
--------------------------------------------------------------------------------
/archive/application_rules.yaml:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: Apache-2.0
2 | #
3 | # Copyright (C) 2023 The Falco Authors.
4 | #
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | - required_engine_version: 2
20 |
21 | ################################################################
22 | # By default all application-related rules are disabled for
23 | # performance reasons. Depending on the application(s) you use,
24 | # uncomment the corresponding rule definitions for
25 | # application-specific activity monitoring.
26 | ################################################################
27 |
28 | # Elasticsearch ports
29 | - macro: elasticsearch_cluster_port
30 | condition: fd.sport=9300
31 | - macro: elasticsearch_api_port
32 | condition: fd.sport=9200
33 | - macro: elasticsearch_port
34 | condition: elasticsearch_cluster_port or elasticsearch_api_port
35 |
36 | # - rule: Elasticsearch unexpected network inbound traffic
37 | # desc: inbound network traffic to elasticsearch on a port other than the standard ports
38 | # condition: user.name = elasticsearch and inbound and not elasticsearch_port
39 | # output: "Inbound network traffic to Elasticsearch on unexpected port (connection=%fd.name)"
40 | # priority: WARNING
41 |
42 | # - rule: Elasticsearch unexpected network outbound traffic
43 | # desc: outbound network traffic from elasticsearch on a port other than the standard ports
44 | # condition: user.name = elasticsearch and outbound and not elasticsearch_cluster_port
45 | # output: "Outbound network traffic from Elasticsearch on unexpected port (connection=%fd.name)"
46 | # priority: WARNING
47 |
48 |
49 | # ActiveMQ ports
50 | - macro: activemq_cluster_port
51 | condition: fd.sport=61616
52 | - macro: activemq_web_port
53 | condition: fd.sport=8161
54 | - macro: activemq_port
55 | condition: activemq_web_port or activemq_cluster_port
56 |
57 | # - rule: Activemq unexpected network inbound traffic
58 | # desc: inbound network traffic to activemq on a port other than the standard ports
59 | # condition: user.name = activemq and inbound and not activemq_port
60 | # output: "Inbound network traffic to ActiveMQ on unexpected port (connection=%fd.name)"
61 | # priority: WARNING
62 |
63 | # - rule: Activemq unexpected network outbound traffic
64 | # desc: outbound network traffic from activemq on a port other than the standard ports
65 | # condition: user.name = activemq and outbound and not activemq_cluster_port
66 | # output: "Outbound network traffic from ActiveMQ on unexpected port (connection=%fd.name)"
67 | # priority: WARNING
68 |
69 |
70 | # Cassandra ports
71 | # https://docs.datastax.com/en/cassandra/2.0/cassandra/security/secureFireWall_r.html
72 | - macro: cassandra_thrift_client_port
73 | condition: fd.sport=9160
74 | - macro: cassandra_cql_port
75 | condition: fd.sport=9042
76 | - macro: cassandra_cluster_port
77 | condition: fd.sport=7000
78 | - macro: cassandra_ssl_cluster_port
79 | condition: fd.sport=7001
80 | - macro: cassandra_jmx_port
81 | condition: fd.sport=7199
82 | - macro: cassandra_port
83 | condition: >
84 | cassandra_thrift_client_port or
85 | cassandra_cql_port or cassandra_cluster_port or
86 | cassandra_ssl_cluster_port or cassandra_jmx_port
87 |
88 | # - rule: Cassandra unexpected network inbound traffic
89 | # desc: inbound network traffic to cassandra on a port other than the standard ports
90 | # condition: user.name = cassandra and inbound and not cassandra_port
91 | # output: "Inbound network traffic to Cassandra on unexpected port (connection=%fd.name)"
92 | # priority: WARNING
93 |
94 | # - rule: Cassandra unexpected network outbound traffic
95 | # desc: outbound network traffic from cassandra on a port other than the standard ports
96 | # condition: user.name = cassandra and outbound and not (cassandra_ssl_cluster_port or cassandra_cluster_port)
97 | # output: "Outbound network traffic from Cassandra on unexpected port (connection=%fd.name)"
98 | # priority: WARNING
99 |
100 | # Couchdb ports
101 | # https://github.com/davisp/couchdb/blob/master/etc/couchdb/local.ini
102 | - macro: couchdb_httpd_port
103 | condition: fd.sport=5984
104 | - macro: couchdb_httpd_ssl_port
105 | condition: fd.sport=6984
106 | # xxx can't tell what clustering ports are used. not writing rules for this
107 | # yet.
108 |
109 | # Fluentd ports
110 | - macro: fluentd_http_port
111 | condition: fd.sport=9880
112 | - macro: fluentd_forward_port
113 | condition: fd.sport=24224
114 |
115 | # - rule: Fluentd unexpected network inbound traffic
116 | # desc: inbound network traffic to fluentd on a port other than the standard ports
117 | # condition: user.name = td-agent and inbound and not (fluentd_forward_port or fluentd_http_port)
118 | # output: "Inbound network traffic to Fluentd on unexpected port (connection=%fd.name)"
119 | # priority: WARNING
120 |
121 | # - rule: Tdagent unexpected network outbound traffic
122 | # desc: outbound network traffic from fluentd on a port other than the standard ports
123 | # condition: user.name = td-agent and outbound and not fluentd_forward_port
124 | # output: "Outbound network traffic from Fluentd on unexpected port (connection=%fd.name)"
125 | # priority: WARNING
126 |
127 | # Gearman ports
128 | # http://gearman.org/protocol/
129 | # - rule: Gearman unexpected network outbound traffic
130 | # desc: outbound network traffic from gearman on a port other than the standard ports
131 | # condition: user.name = gearman and outbound and outbound and not fd.sport = 4730
132 | # output: "Outbound network traffic from Gearman on unexpected port (connection=%fd.name)"
133 | # priority: WARNING
134 |
135 | # Zookeeper
136 | - macro: zookeeper_port
137 | condition: fd.sport = 2181
138 |
139 | # Kafka ports
140 | # - rule: Kafka unexpected network inbound traffic
141 | # desc: inbound network traffic to kafka on a port other than the standard ports
142 | # condition: user.name = kafka and inbound and fd.sport != 9092
143 | # output: "Inbound network traffic to Kafka on unexpected port (connection=%fd.name)"
144 | # priority: WARNING
145 |
146 | # Memcached ports
147 | # - rule: Memcached unexpected network inbound traffic
148 | # desc: inbound network traffic to memcached on a port other than the standard ports
149 | # condition: user.name = memcached and inbound and fd.sport != 11211
150 | # output: "Inbound network traffic to Memcached on unexpected port (connection=%fd.name)"
151 | # priority: WARNING
152 |
153 | # - rule: Memcached unexpected network outbound traffic
154 | # desc: any outbound network traffic from memcached. memcached never initiates outbound connections.
155 | # condition: user.name = memcached and outbound
156 | # output: "Unexpected Memcached outbound connection (connection=%fd.name)"
157 | # priority: WARNING
158 |
159 |
160 | # MongoDB ports
161 | - macro: mongodb_server_port
162 | condition: fd.sport = 27017
163 | - macro: mongodb_shardserver_port
164 | condition: fd.sport = 27018
165 | - macro: mongodb_configserver_port
166 | condition: fd.sport = 27019
167 | - macro: mongodb_webserver_port
168 | condition: fd.sport = 28017
169 |
170 | # - rule: Mongodb unexpected network inbound traffic
171 | # desc: inbound network traffic to mongodb on a port other than the standard ports
172 | # condition: >
173 | # user.name = mongodb and inbound and not (mongodb_server_port or
174 | # mongodb_shardserver_port or mongodb_configserver_port or mongodb_webserver_port)
175 | # output: "Inbound network traffic to MongoDB on unexpected port (connection=%fd.name)"
176 | # priority: WARNING
177 |
178 | # MySQL ports
179 | # - rule: Mysql unexpected network inbound traffic
180 | # desc: inbound network traffic to mysql on a port other than the standard ports
181 | # condition: user.name = mysql and inbound and fd.sport != 3306
182 | # output: "Inbound network traffic to MySQL on unexpected port (connection=%fd.name)"
183 | # priority: WARNING
184 |
185 | # - rule: HTTP server unexpected network inbound traffic
186 | # desc: inbound network traffic to a http server program on a port other than the standard ports
187 | # condition: proc.name in (http_server_binaries) and inbound and fd.sport != 80 and fd.sport != 443
188 | # output: "Inbound network traffic to HTTP Server on unexpected port (connection=%fd.name)"
189 | # priority: WARNING
190 |
--------------------------------------------------------------------------------
/archive/falco-deprecated_rules.yaml:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: Apache-2.0
2 | #
3 | # Copyright (C) 2025 The Falco Authors.
4 | #
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | # Information about rules tags and fields can be found here: https://falco.org/docs/rules/#tags-for-current-falco-ruleset
20 | # The initial item in the `tags` fields reflects the maturity level of the rules introduced upon the proposal https://github.com/falcosecurity/rules/blob/main/proposals/20230605-rules-adoption-management-maturity-framework.md
21 | # `tags` fields also include information about the type of workload inspection (host and/or container), and Mitre Attack killchain phases and Mitre TTP code(s)
22 | # Mitre Attack References:
23 | # [1] https://attack.mitre.org/tactics/enterprise/
24 | # [2] https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json
25 |
26 | # Starting with version 8, the Falco engine supports exceptions.
27 | # However the Falco rules file does not use them by default.
28 | - required_engine_version: 0.50.0
29 |
30 | - required_plugin_versions:
31 | - name: container
32 | version: 0.2.2
33 |
34 | # This macro `never_true` is used as placeholder for tuning negative logical sub-expressions, for example
35 | # - macro: allowed_ssh_hosts
36 | # condition: (never_true)
37 | # can be used in a rules' expression with double negation `and not allowed_ssh_hosts` which effectively evaluates
38 | # to true and does nothing, the perfect empty template for `logical` cases as opposed to list templates.
39 | # When tuning the rule you can override the macro with something useful, e.g.
40 | # - macro: allowed_ssh_hosts
41 | # condition: (evt.hostname contains xyz)
42 | - macro: never_true
43 | condition: (evt.num=0)
44 |
45 | # RFC1918 addresses were assigned for private network usage
46 | - list: rfc_1918_addresses
47 | items: ['"10.0.0.0/8"', '"172.16.0.0/12"', '"192.168.0.0/16"']
48 |
49 | - macro: outbound
50 | condition: >
51 | (((evt.type = connect and evt.dir=<) or
52 | (evt.type in (sendto,sendmsg) and evt.dir=< and
53 | fd.l4proto != tcp and fd.connected=false and fd.name_changed=true)) and
54 | (fd.typechar = 4 or fd.typechar = 6) and
55 | (fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8" and not fd.snet in (rfc_1918_addresses)) and
56 | (evt.rawres >= 0 or evt.res = EINPROGRESS))
57 |
58 | # Very similar to inbound/outbound, but combines the tests together
59 | # for efficiency.
60 | - macro: inbound_outbound
61 | condition: >
62 | ((((evt.type in (accept,accept4,listen,connect) and evt.dir=<)) and
63 | (fd.typechar = 4 or fd.typechar = 6)) and
64 | (fd.ip != "0.0.0.0" and fd.net != "127.0.0.0/8") and
65 | (evt.rawres >= 0 or evt.res = EINPROGRESS))
66 |
67 | - macro: ssh_port
68 | condition: (fd.sport=22)
69 |
70 | # In a local/user rules file, you could override this macro to
71 | # enumerate the servers for which ssh connections are allowed. For
72 | # example, you might have a ssh gateway host for which ssh connections
73 | # are allowed.
74 | #
75 | # In the main falco rules file, there isn't any way to know the
76 | # specific hosts for which ssh access is allowed, so this macro just
77 | # repeats ssh_port, which effectively allows ssh from all hosts. In
78 | # the overridden macro, the condition would look something like
79 | # "fd.sip="a.b.c.d" or fd.sip="e.f.g.h" or ..."
80 | - macro: allowed_ssh_hosts
81 | condition: (never_true)
82 |
83 | - rule: Disallowed SSH Connection
84 | desc: >
85 | Detect any new SSH connection on port 22 to a host other than those in an allowed list of hosts.
86 | This rule absolutely requires profiling your environment beforehand. Network-based rules are extremely
87 | crucial in any security program, as they can often provide the only definitive evidence. However,
88 | effectively operationalizing them can be challenging due to the potential for noise.
89 | condition: >
90 | inbound_outbound
91 | and ssh_port
92 | and not allowed_ssh_hosts
93 | enabled: false
94 | output: Disallowed SSH Connection | connection=%fd.name lport=%fd.lport rport=%fd.rport fd_type=%fd.type fd_proto=fd.l4proto evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty
95 | priority: NOTICE
96 | tags: [maturity_deprecated, host, container, network, mitre_lateral_movement, T1021.004]
97 |
98 | # These rules and supporting macros are more of an example for how to
99 | # use the fd.*ip and fd.*ip.name fields to match connection
100 | # information against ips, netmasks, and complete domain names.
101 | #
102 | # To use this rule, you should enable it and
103 | # populate allowed_{source,destination}_{ipaddrs,networks,domains} with the
104 | # values that make sense for your environment.
105 |
106 | # Note that this can be either individual IPs or netmasks
107 | - list: allowed_outbound_destination_ipaddrs
108 | items: ['"127.0.0.1"', '"8.8.8.8"']
109 |
110 | - list: allowed_outbound_destination_networks
111 | items: ['"127.0.0.1/8"']
112 |
113 | - list: allowed_outbound_destination_domains
114 | items: [google.com, www.yahoo.com]
115 |
116 | - rule: Unexpected outbound connection destination
117 | desc: >
118 | Detect any outbound connection to a destination outside of an allowed set of ips, networks, or domain names.
119 | This rule absolutely requires profiling your environment beforehand. Network-based rules are extremely crucial
120 | in any security program, as they can often provide the only definitive evidence. However, effectively operationalizing
121 | them can be challenging due to the potential for noise.
122 | condition: >
123 | outbound
124 | and not ((fd.sip in (allowed_outbound_destination_ipaddrs)) or
125 | (fd.snet in (allowed_outbound_destination_networks)) or
126 | (fd.sip.name in (allowed_outbound_destination_domains)))
127 | enabled: false
128 | output: Disallowed outbound connection destination | connection=%fd.name lport=%fd.lport rport=%fd.rport fd_type=%fd.type fd_proto=fd.l4proto evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty
129 | priority: NOTICE
130 | tags: [maturity_deprecated, host, container, network, mitre_command_and_control, TA0011]
131 |
132 | # Use this to test whether the event occurred within a container.
133 | - macro: container
134 | condition: (container.id != host)
135 |
136 | - list: allowed_image
137 | items: [] # add image to monitor, i.e.: bitnami/nginx
138 |
139 | - list: authorized_server_binary
140 | items: [] # add binary to allow, i.e.: nginx
141 |
142 | - list: authorized_server_port
143 | items: [] # add port to allow, i.e.: 80
144 |
145 | # # How to test:
146 | # kubectl run --image=nginx nginx-app --port=80 --env="DOMAIN=cluster"
147 | # kubectl expose deployment nginx-app --port=80 --name=nginx-http --type=LoadBalancer
148 | # # On minikube:
149 | # minikube service nginx-http
150 | # # On general K8s:
151 | # kubectl get services
152 | # kubectl cluster-info
153 | # # Visit the Nginx service and port, should not fire.
154 | # # Change rule to different port, then different process name, and test again that it fires.
155 |
156 | - rule: Outbound or Inbound Traffic not to Authorized Server Process and Port
157 | desc: >
158 | Detect traffic to an unauthorized server process and port within pre-defined containers.
159 | This rule absolutely requires profiling your environment beforehand and also necessitates adjusting the list of containers
160 | to which this rule will be applied. The current expression logic will never evaluate to true unless the list is populated.
161 | Network-based rules are extremely crucial in any security program, as they can often provide the only definitive evidence.
162 | However, effectively operationalizing them can be challenging due to the potential for noise. Notably, this rule is challenging
163 | to operationalize.
164 | condition: >
165 | inbound_outbound
166 | and container
167 | and container.image.repository in (allowed_image)
168 | and not proc.name in (authorized_server_binary)
169 | and not fd.sport in (authorized_server_port)
170 | enabled: false
171 | output: Network connection outside authorized port and binary | connection=%fd.name lport=%fd.lport rport=%fd.rport fd_type=%fd.type fd_proto=fd.l4proto evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty
172 | priority: WARNING
173 | tags: [maturity_deprecated, container, network, mitre_discovery, TA0011, NIST_800-53_CM-7]
174 |
175 | - list: c2_server_ip_list
176 | items: []
177 |
178 | - list: c2_server_fqdn_list
179 | items: []
180 |
181 | - rule: Outbound Connection to C2 Servers
182 | desc: >
183 | Detect outbound connections to command and control servers using a list of IP addresses and fully qualified domain names (FQDNs).
184 | This rule absolutely requires profiling your environment beforehand and also necessitates adjusting the template lists. The current
185 | expression logic will never evaluate to true unless the lists are populated. Network-based rules are extremely crucial in any
186 | security program, as they can often provide the only definitive evidence. However, effectively operationalizing them can be challenging
187 | due to the potential for noise. Notably, this rule is challenging to operationalize.
188 | condition: >
189 | outbound
190 | and ((fd.sip in (c2_server_ip_list)) or
191 | (fd.sip.name in (c2_server_fqdn_list)))
192 | output: Outbound connection to C2 server | c2_domain=%fd.sip.name c2_addr=%fd.sip connection=%fd.name lport=%fd.lport rport=%fd.rport fd_type=%fd.type fd_proto=fd.l4proto evt_type=%evt.type user=%user.name user_uid=%user.uid user_loginuid=%user.loginuid process=%proc.name proc_exepath=%proc.exepath parent=%proc.pname command=%proc.cmdline terminal=%proc.tty
193 | priority: WARNING
194 | enabled: false
195 | tags: [maturity_deprecated, host, container, network, mitre_command_and_control, TA0011]
196 |
--------------------------------------------------------------------------------
/build/checker/.gitignore:
--------------------------------------------------------------------------------
1 | checker
--------------------------------------------------------------------------------
/build/checker/cmd/common.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package cmd
19 |
20 | import (
21 | "fmt"
22 | "math"
23 | "strings"
24 | )
25 |
26 | const defaultFalcoDockerImage = "falcosecurity/falco:master"
27 |
28 | const defaultFalcoDockerEntrypoint = "/usr/bin/falco"
29 |
30 | var falcoPriorities = []string{
31 | "emergency",
32 | "alert",
33 | "critical",
34 | "error",
35 | "warning",
36 | "notice",
37 | "informational", // or "info"
38 | "debug",
39 | }
40 |
41 | // compareInt returns 1 if "left" is greater than right,
42 | // -1 if "right" is greater than left, and 0 otherwise.
43 | func compareInt(a, b int) int {
44 | if a == b {
45 | return 0
46 | }
47 | if a < b {
48 | return -1
49 | }
50 | return 1
51 | }
52 |
53 | // compareFalcoPriorities returns 1 if "left" is more urgent than right,
54 | // -1 if "right" is more urgent than left, and 0 otherwise.
55 | func compareFalcoPriorities(left, right string) int {
56 | lIndex := math.MaxInt
57 | rIndex := math.MaxInt
58 | for i, p := range falcoPriorities {
59 | if strings.HasPrefix(p, strings.ToLower(left)) {
60 | lIndex = i
61 | }
62 | if strings.HasPrefix(p, strings.ToLower(right)) {
63 | rIndex = i
64 | }
65 | }
66 | return compareInt(rIndex, lIndex)
67 | }
68 |
69 | // errAppend returns an error resulting froma appending two errors.
70 | func errAppend(left, right error) error {
71 | if left == nil {
72 | return right
73 | }
74 | if right == nil {
75 | return left
76 | }
77 | return fmt.Errorf("%s, %s", left.Error(), right.Error())
78 | }
79 |
80 | // strSliceToMap returns a map[string]bool (a set, basically) from a strings slice.
81 | func strSliceToMap(s []string) map[string]bool {
82 | items := make(map[string]bool)
83 | for _, item := range s {
84 | items[item] = true
85 | }
86 | return items
87 | }
88 |
89 | // diffStrSet returns a map[string]bool containing all the strings present
90 | // in left but without the strings present in right.
91 | func diffStrSet(left, right []string) map[string]bool {
92 | l := strSliceToMap(left)
93 | r := strSliceToMap(right)
94 | for k := range r {
95 | delete(l, k)
96 | }
97 | return l
98 | }
99 |
--------------------------------------------------------------------------------
/build/checker/cmd/common_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package cmd
19 |
20 | import (
21 | "testing"
22 |
23 | "github.com/stretchr/testify/assert"
24 | )
25 |
26 | func TestCompareInt(t *testing.T) {
27 | t.Parallel()
28 | assert.Equal(t, -1, compareInt(1, 2))
29 | assert.Equal(t, -1, compareInt(1, 5))
30 | assert.Equal(t, 0, compareInt(5, 5))
31 | assert.Equal(t, 1, compareInt(6, 5))
32 | assert.Equal(t, 1, compareInt(10, 5))
33 | }
34 |
35 | func TestCompareFalcoPriorities(t *testing.T) {
36 | t.Parallel()
37 | assert.Equal(t, -1, compareFalcoPriorities("debug", "info"))
38 | assert.Equal(t, 1, compareFalcoPriorities("info", "debug"))
39 | assert.Equal(t, 0, compareFalcoPriorities("info", "informational"))
40 | }
41 |
42 | func TestDiffStrSet(t *testing.T) {
43 | t.Parallel()
44 | a := []string{"a", "b", "c"}
45 | b := []string{"b", "d"}
46 |
47 | d1 := diffStrSet(a, b)
48 | assert.Len(t, d1, 2)
49 | assert.Contains(t, d1, "a")
50 | assert.Contains(t, d1, "c")
51 |
52 | d2 := diffStrSet(b, a)
53 | assert.Len(t, d2, 1)
54 | assert.Contains(t, d2, "d")
55 | }
56 |
--------------------------------------------------------------------------------
/build/checker/cmd/root.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package cmd
19 |
20 | import (
21 | "github.com/sirupsen/logrus"
22 | "github.com/spf13/cobra"
23 | )
24 |
25 | var rootCmd = &cobra.Command{
26 | Use: "checker",
27 | Short: "General-purpose tool for sanity checks around Falco rules files",
28 | Long: "",
29 | }
30 |
31 | // Execute adds all child commands to the root command.
32 | func Execute() {
33 | logrus.SetLevel(logrus.DebugLevel)
34 | cobra.CheckErr(rootCmd.Execute())
35 | }
36 |
--------------------------------------------------------------------------------
/build/checker/cmd/validate.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package cmd
19 |
20 | import (
21 | "fmt"
22 |
23 | "github.com/falcosecurity/testing/pkg/falco"
24 | "github.com/falcosecurity/testing/pkg/run"
25 | "github.com/spf13/cobra"
26 | )
27 |
28 | var validateCmd = &cobra.Command{
29 | Use: "validate",
30 | Short: "Validate one or more rules file with a given Falco version",
31 | RunE: func(cmd *cobra.Command, args []string) error {
32 | falcoImage, err := cmd.Flags().GetString("falco-image")
33 | if err != nil {
34 | return err
35 | }
36 |
37 | rulesFilesPaths, err := cmd.Flags().GetStringArray("rule")
38 | if err != nil {
39 | return err
40 | }
41 |
42 | if len(rulesFilesPaths) == 0 {
43 | return fmt.Errorf("you must specify at least one rules file")
44 | }
45 |
46 | var ruleFiles []run.FileAccessor
47 | for _, rf := range rulesFilesPaths {
48 | f := run.NewLocalFileAccessor(rf, rf)
49 | ruleFiles = append(ruleFiles, f)
50 | }
51 |
52 | falcoTestOptions := []falco.TestOption{
53 | falco.WithOutputJSON(),
54 | falco.WithRulesValidation(ruleFiles...),
55 | }
56 |
57 | falcoConfigPath, err := cmd.Flags().GetString("config")
58 | if err != nil {
59 | return err
60 | }
61 | if len(falcoConfigPath) > 0 {
62 | config := run.NewLocalFileAccessor(falcoConfigPath, falcoConfigPath)
63 | falcoTestOptions = append(falcoTestOptions, falco.WithConfig(config))
64 | }
65 |
66 | falcoFilesPaths, err := cmd.Flags().GetStringArray("file")
67 | if err != nil {
68 | return err
69 | }
70 | if len(falcoFilesPaths) > 0 {
71 | for _, path := range falcoFilesPaths {
72 | file := run.NewLocalFileAccessor(path, path)
73 | falcoTestOptions = append(falcoTestOptions, falco.WithExtraFiles(file))
74 | }
75 | }
76 |
77 | // run falco and collect/print validation issues
78 | runner, err := run.NewDockerRunner(falcoImage, defaultFalcoDockerEntrypoint, nil)
79 | if err != nil {
80 | return err
81 | }
82 |
83 | res := falco.Test(runner, falcoTestOptions...)
84 | if res.RuleValidation() == nil {
85 | err = errAppend(err, fmt.Errorf("rules validation command failed"))
86 | } else {
87 | for _, r := range res.RuleValidation().Results {
88 | if !r.Successful || len(r.Errors) > 0 || len(r.Warnings) > 0 {
89 | err = errAppend(err, fmt.Errorf("rules validation had warning or errors"))
90 | fmt.Fprintln(cmd.OutOrStdout(), res.Stdout())
91 | break
92 | }
93 | }
94 | }
95 |
96 | // collect errors
97 | err = errAppend(err, res.Err())
98 | if res.ExitCode() != 0 {
99 | err = errAppend(err, fmt.Errorf("unexpected exit code (%d)", res.ExitCode()))
100 | }
101 | if err != nil {
102 | fmt.Fprintln(cmd.ErrOrStderr(), res.Stderr())
103 | }
104 | return err
105 | },
106 | }
107 |
108 | func init() {
109 | validateCmd.Flags().StringP("falco-image", "i", defaultFalcoDockerImage, "Docker image of Falco to be used for validation")
110 | validateCmd.Flags().StringP("config", "c", "", "Config file to be used for running Falco")
111 | validateCmd.Flags().StringArrayP("file", "f", []string{}, "Extra files required by Falco for running")
112 | validateCmd.Flags().StringArrayP("rule", "r", []string{}, "Rules files to be validated by Falco")
113 | rootCmd.AddCommand(validateCmd)
114 | }
115 |
--------------------------------------------------------------------------------
/build/checker/go.mod:
--------------------------------------------------------------------------------
1 | module checker
2 |
3 | go 1.19
4 |
5 | require (
6 | github.com/blang/semver v3.5.1+incompatible
7 | github.com/falcosecurity/testing v0.0.0-20230929141322-214d1fbb9327
8 | github.com/sirupsen/logrus v1.9.0
9 | github.com/spf13/cobra v1.7.0
10 | github.com/stretchr/testify v1.8.3
11 | )
12 |
13 | require (
14 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
15 | github.com/Microsoft/go-winio v0.6.0 // indirect
16 | github.com/davecgh/go-spew v1.1.1 // indirect
17 | github.com/docker/distribution v2.8.1+incompatible // indirect
18 | github.com/docker/docker v24.0.3+incompatible // indirect
19 | github.com/docker/go-connections v0.4.0 // indirect
20 | github.com/docker/go-units v0.5.0 // indirect
21 | github.com/gogo/protobuf v1.3.2 // indirect
22 | github.com/google/go-cmp v0.5.9 // indirect
23 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
24 | github.com/opencontainers/go-digest v1.0.0 // indirect
25 | github.com/opencontainers/image-spec v1.0.2 // indirect
26 | github.com/pkg/errors v0.9.1 // indirect
27 | github.com/pmezard/go-difflib v1.0.0 // indirect
28 | github.com/spf13/pflag v1.0.5 // indirect
29 | go.uber.org/multierr v1.11.0 // indirect
30 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
31 | golang.org/x/net v0.7.0 // indirect
32 | golang.org/x/sys v0.5.0 // indirect
33 | golang.org/x/tools v0.1.12 // indirect
34 | gopkg.in/yaml.v3 v3.0.1 // indirect
35 | )
36 |
--------------------------------------------------------------------------------
/build/checker/go.sum:
--------------------------------------------------------------------------------
1 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
2 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
3 | github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
4 | github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
5 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
6 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
7 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
11 | github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
12 | github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
13 | github.com/docker/docker v24.0.3+incompatible h1:Kz/tcUmXhIojEivEoPcRWzL01tVRek7Th15/8BsRPWw=
14 | github.com/docker/docker v24.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
15 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
16 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
17 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
18 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
19 | github.com/falcosecurity/testing v0.0.0-20230929122211-ffc9e0c14c6b h1:4sFusgDWtUSTVa9ZY/BjZThNUdUp6m3QEPpzNfZGWVM=
20 | github.com/falcosecurity/testing v0.0.0-20230929122211-ffc9e0c14c6b/go.mod h1:yJVIonjfOTCI7yFQsJRU1PRFL57Ddad0HH2bo3dsiKA=
21 | github.com/falcosecurity/testing v0.0.0-20230929141322-214d1fbb9327 h1:AF3VgWSrzKQoYDdXzxF3F8JmV6T3y1910gcA9c0ej4E=
22 | github.com/falcosecurity/testing v0.0.0-20230929141322-214d1fbb9327/go.mod h1:yJVIonjfOTCI7yFQsJRU1PRFL57Ddad0HH2bo3dsiKA=
23 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
24 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
25 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
26 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
27 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
28 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
29 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
30 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
31 | github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
32 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
33 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
34 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
35 | github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
36 | github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
37 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
38 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
39 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
40 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
41 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
42 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
43 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
44 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
45 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
46 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
47 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
48 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
49 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
50 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
51 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
52 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
53 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
54 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
55 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
56 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
57 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
58 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
59 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
60 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
61 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
62 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
63 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
64 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
65 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
66 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
67 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
68 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
69 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
70 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
71 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
72 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
73 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
74 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
75 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
76 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
77 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
78 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
79 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
80 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
81 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
82 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
83 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
84 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
85 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
86 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
87 | golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
88 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
89 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
90 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
91 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
92 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
93 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
94 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
95 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
96 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
97 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
98 | gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
99 |
--------------------------------------------------------------------------------
/build/checker/main.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import "checker/cmd"
21 |
22 | func main() {
23 | cmd.Execute()
24 | }
25 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/.python-version:
--------------------------------------------------------------------------------
1 | 3.10.12
2 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/README.md:
--------------------------------------------------------------------------------
1 | # Mitre ATT&CK Checker Module
2 |
3 | The Mitre ATT&CK Checker module aims to check the compliance of the Falco rules against the Mitre ATT&CK
4 | framework. This module provides to Falco experts and Falco users a way to check default and custom
5 | rules for Mitre ATT&CK extra tags.
6 | This module uses STIX from the OASIS standards. Structured Threat Information Expression (STIX™) is a
7 | language and serialization format used to exchange cyber threat intelligence (CTI) :
8 |
9 | - [STIX CTI documentation](https://oasis-open.github.io/cti-documentation/stix/intro)
10 |
11 | Leveraging STIX, Mitre ATT&CK Checker fetches the ATT&CK® STIX Data from MITRE ATT&CK repositories using
12 | the `python-stix2` library implemented by OASIS:
13 |
14 | - [ATT&CK STIX Data repository](https://github.com/mitre-attack/attack-stix-data)
15 | - [Python STIX2 repository](https://github.com/oasis-open/cti-python-stix2)
16 |
17 | The choice of a module is motivated by the packaging of a python code to integrate it into wider Falco
18 | implementations. More precisely, the module can be used :
19 |
20 | - by the rules_overview_generator.py script
21 | - by Falco users and experts to check their Falco rules files
22 | - by other Falco components that need to check the validity of rules files
23 |
24 | ## Build
25 |
26 | Requirements :
27 |
28 | - Python >= `3.10`
29 | - Poetry >= `1.5.1`
30 |
31 | ```sh
32 | ./build.sh
33 | ```
34 |
35 | ## Install
36 |
37 | Requirements :
38 |
39 | - Python >= `3.10`
40 |
41 | ```sh
42 | ./install.sh
43 | ```
44 |
45 | Or manualy using `pip` :
46 |
47 | ```sh
48 | pip install dist/falco_mitre_attack_checker-0.1.0-py3-none-any.whl
49 | ```
50 |
51 | ## Usage
52 |
53 | ```sh
54 | python -m falco_mitre_attack_checker --help
55 | ```
56 |
57 | Using the stable falco rules :
58 |
59 | ```sh
60 | python -m falco_mitre_attack_checker -f ../../rules/falco_rules.yaml -o /tmp/
61 | ```
62 |
63 | ## Development
64 |
65 | Requirements :
66 |
67 | - Python >= `3.10`
68 | - Poetry >= `1.5.1`
69 |
70 | ```sh
71 | poetry check
72 | poetry update
73 | poetry install --sync
74 | ```
75 |
76 | ### Testing
77 |
78 | With coverage :
79 |
80 | ```sh
81 | poetry update
82 | poetry run python -m pytest --cov=falco_mitre_attack_checker
83 | ```
84 |
85 | ```
86 | ---------- coverage: platform linux, python 3.10.12-final-0 ----------
87 | Name Stmts Miss Cover
88 | ----------------------------------------------------------------------------
89 | falco_mitre_checker/__init__.py 0 0 100%
90 | falco_mitre_checker/__main__.py 7 7 0%
91 | falco_mitre_checker/api/__init__.py 0 0 100%
92 | falco_mitre_checker/api/core.py 19 19 0%
93 | falco_mitre_checker/cli/__init__.py 0 0 100%
94 | falco_mitre_checker/cli/core.py 18 18 0%
95 | falco_mitre_checker/engine/__init__.py 0 0 100%
96 | falco_mitre_checker/engine/mitre_checker.py 46 1 98%
97 | falco_mitre_checker/exceptions/__init__.py 0 0 100%
98 | falco_mitre_checker/exceptions/rules_exceptions.py 8 0 100%
99 | falco_mitre_checker/models/__init__.py 0 0 100%
100 | falco_mitre_checker/models/falco_mitre_errors.py 16 0 100%
101 | falco_mitre_checker/models/falco_mitre_relations.py 14 2 86%
102 | falco_mitre_checker/parsers/__init__.py 0 0 100%
103 | falco_mitre_checker/parsers/falco_rules.py 30 1 97%
104 | falco_mitre_checker/parsers/mitre_stix.py 31 4 87%
105 | falco_mitre_checker/tests/__init__.py 0 0 100%
106 | falco_mitre_checker/tests/engine/__init__.py 0 0 100%
107 | falco_mitre_checker/tests/engine/test_mitre_checker.py 41 0 100%
108 | falco_mitre_checker/tests/parsers/__init__.py 0 0 100%
109 | falco_mitre_checker/tests/parsers/test_falco_rules.py 18 0 100%
110 | falco_mitre_checker/tests/parsers/test_mitre_stix.py 34 0 100%
111 | falco_mitre_checker/tests/test_common.py 13 2 85%
112 | falco_mitre_checker/utils/__init__.py 0 0 100%
113 | falco_mitre_checker/utils/file.py 10 0 100%
114 | falco_mitre_checker/utils/logger.py 36 7 81%
115 | ----------------------------------------------------------------------------
116 | TOTAL 341 61 82%
117 | ```
118 |
119 | ### Security
120 |
121 | You should run a vulnerability scanner every time you add a new dependency in projects :
122 |
123 | ```sh
124 | poetry update
125 | poetry run python -m safety check
126 | ```
127 |
128 | ```
129 | Using non-commercial database
130 | Found and scanned 33 packages
131 | Timestamp 2023-10-02 13:43:51
132 | 0 vulnerabilities found
133 | 0 vulnerabilities ignored
134 | +=======================================================================================================+
135 |
136 | No known security vulnerabilities found.
137 |
138 | +=======================================================================================================+
139 | ```
140 |
141 |
142 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | SCRIPT_PATH="$(realpath "$0")"
4 | MODULE_DIR="$(dirname "${SCRIPT_PATH:?}")"
5 |
6 | #
7 | # FUNCTIONS
8 | #
9 |
10 | function check_requirement(){
11 | if ! eval "$@" >> /dev/null 2>&1 ; then
12 | echo "! Fatal : missing requirement"
13 | if [ -n "${*: -1}" ]; then echo "${@: -1}"; fi
14 | exit 1
15 | fi
16 | }
17 |
18 | #
19 | # MAIN
20 | #
21 |
22 | check_requirement poetry --version "Install poetry first"
23 |
24 | cd "${MODULE_DIR}" || exit
25 | echo "Build environment :"
26 | poetry env info
27 |
28 | echo ""
29 | echo "Update dependencies"
30 | poetry check
31 | poetry update --without dev
32 | poetry install --without dev --sync
33 |
34 | echo "Build Falco Mitre Checker module"
35 | rm -rf "${MODULE_DIR}/dist"
36 | poetry build --format wheel --no-cache
37 |
38 | echo "Built in dist/:"
39 | ls "${MODULE_DIR}/dist/"
40 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/__main__.py:
--------------------------------------------------------------------------------
1 | from falco_mitre_attack_checker.cli.core import cli
2 | from falco_mitre_attack_checker.utils.logger import MitreCheckerLogger
3 |
4 |
5 | def main():
6 | # init logger
7 | MitreCheckerLogger()
8 | # init cli
9 | cli()
10 |
11 |
12 | if __name__ == '__main__':
13 | """
14 | for debug purpose
15 | """
16 | main()
17 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/api/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/api/core.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from pathlib import Path
3 | from typing import List, Dict
4 |
5 | from falco_mitre_attack_checker.engine.mitre_checker import FalcoMitreChecker
6 | from falco_mitre_attack_checker.models.falco_mitre_errors import FalcoMitreError
7 | from falco_mitre_attack_checker.utils.logger import MitreCheckerLogger
8 |
9 | logger = logging.getLogger(MitreCheckerLogger.name)
10 |
11 |
12 | def mitre_checker_engine(rules_files: List[Path], mitre_domain: str, mitre_version: str,
13 | output_dir: Path = None) -> "Dict[str, List[FalcoMitreError]]":
14 | """
15 | CLI core function to validate the rules against the Mitre ATT&CK's data.
16 | :param rules_files: One or more falco rules files to check
17 | :param mitre_domain: The name of the Mitre ATT&CK matrix domain to validate the rules. This name is
18 | used to pull the data from the Mitre CTI's database.
19 | :param mitre_version: The version of the Mitre ATT&CK to validate the rules. This version is used to
20 | pull the data from the Mitre CTI's database.
21 | :param output_dir: A folder path to dump the errors information in json format.
22 | :param fix: If True, automatically generate the corrected falco rules file next to the original one
23 | """
24 | mitre_checker = FalcoMitreChecker(mitre_domain, mitre_version)
25 | errors_reports: Dict[str, List[FalcoMitreError]] = {}
26 | for file in rules_files:
27 | # validate the falco rules against the data of the mitre ATT&CK framework
28 | errors = mitre_checker.validate(file)
29 | errors_reports[file.stem] = errors
30 | output_name = f"{file.stem}_mitre_errors.json"
31 | output_path = output_dir / output_name if output_dir else file.parent / output_name
32 |
33 | FalcoMitreChecker.dump_errors(errors, output_path)
34 | logger.info(f"Dumped errors report in '{output_path}'")
35 |
36 | logger.info(f"Found {len(errors)} Mitre errors")
37 |
38 | return errors_reports
39 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/cli/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/cli/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/cli/core.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from pathlib import Path
3 | from typing import List
4 |
5 | import typer
6 |
7 | from falco_mitre_attack_checker.api.core import mitre_checker_engine
8 | from falco_mitre_attack_checker.exceptions.rules_exceptions import FalcoRulesFileContentError
9 | from falco_mitre_attack_checker.utils.logger import MitreCheckerLogger
10 |
11 | app = typer.Typer(help=f"Mitre Checker",
12 | no_args_is_help=True,
13 | context_settings={"help_option_names": ["-h", "--help"]})
14 |
15 | logger = logging.getLogger(MitreCheckerLogger.name)
16 |
17 |
18 | @app.command()
19 | def core(rules_files: List[Path] = typer.Option(..., "-f", "--file",
20 | help="Path to a Falco rules file. "
21 | "Repeat for multiple files validation."),
22 | mitre_domain: str = typer.Option("enterprise-attack", "-d", "--domain",
23 | help="Mitre ATT&CK domain name."),
24 | mitre_version: str = typer.Option("13.1", "-V", "--Version",
25 | help="Mitre ATT&CK domain version."),
26 | output_dir: Path = typer.Option(None, "-o", "--output-dir",
27 | help="Path to a directory to dump the error report for Mitre "
28 | "ATT&CK.")
29 | ):
30 | try:
31 | mitre_checker_engine(rules_files, mitre_domain, mitre_version, output_dir)
32 | except FalcoRulesFileContentError as e:
33 | logger.error(e.message)
34 | typer.Exit(1)
35 |
36 |
37 | def cli():
38 | app()
39 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/engine/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/engine/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/engine/mitre_checker.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from pathlib import Path
3 | from typing import List
4 |
5 | from falco_mitre_attack_checker.models.falco_mitre_errors import \
6 | ErrorReason, FalcoMitreError, FalcoRulesErrors
7 | from falco_mitre_attack_checker.models.falco_mitre_relations import MitreRelations
8 | from falco_mitre_attack_checker.parsers.falco_rules import FalcoRulesParser
9 | from falco_mitre_attack_checker.parsers.mitre_stix import MitreParser
10 | from falco_mitre_attack_checker.utils.file import write_file
11 | from falco_mitre_attack_checker.utils.logger import MitreCheckerLogger
12 |
13 | logger = logging.getLogger(MitreCheckerLogger.name)
14 |
15 |
16 | class FalcoMitreChecker(object):
17 |
18 | def __init__(self, mitre_domain: str, mitre_domain_version: str):
19 | logger.info(f"Load Mitre ATT&CK STIX Data for domain '{mitre_domain}' and version "
20 | f"'{mitre_domain_version}'")
21 | self.mitre_parser = MitreParser(mitre_domain, mitre_domain_version)
22 |
23 | def validate(self, falco_rules_file: Path) -> "List[FalcoMitreError]":
24 | """
25 | This function validates the falco rules' extra tags against Mitre ATT&CK STIX Data when they
26 | contain mitre information.
27 | This method gets the mitre techniques or sub-techniques IDs and the mitre tactics (mitre phases)
28 | names in the extra tags of each falco rules.
29 | If the mitre techniques or sub-techniques IDs in the tags are not related to proper the mitre
30 | tactics names by comparing them with the mitre data (STIX data from Mitre CTI), this method
31 | considers that the rule contains an error.
32 | For example, if the extra tags contain :
33 | {"tags": ["T1611", "mitre_initial_access"] }
34 | And the actual mitre domain is 'enterprise-attack' in version '13.1', the tags' rule will be
35 | considered erroneous since the proper mitre phase for 'T1611' is 'privilege-escalation' in this
36 | version.
37 | :param falco_rules_file: A falco rule file to analyse against the Mitre ATT&CK STIX Data
38 | :return: A list of models containing a description of each error in the falco rules for Mitre
39 | ATT&CK
40 | """
41 | logger.info(f"Audit Falco rules file '{falco_rules_file}' for Mitre ATT&CK")
42 | falco_rules_parser = FalcoRulesParser(falco_rules_file)
43 | falco_mitre_errors: List[FalcoMitreError] = []
44 | # build the model relation between technique (or sub-technique) ID and the mitre phase configured
45 | # in each rule
46 | rules_mitre_relations: MitreRelations = falco_rules_parser.get_mitre_relations()
47 | for rule_name, rule_mitre_relation in rules_mitre_relations.rules.items():
48 | rule_tactics = rule_mitre_relation.tactics
49 | all_mitre_tactics = []
50 | all_mitre_techniques_names = []
51 | all_mitre_techniques_urls = []
52 |
53 | # verify each technique tag against mitre data
54 | for rule_technique_or_tactic in rule_mitre_relation.techniques:
55 | mitre_technique_or_tactic = self.mitre_parser.get_tactic_or_technique_by_id(
56 | rule_technique_or_tactic)
57 | mitre_tactics_names = self.mitre_parser.get_tactics_names(mitre_technique_or_tactic)
58 | formatted_mitre_tactics_names = [f"mitre_{tactic.replace('-', '_')}" for tactic in
59 | mitre_tactics_names]
60 | # gather all correct mitre tactics & techniques of this rule
61 | all_mitre_tactics += mitre_tactics_names
62 | mitre_technique_name = self.mitre_parser.get_mitre_name(mitre_technique_or_tactic)
63 | mitre_technique_url = self.mitre_parser.get_technique_external_reference(
64 | mitre_technique_or_tactic)['url']
65 | all_mitre_techniques_names.append(mitre_technique_name)
66 | all_mitre_techniques_urls.append(mitre_technique_url)
67 | if not set(formatted_mitre_tactics_names).issubset(set(rule_tactics)):
68 | # detect errors
69 | # missing tactic tag in rule for this technique
70 | falco_error = FalcoMitreError(rule=rule_name,
71 | techniques_tags=[rule_technique_or_tactic],
72 | tactics_tags=rule_tactics,
73 | mitre_techniques_names=[mitre_technique_name],
74 | mitre_tactics_names=mitre_tactics_names,
75 | mitre_techniques_urls=[mitre_technique_url],
76 | reasons=[ErrorReason.MISSING])
77 |
78 | falco_mitre_errors.append(falco_error)
79 |
80 | # verify tactics
81 | all_mitre_tactics_set = set(all_mitre_tactics)
82 | if len(rule_tactics) > len(all_mitre_tactics_set):
83 | # detect errors when too many tactic tags are included into the rule extra tags
84 | falco_error = FalcoMitreError(rule=rule_name,
85 | techniques_tags=rule_mitre_relation.techniques,
86 | tactics_tags=rule_tactics,
87 | mitre_techniques_names=list(
88 | set(all_mitre_techniques_names)),
89 | mitre_tactics_names=list(set(all_mitre_tactics_set)),
90 | mitre_techniques_urls=list(set(all_mitre_techniques_urls)),
91 | reasons=[ErrorReason.OVERDO])
92 | falco_mitre_errors.append(falco_error)
93 |
94 | return falco_mitre_errors
95 |
96 | def autofix(self, falco_rules_file: Path, falco_mitre_errors: List[FalcoMitreError]):
97 | """
98 | Automatically fix Mitre tags in a falco rules file from a provided falco mitre errors report
99 | :param falco_rules_file: the rules file to fix
100 | :param falco_mitre_errors: the falco mitre error report for this file
101 | """
102 | pass
103 |
104 | @staticmethod
105 | def dump_errors(falco_mitre_errors: List[FalcoMitreError], output: Path) -> None:
106 | """
107 | Write a list of falco mitre errors model to a file
108 | :param output: output file to dump the errors
109 | :param falco_mitre_errors: List of falco mitre errors models
110 | """
111 | write_file(FalcoRulesErrors(errors=falco_mitre_errors).json(), output)
112 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/exceptions/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/exceptions/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/exceptions/rules_exceptions.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 |
3 |
4 | class FalcoException(Exception):
5 | pass
6 |
7 |
8 | class FalcoRulesFileContentError(Exception):
9 | def __init__(self, file: Path, message: str = "Wrong Falco Rules file content or format", *args):
10 | self.file = file
11 | self.message = message
12 | super().__init__(self.message, args)
13 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/models/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/models/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/models/falco_mitre_errors.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import List
3 |
4 | from pydantic import BaseModel
5 |
6 |
7 | class ErrorReason(str, Enum):
8 | MISSING = "One or more tactics tags are missing"
9 | OVERDO = "Too many tactics tags"
10 |
11 |
12 | class FalcoMitreError(BaseModel):
13 | """
14 | This model describe an error located in a falco rule and related to its mitre tag
15 | """
16 | # FALCO RELATED INFORMATION
17 | # 'rule' is the rule description in the falco rules file
18 | rule: str
19 | # 'tactics_tags' are the tags of Mitre ATT&CK tactics in the current falco rule
20 | tactics_tags: List[str]
21 | # 'technique_tag' is the tag of a Mitre ATT&CK technique in the current falco rule
22 | techniques_tags: List[str]
23 |
24 | # MITRE ATT&CK RELATED INFORMATION FROM STIX DATA
25 | # 'mitre_tactics_names' are the Mitre ATT&CK's tactics name related to the technique tag in the
26 | # current falco rule. These names are taken from STIX data.
27 | mitre_tactics_names: List[str]
28 | # 'mitre_technique_name' is the Mitre ATT&CK's technique name related to the technique tag in the
29 | # current falco rule. This name is taken from STIX data.
30 | mitre_techniques_names: List[str]
31 | # 'mitre_technique_url' is the Mitre ATT&CK's technique url related to the technique tag in the
32 | # current falco rule. This url is taken from STIX data.
33 | mitre_techniques_urls: List[str]
34 | # details about the error
35 | reasons: List[ErrorReason]
36 |
37 |
38 | class FalcoRulesErrors(BaseModel):
39 | """
40 | This model is just useful to dump errors to disk
41 | """
42 | errors: List[FalcoMitreError]
43 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/models/falco_mitre_relations.py:
--------------------------------------------------------------------------------
1 | from typing import List, Dict
2 |
3 | from pydantic import BaseModel
4 |
5 |
6 | class MitreRelation(BaseModel):
7 | """
8 | Simple relation between Mitre techniques or sub-techniques and the attached mitre phases
9 | """
10 | techniques: List[str]
11 | tactics: List[str]
12 |
13 |
14 | class MitreRelations(BaseModel):
15 | """
16 | This class builds a relation between a Falco rule and the extra tags it uses for Mitre ATT&CK
17 | """
18 | rules: Dict[str, MitreRelation] = {}
19 |
20 | def __len__(self):
21 | return len(self.rules)
22 |
23 | def add_techniques_and_tactics(self, rule_name: str, techniques_ids: List[str],
24 | tactics_names: List[str]):
25 | if rule_name in self.rules.keys():
26 | self.rules[rule_name].techniques += techniques_ids
27 | self.rules[rule_name].tactics += tactics_names
28 | else:
29 | self.rules[rule_name] = MitreRelation(techniques=techniques_ids, tactics=tactics_names)
30 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/parsers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/parsers/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/parsers/falco_rules.py:
--------------------------------------------------------------------------------
1 | import re
2 | from pathlib import Path
3 | from typing import Dict
4 |
5 | from falco_mitre_attack_checker.exceptions.rules_exceptions import FalcoRulesFileContentError
6 | from falco_mitre_attack_checker.models.falco_mitre_relations import MitreRelations
7 | from falco_mitre_attack_checker.utils.file import read_yaml
8 |
9 |
10 | class FalcoRulesParser(object):
11 | """
12 | A Deserialization class for Falco rules file in order to define parsing methods
13 | """
14 | VALIDATION_KEY = "required_engine_version"
15 | rules: Dict
16 |
17 | def __init__(self, rules_file: Path):
18 | self.path = rules_file
19 | self.rules = read_yaml(rules_file)
20 | self.validate()
21 |
22 | def validate(self):
23 | """
24 | Simple function to check if the submitted file contains some requirements that Falco rules files
25 | should have.
26 | """
27 | error = FalcoRulesFileContentError(self.path,
28 | message=f"Missing 'required_engine_version' conf in "
29 | f"{self.path}, so wrong falco rules file format or "
30 | f"not a rules file.")
31 | try:
32 | if not [items for items in self.rules if self.VALIDATION_KEY in items.keys()]:
33 | raise error
34 | except AttributeError:
35 | raise error
36 |
37 | def get_mitre_relations(self) -> "MitreRelations":
38 | """
39 | Build a relation model between techniques and mitre phases described in the falco rules
40 | :return: the list of the relations
41 | """
42 | # filter for rules with extra tags
43 | filtered_rules = [rule for rule in self.rules if "tags" in rule.keys()]
44 | relations = MitreRelations()
45 | for rule in filtered_rules:
46 | rule_desc: str = rule['rule']
47 | formatted_tags = [str(tag).upper() for tag in rule['tags']]
48 | tactics = [tag.lower() for tag in formatted_tags if "MITRE_" in tag]
49 | techniques = [tag for tag in formatted_tags if re.search("^TA?(\\d+).(\\d+)", tag)]
50 | relations.add_techniques_and_tactics(rule_desc, techniques, tactics)
51 |
52 | return relations
53 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/parsers/mitre_stix.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from typing import Dict, List
3 |
4 | import requests
5 | from stix2 import MemoryStore, Filter, AttackPattern
6 |
7 | from falco_mitre_attack_checker.utils.logger import MitreCheckerLogger
8 |
9 | logger = logging.getLogger(MitreCheckerLogger.name)
10 |
11 |
12 | class MitreParser(object):
13 | """
14 | A Deserialization class for Mitre ATT&CK STIX2 data from Mitre CTI in order to define parsing methods
15 | """
16 | # src is the source data directly fetched from STIX2 CTI bundle
17 | src: MemoryStore
18 |
19 | def __init__(self, mitre_domain: str, mitre_domain_version: str):
20 | """
21 | Init the Mitre parser by loading Mitre's STIX data from source.
22 | https://github.com/mitre/cti/blob/master/USAGE.md
23 | :param mitre_domain: either 'enterprise-attack', 'mobile-attack', or 'ics-attack'
24 | :param mitre_domain_version: version of the mitre domain in format 'XX.XX'
25 | """
26 | self.mitre_domain = mitre_domain
27 | self.mitre_domain_version = mitre_domain_version
28 | stix_json = requests.get(
29 | f"https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v{mitre_domain_version}/{mitre_domain}/{mitre_domain}.json").json()
30 | self.src = MemoryStore(stix_data=stix_json["objects"])
31 |
32 | def get_tactic_or_technique_by_id(self, external_id: str) -> "AttackPattern | None":
33 | """
34 | Query Mitre CTI's STIX data to search a STIX technique definition by its ID
35 | :param external_id: ID of the MITRE ATT&CK technique
36 | :return: the technique definition in STIX2 data format
37 | """
38 | # by default, a List is returned for STIX2 refs, but we expect only one technique per ID
39 | try:
40 | technique = self.src.query([
41 | Filter('external_references.external_id', '=', external_id),
42 | Filter('type', 'in', ['x-mitre-tactic', 'attack-pattern']),
43 | ])[0]
44 | # Some techniques do not contain the 'x_mitre_deprecated' field
45 | # So it is not exploitable with a filter, but we can do it by ourselves
46 | if 'x_mitre_technique' in technique:
47 | # return None if deprecated
48 | return technique if not technique.x_mitre_deprecated else None
49 | # considering technique is valid if no 'deprecation' field is defined
50 | return technique
51 | except IndexError:
52 | logger.warning(f"Technique {external_id} doesn't exist for '{self.mitre_domain}' "
53 | f"v{self.mitre_domain_version}")
54 | return None
55 |
56 | @classmethod
57 | def get_tactics_names(cls, ttp: AttackPattern) -> "List[str]":
58 | """
59 | Get the mitre phase name (tactic) of a given technique or tactic.
60 | If it is a tactic, only return the tactic name.
61 | :param ttp: The MITRE ATT&CK data of a technique of a tactic
62 | :return: The mitre phase names of the given technique or tactic
63 | """
64 | return [tactic["phase_name"] for tactic in
65 | ttp["kill_chain_phases"]] if "kill_chain_phases" in ttp else [ttp["x_mitre_shortname"]]
66 |
67 | @classmethod
68 | def get_mitre_name(cls, ttp: AttackPattern) -> str:
69 | return ttp['name']
70 |
71 | @classmethod
72 | def get_technique_external_reference(cls, ttp: AttackPattern) -> "Dict[str, str]":
73 | return [reference for reference in ttp['external_references']
74 | if reference['source_name'] == "mitre-attack"][0]
75 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/tests/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/tests/engine/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/tests/engine/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/tests/engine/test_mitre_checker.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | from typing import List
3 |
4 | from falco_mitre_attack_checker.engine.mitre_checker import FalcoMitreChecker
5 | from falco_mitre_attack_checker.models.falco_mitre_errors import ErrorReason, FalcoRulesErrors, FalcoMitreError
6 | from falco_mitre_attack_checker.tests.test_common import MITRE_DOMAIN, MITRE_VERSION, FALCO_RULES_FILE
7 |
8 | # global
9 | mitre_checker = FalcoMitreChecker(MITRE_DOMAIN, MITRE_VERSION)
10 | assert mitre_checker.mitre_parser
11 |
12 | errors: List[FalcoMitreError] = mitre_checker.validate(FALCO_RULES_FILE)
13 | assert errors
14 |
15 |
16 | def get_errors_by_rule(rule_name: str,
17 | myerrors: "List[FalcoMitreError]") -> "List[FalcoMitreError]":
18 | return [e for e in myerrors if e.rule == rule_name]
19 |
20 |
21 | def get_error_by_technique(technique: str,
22 | myerrors: "List[FalcoMitreError]") -> "FalcoMitreError":
23 | return [e for e in myerrors if e.techniques_tags == [technique]][0]
24 |
25 |
26 | def test_validate():
27 | # mitre tag not matching the technique phase
28 | errors_1 = get_errors_by_rule('wrong mitre rule', errors)
29 | assert errors_1
30 | assert len(errors_1) == 1
31 | error_1: FalcoMitreError = get_error_by_technique('T1610', errors_1)
32 | assert error_1
33 | assert error_1.tactics_tags == ['mitre_lateral_movement']
34 | assert error_1.mitre_tactics_names == ['defense-evasion', 'execution']
35 | assert error_1.reasons == [ErrorReason.MISSING]
36 |
37 | # missing mitre tag for multiple techniques
38 | # desc: one tactic tag is missing to fulfill all the mitre phases from the tagged techniques
39 | errors_2 = get_errors_by_rule("wrong mitre rule multiple techniques and missing one tactic", errors)
40 | assert len(errors_2) == 1
41 | error_1020: FalcoMitreError = get_error_by_technique('T1020', errors_2)
42 | assert error_1020
43 | assert error_1020.tactics_tags == ['mitre_credential_access', 'mitre_discovery']
44 | assert error_1020.mitre_tactics_names == ['exfiltration']
45 | assert error_1020.reasons == [ErrorReason.MISSING]
46 |
47 | # too many tactics tags
48 | errors_3 = get_errors_by_rule("too many tactics tags with multiple techniques", errors)
49 | assert len(errors_3) == 1
50 | error_tactics: FalcoMitreError = errors_3[0]
51 | assert error_tactics.tactics_tags.sort() == ["mitre_discovery", "mitre_exfiltration",
52 | "mitre_credential_access", "mitre_execution"].sort()
53 | assert error_tactics.mitre_tactics_names.sort() == ["mitre_discovery", "mitre_exfiltration",
54 | "mitre_credential_access"].sort()
55 | assert error_tactics.reasons == [ErrorReason.OVERDO]
56 |
57 |
58 | def test_dump():
59 | output = Path('/tmp/test_falco_mitre_checker_dump.json')
60 | mitre_checker.dump_errors(errors, output)
61 | assert output.exists()
62 | from_file = FalcoRulesErrors.parse_file(output)
63 | assert from_file
64 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/tests/parsers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/tests/parsers/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/tests/parsers/test_falco_rules.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from falco_mitre_attack_checker.exceptions.rules_exceptions import FalcoRulesFileContentError
4 | from falco_mitre_attack_checker.parsers.falco_rules import FalcoRulesParser
5 | from falco_mitre_attack_checker.tests.test_common import NOT_FALCO_RULES_FILE, FALCO_RULES_FILE
6 |
7 | # test falco rules file validation
8 | with pytest.raises(FalcoRulesFileContentError):
9 | FalcoRulesParser(NOT_FALCO_RULES_FILE)
10 |
11 | falco_rules_parser = FalcoRulesParser(FALCO_RULES_FILE)
12 | assert falco_rules_parser.rules
13 |
14 |
15 | def test_get_mitre_relations():
16 | relations = falco_rules_parser.get_mitre_relations()
17 | assert relations
18 | assert len(relations) == 6
19 |
20 | correct_mitre_rule = relations.rules['correct mitre rule']
21 | assert correct_mitre_rule.tactics == ['mitre_persistence']
22 | assert correct_mitre_rule.techniques == ['T1098']
23 |
24 | wrong_mitre_rule = relations.rules['wrong mitre rule']
25 | assert wrong_mitre_rule.tactics == ['mitre_lateral_movement']
26 | assert wrong_mitre_rule.techniques == ['T1610']
27 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/tests/parsers/test_mitre_stix.py:
--------------------------------------------------------------------------------
1 | from falco_mitre_attack_checker.parsers.mitre_stix import MitreParser
2 | from falco_mitre_attack_checker.tests.test_common import RESOURCES_DIR, MITRE_VERSION, MITRE_DOMAIN
3 |
4 | MITRE_STIX_DATAFILE = f"{RESOURCES_DIR}/mitre_cti_stix_13_1.json"
5 |
6 | mitre_parser = MitreParser(MITRE_DOMAIN, MITRE_VERSION)
7 | assert mitre_parser.src
8 |
9 |
10 | def test_get_tactic_or_technique_by_id():
11 | # technique
12 | technique = mitre_parser.get_tactic_or_technique_by_id("T1548.001")
13 | assert technique
14 | assert not bool(technique.x_mitre_deprecated)
15 | assert technique.type == 'attack-pattern'
16 | assert technique.kill_chain_phases
17 | kill_chain_names = [chain.kill_chain_name for chain in technique.kill_chain_phases]
18 | assert 'mitre-attack' in kill_chain_names
19 |
20 | # tactic
21 | tactic = mitre_parser.get_tactic_or_technique_by_id("TA0001")
22 | assert tactic
23 | assert tactic['type'] == "x-mitre-tactic"
24 |
25 |
26 | def test_get_mitre_name():
27 | technique = mitre_parser.get_tactic_or_technique_by_id("T1548.001")
28 | assert mitre_parser.get_mitre_name(technique) == "Setuid and Setgid"
29 |
30 |
31 | def test_get_technique_external_reference():
32 | technique = mitre_parser.get_tactic_or_technique_by_id("T1548.001")
33 | reference = mitre_parser.get_technique_external_reference(technique)
34 | assert reference
35 | assert reference['source_name'] == 'mitre-attack'
36 | assert reference['url'] == "https://attack.mitre.org/techniques/T1548/001"
37 |
38 |
39 | def test_get_tactics_names():
40 | # technique with multiple tactics
41 | technique = mitre_parser.get_tactic_or_technique_by_id("T1610")
42 | tactics_names = mitre_parser.get_tactics_names(technique)
43 | assert tactics_names
44 | assert tactics_names == ['defense-evasion', 'execution']
45 |
46 | # tactic
47 | tactic = mitre_parser.get_tactic_or_technique_by_id("TA0001")
48 | tactics_names = mitre_parser.get_tactics_names(tactic)
49 | assert tactics_names
50 | assert tactics_names == ['initial-access']
51 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/tests/resources/falco_rules_test.yaml:
--------------------------------------------------------------------------------
1 | - required_engine_version: 0.31.0
2 |
3 | - macro: not a rule
4 | condition: true
5 |
6 | - rule: not a mitre rule
7 | desc: an attempt to write to any file below /etc
8 | condition: write_etc_common
9 | output: "File below /etc opened for writing (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent=%proc.pname pcmdline=%proc.pcmdline file=%fd.name program=%proc.name gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] container_id=%container.id image=%container.image.repository)"
10 | priority: ERROR
11 | tags: [ host, container, filesystem ]
12 |
13 | - rule: correct mitre rule
14 | desc: an attempt to write to any file below /etc
15 | condition: write_etc_common
16 | output: "File below /etc opened for writing (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent=%proc.pname pcmdline=%proc.pcmdline file=%fd.name program=%proc.name gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] container_id=%container.id image=%container.image.repository)"
17 | priority: ERROR
18 | tags: [ host, container, network, mitre_persistence, T1098 ]
19 |
20 | - rule: wrong mitre rule
21 | desc: >
22 | Detect the initial process started by a container that is not in a list of allowed containers.
23 | condition: container_started and container and not allowed_containers
24 | output: Container started and not in allowed list (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid %container.info image=%container.image.repository:%container.image.tag)
25 | priority: WARNING
26 | tags: [ container, mitre_lateral_movement, T1610 ]
27 |
28 | - rule: correct mitre rule tactics tags with multiple techniques
29 | desc: >
30 | Directory traversal monitored file read - Web applications can be vulnerable to directory traversal attacks that allow accessing files outside of the web app's root directory (e.g. Arbitrary File Read bugs).
31 | System directories like /etc are typically accessed via absolute paths. Access patterns outside of this (here path traversal) can be regarded as suspicious.
32 | This rule includes failed file open attempts.
33 | condition: (open_read or open_file_failed) and (etc_dir or user_ssh_directory or fd.name startswith /root/.ssh or fd.name contains "id_rsa") and directory_traversal and not proc.pname in (shell_binaries)
34 | enabled: true
35 | output: >
36 | Read monitored file via directory traversal (username=%user.name useruid=%user.uid user_loginuid=%user.loginuid program=%proc.name exe=%proc.exepath
37 | command=%proc.cmdline pid=%proc.pid parent=%proc.pname file=%fd.name fileraw=%fd.nameraw parent=%proc.pname
38 | gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository returncode=%evt.res cwd=%proc.cwd)
39 | priority: WARNING
40 | tags: [ host, container, filesystem, mitre_discovery, mitre_exfiltration, mitre_credential_access, T1555, T1212, T1020, T1552, T1083 ]
41 |
42 | - rule: too many tactics tags with multiple techniques
43 | desc: >
44 | Directory traversal monitored file read - Web applications can be vulnerable to directory traversal attacks that allow accessing files outside of the web app's root directory (e.g. Arbitrary File Read bugs).
45 | System directories like /etc are typically accessed via absolute paths. Access patterns outside of this (here path traversal) can be regarded as suspicious.
46 | This rule includes failed file open attempts.
47 | condition: (open_read or open_file_failed) and (etc_dir or user_ssh_directory or fd.name startswith /root/.ssh or fd.name contains "id_rsa") and directory_traversal and not proc.pname in (shell_binaries)
48 | enabled: true
49 | output: >
50 | Read monitored file via directory traversal (username=%user.name useruid=%user.uid user_loginuid=%user.loginuid program=%proc.name exe=%proc.exepath
51 | command=%proc.cmdline pid=%proc.pid parent=%proc.pname file=%fd.name fileraw=%fd.nameraw parent=%proc.pname
52 | gparent=%proc.aname[2] container_id=%container.id image=%container.image.repository returncode=%evt.res cwd=%proc.cwd)
53 | priority: WARNING
54 | tags: [ host, container, filesystem, mitre_discovery, mitre_exfiltration, mitre_credential_access, mitre_execution, T1555, T1212, T1020, T1552, T1083 ]
55 |
56 | - rule: wrong mitre rule multiple techniques and missing one tactic
57 | desc: >
58 | Read sensitive file untrusted - an attempt to read any sensitive file (e.g. files containing user/password/authentication
59 | information). Exceptions are made for known trusted programs.
60 | condition: >
61 | sensitive_files and open_read
62 | and proc_name_exists
63 | and not proc.name in (user_mgmt_binaries, userexec_binaries, package_mgmt_binaries,
64 | cron_binaries, read_sensitive_file_binaries, shell_binaries, hids_binaries,
65 | vpn_binaries, mail_config_binaries, nomachine_binaries, sshkit_script_binaries,
66 | in.proftpd, mandb, salt-call, salt-minion, postgres_mgmt_binaries,
67 | google_oslogin_
68 | )
69 | and not cmp_cp_by_passwd
70 | and not ansible_running_python
71 | and not run_by_qualys
72 | and not run_by_chef
73 | and not run_by_google_accounts_daemon
74 | and not user_read_sensitive_file_conditions
75 | and not mandb_postinst
76 | and not perl_running_plesk
77 | and not perl_running_updmap
78 | and not veritas_driver_script
79 | and not perl_running_centrifydc
80 | and not runuser_reading_pam
81 | and not linux_bench_reading_etc_shadow
82 | and not user_known_read_sensitive_files_activities
83 | and not user_read_sensitive_file_containers
84 | output: >
85 | Sensitive file opened for reading by non-trusted program (user=%user.name user_loginuid=%user.loginuid program=%proc.name
86 | command=%proc.cmdline pid=%proc.pid file=%fd.name parent=%proc.pname gparent=%proc.aname[2] ggparent=%proc.aname[3] gggparent=%proc.aname[4] container_id=%container.id image=%container.image.repository)
87 | priority: WARNING
88 | tags: [ host, container, filesystem, mitre_credential_access, mitre_discovery, T1555, T1212, T1020, T1552, T1083 ]
89 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/tests/resources/not_falco_rules_test.yaml:
--------------------------------------------------------------------------------
1 | test: True
2 | falco-rules: false
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/tests/test_common.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 |
4 | from falco_mitre_attack_checker.utils.logger import MitreCheckerLogger
5 |
6 | MitreCheckerLogger()
7 |
8 | TEST_DIR = os.path.dirname(os.path.abspath(__file__))
9 | RESOURCES_DIR = f"{TEST_DIR}/resources"
10 |
11 | MITRE_VERSION = "13.1"
12 | MITRE_DOMAIN = "enterprise-attack"
13 |
14 | FALCO_RULES_FILE = Path(f"{RESOURCES_DIR}/falco_rules_test.yaml")
15 | NOT_FALCO_RULES_FILE = Path(f"{RESOURCES_DIR}/not_falco_rules_test.yaml")
16 |
17 |
18 | def read_file(path: Path) -> "str":
19 | with open(path, 'r') as f:
20 | return str(f.read())
21 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/build/mitre_attack_checker/falco_mitre_attack_checker/utils/__init__.py
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/utils/file.py:
--------------------------------------------------------------------------------
1 | import os
2 | from pathlib import Path
3 | from typing import Dict
4 |
5 | import yaml
6 |
7 |
8 | def read_yaml(path: Path) -> Dict:
9 | """
10 | Validate format and read yaml file content
11 | :param path: Path to a yaml file
12 | :return: file content as dictionnary
13 | """
14 | with open(path, "r") as p:
15 | return yaml.safe_load(p.read())
16 |
17 |
18 | def write_file(content: str, output: Path):
19 | with open(os.path.expandvars(output), 'w') as f:
20 | f.write(content)
21 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/falco_mitre_attack_checker/utils/logger.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import sys
3 | from pathlib import Path
4 | from typing import Optional
5 |
6 |
7 | class LoggerFormatter(logging.Formatter):
8 | green = "\x1b[92;21m"
9 | cyan = "\x1b[96;21m"
10 | yellow = "\x1b[93;21m"
11 | red = "\x1b[91;21m"
12 | reset = "\x1b[0m"
13 | tag = "[%(levelname)s]"
14 | message = " %(message)s"
15 | file = " - (%(filename)s:%(lineno)d)"
16 |
17 | FORMATS = {
18 | logging.DEBUG: cyan + tag + reset + message,
19 | logging.INFO: green + tag + reset + message,
20 | logging.WARNING: yellow + tag + reset + message + file,
21 | logging.ERROR: red + tag + reset + message + file,
22 | logging.FATAL: red + tag + reset + message + file
23 | }
24 |
25 | def format(self, record):
26 | log_fmt = self.FORMATS.get(record.levelno)
27 | formatter = logging.Formatter(log_fmt)
28 | return formatter.format(record)
29 |
30 |
31 | class MitreCheckerLogger:
32 | name: str = "mitre-checker"
33 | formatter: logging.Formatter = LoggerFormatter()
34 |
35 | def __init__(self, debug: bool = False, logfile: Optional[Path] = None):
36 | logger = logging.getLogger(self.name)
37 |
38 | # verbosity
39 | level = logging.DEBUG if debug else logging.INFO
40 | logger.setLevel(level)
41 |
42 | # add stdout logger to logging
43 | stdout_handler = logging.StreamHandler(sys.stdout)
44 | stdout_handler.setFormatter(self.formatter)
45 | logger.addHandler(stdout_handler)
46 |
47 | # logfile output
48 | if logfile is not None:
49 | logfile_path = Path(logfile)
50 | logfile_path.parent.mkdir(parents=True, exist_ok=True)
51 | logfile_path.touch(exist_ok=True)
52 | output_file_handler = logging.FileHandler(logfile)
53 | output_file_handler.setLevel(level)
54 | output_file_handler.setFormatter(self.formatter)
55 | logger.addHandler(output_file_handler)
56 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | SCRIPT_PATH="$(realpath "$0")"
4 | MODULE_DIR="$(dirname "${SCRIPT_PATH:?}")"
5 |
6 | #
7 | # FUNCTIONS
8 | #
9 |
10 | function check_requirement(){
11 | if ! eval "$@" >> /dev/null 2>&1 ; then
12 | echo "! Fatal : missing requirement"
13 | if [ -n "${*: -1}" ]; then echo "${@: -1}"; fi
14 | exit 1
15 | fi
16 | }
17 |
18 | #
19 | # MAIN
20 | #
21 |
22 | cd "${MODULE_DIR}" || exit
23 | pyversion=$(python --version)
24 | echo "Install falco mitre checker module for: ${pyversion}"
25 |
26 | echo ""
27 | wheel="$(find "./dist/" -type f -name "*.whl")"
28 | python -m pip install "${wheel}" --force-reinstall --no-cache-dir
29 |
30 | echo ""
31 | echo "OK"
32 |
--------------------------------------------------------------------------------
/build/mitre_attack_checker/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "falco_mitre_attack_checker"
3 | version = "0.1.0"
4 | description = "Audit module to validate Falco rules against the Mitre ATT&CK Framework"
5 | authors = ["The Falco Authors "]
6 | license = "Apache-2.0"
7 | readme = "README.md"
8 | packages = [{include = "falco_mitre_attack_checker"}]
9 |
10 | [tool.poetry.dependencies]
11 | python = "^3.10"
12 | pydantic = "^1.10.9"
13 | stix2 = "^3.0.1"
14 | typer = "^0.9.0"
15 | pyyaml = "^6.0"
16 |
17 | [tool.poetry.group.dev.dependencies]
18 | pytest-cov = "^4.1.0"
19 | pytest = "^7.4.0"
20 | safety = "^2.3.5"
21 |
22 | [build-system]
23 | requires = ["poetry-core"]
24 | build-backend = "poetry.core.masonry.api"
25 |
26 | [tool.poetry.scripts]
27 | falco_mitre_attack_checker = "mitre_attack_checker.__main__:main"
28 |
--------------------------------------------------------------------------------
/build/registry/cmd.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "errors"
23 | "fmt"
24 | "log"
25 | "os"
26 | "path"
27 | "path/filepath"
28 | "strings"
29 |
30 | "github.com/aws/aws-sdk-go/aws"
31 | "github.com/aws/aws-sdk-go/aws/session"
32 | "github.com/falcosecurity/falcoctl/pkg/oci/authn"
33 | "github.com/falcosecurity/falcoctl/pkg/oci/repository"
34 | "github.com/spf13/cobra"
35 | "oras.land/oras-go/v2/errdef"
36 | "oras.land/oras-go/v2/registry/remote/auth"
37 | )
38 |
39 | const (
40 | RegistryTokenEnv = "REGISTRY_TOKEN"
41 | RegistryUserEnv = "REGISTRY_USER"
42 | OCIRepoPrefixEnv = "OCI_REPO_PREFIX"
43 | RepoGithubEnv = "GITHUB_REPO_URL"
44 | S3BucketEnv = "AWS_S3_BUCKET"
45 | S3PrefixEnv = "AWS_S3_PREFIX"
46 | S3RegionEnv = "AWS_S3_REGION"
47 | )
48 |
49 | func doCheck(fileName string) error {
50 | registry, err := loadRegistryFromFile(fileName)
51 | if err != nil {
52 | return err
53 | }
54 | return registry.Validate()
55 | }
56 |
57 | func doUploadToS3(registryFilename, gitTag string) error {
58 | var s3prefix, s3bucket, s3region string
59 | var found bool
60 |
61 | if s3prefix, found = os.LookupEnv(S3PrefixEnv); !found {
62 | return fmt.Errorf("environment variable with key %q not found, please set it before running this tool", S3PrefixEnv)
63 | }
64 |
65 | if s3bucket, found = os.LookupEnv(S3BucketEnv); !found {
66 | return fmt.Errorf("environment variable with key %q not found, please set it before running this tool", S3BucketEnv)
67 | }
68 |
69 | if s3region, found = os.LookupEnv(S3RegionEnv); !found {
70 | return fmt.Errorf("environment variable with key %q not found, please set it before running this tool", S3RegionEnv)
71 | }
72 |
73 | s3s, err := session.NewSession(&aws.Config{Region: aws.String(s3region)})
74 | if err != nil {
75 | log.Fatal(err)
76 | }
77 |
78 | pt, err := parseGitTag(gitTag)
79 | if err != nil {
80 | return err
81 | }
82 |
83 | reg, err := loadRegistryFromFile(registryFilename)
84 | if err != nil {
85 | return fmt.Errorf("could not read registry from %s: %w", registryFilename, err)
86 | }
87 |
88 | rulesfileInfo := reg.RulesfileByName(pt.Name)
89 | if rulesfileInfo == nil {
90 | return fmt.Errorf("could not find rulesfile %s in the registry (reserved or archived are ignored)", pt.Name)
91 | }
92 |
93 | tmpDir, err := os.MkdirTemp("", "falco-artifacts-to-upload")
94 | if err != nil {
95 | log.Fatal(err)
96 | }
97 | defer os.RemoveAll(tmpDir)
98 |
99 | tgzFile := filepath.Join(tmpDir, filepath.Base(rulesfileInfo.Path)+".tar.gz")
100 | if err = tarGzSingleFile(tgzFile, rulesfileInfo.Path); err != nil {
101 | return fmt.Errorf("could not compress %s: %w", rulesfileInfo.Path, err)
102 | }
103 | defer os.RemoveAll(tgzFile)
104 |
105 | key := path.Join(s3prefix, gitTag+".tar.gz")
106 | if err = s3UploadFile(s3s, s3bucket, tgzFile, key); err != nil {
107 | return fmt.Errorf("could not upload %s to bucket %s, key %s: %w", tgzFile, s3bucket, key, err)
108 | }
109 |
110 | return nil
111 | }
112 |
113 | func doPushToOCI(registryFilename, gitTag string) (*string, error) {
114 | var ociRepoPrefix, repoGit, user, token string
115 | var found bool
116 |
117 | if token, found = os.LookupEnv(RegistryTokenEnv); !found {
118 | return nil, fmt.Errorf("environment variable with key %q not found, please set it before running this tool", RegistryTokenEnv)
119 | }
120 |
121 | if user, found = os.LookupEnv(RegistryUserEnv); !found {
122 | return nil, fmt.Errorf("environment variable with key %q not found, please set it before running this tool", RegistryUserEnv)
123 | }
124 |
125 | if ociRepoPrefix, found = os.LookupEnv(OCIRepoPrefixEnv); !found {
126 | return nil, fmt.Errorf("environment variable with key %q not found, please set it before running this tool", OCIRepoPrefixEnv)
127 | }
128 |
129 | if repoGit, found = os.LookupEnv(RepoGithubEnv); !found {
130 | return nil, fmt.Errorf("environment variable with key %q not found, please set it before running this tool", RepoGithubEnv)
131 | }
132 |
133 | pt, err := parseGitTag(gitTag)
134 | if err != nil {
135 | return nil, err
136 | }
137 |
138 | cred := &auth.Credential{
139 | Username: user,
140 | Password: token,
141 | }
142 |
143 | client := authn.NewClient(authn.WithCredentials(cred))
144 | ociRepoRef := fmt.Sprintf("%s/%s", ociRepoPrefix, pt.Name)
145 |
146 | reg, err := loadRegistryFromFile(registryFilename)
147 | if err != nil {
148 | return nil, fmt.Errorf("could not read registry from %s: %w", registryFilename, err)
149 | }
150 |
151 | rulesfileInfo := reg.RulesfileByName(pt.Name)
152 | if rulesfileInfo == nil {
153 | return nil, fmt.Errorf("could not find rulesfile %s in the registry (reserved or archived are ignored)", pt.Name)
154 | }
155 |
156 | // Create the repository object for the ref.
157 | var repo *repository.Repository
158 | if repo, err = repository.NewRepository(ociRepoRef, repository.WithClient(client)); err != nil {
159 | return nil, fmt.Errorf("unable to create repository for ref %q: %w", ociRepoRef, err)
160 | }
161 |
162 | existingTags, _ := repo.Tags(context.Background())
163 | // note that the call above can generate an error if the repository does not exist yet, so we can proceed
164 |
165 | tagsToUpdate := ociTagsToUpdate(pt.Version(), existingTags)
166 |
167 | tmpDir, err := os.MkdirTemp("", "falco-artifacts-to-upload")
168 | if err != nil {
169 | log.Fatal(err)
170 | }
171 | defer os.RemoveAll(tmpDir)
172 |
173 | tgzFile := filepath.Join(tmpDir, filepath.Base(rulesfileInfo.Path)+".tar.gz")
174 | if err = tarGzSingleFile(tgzFile, rulesfileInfo.Path); err != nil {
175 | return nil, fmt.Errorf("could not compress %s: %w", rulesfileInfo.Path, err)
176 | }
177 | defer os.RemoveAll(tgzFile)
178 |
179 | config, err := rulesfileConfig(rulesfileInfo.Name, pt.Version(), rulesfileInfo.Path)
180 | if err != nil {
181 | return nil, fmt.Errorf("could not generate configuration layer for rulesfiles %q: %w", rulesfileInfo.Path, err)
182 | }
183 |
184 | digest, err := pushCompressedRulesfile(client, tgzFile, ociRepoRef, repoGit, tagsToUpdate, config)
185 | if err != nil {
186 | return nil, fmt.Errorf("could not push %s to %s with source %s and tags %v: %w", tgzFile, ociRepoRef, repoGit, tagsToUpdate, err)
187 | }
188 |
189 | // ociRepoDigest is a string that looks like ghcr.io/falcosecurity/rules/falco-rules@sha256:123456...
190 | ociRepoDigest := fmt.Sprintf("%s@%s", ociRepoRef, *digest)
191 |
192 | return &ociRepoDigest, nil
193 | }
194 |
195 | func rulesOciRepos(registryEntries *Registry, ociRepoPrefix string) (map[string]string, error) {
196 | var user, token string
197 | var repo *repository.Repository
198 | var err error
199 | var foundUser, foundToken bool
200 | var cred *auth.Credential
201 |
202 | user, foundUser = os.LookupEnv(RegistryUserEnv)
203 | token, foundToken = os.LookupEnv(RegistryTokenEnv)
204 |
205 | if !foundUser && !foundToken {
206 | cred = &auth.EmptyCredential
207 | } else {
208 | cred = &auth.Credential{
209 | Username: user,
210 | Password: token,
211 | }
212 | }
213 | ociClient := authn.NewClient(authn.WithCredentials(cred))
214 | ociEntries := make(map[string]string)
215 |
216 | for _, entry := range registryEntries.Rulesfiles {
217 | ref := ociRepoPrefix + "/" + entry.Name
218 | if repo, err = repository.NewRepository(ref, repository.WithClient(ociClient)); err != nil {
219 | return nil, fmt.Errorf("unable to create repository for ref %q: %w", ref, err)
220 | }
221 |
222 | _, _, err = repo.FetchReference(context.Background(), ref+":latest")
223 | if err != nil && (errors.Is(err, errdef.ErrNotFound) || strings.Contains(err.Error(), "requested access to the resource is denied")) {
224 | continue
225 | }
226 |
227 | if err != nil {
228 | return nil, fmt.Errorf("unable to fetch reference for %q: %w", ref+":latest", err)
229 | }
230 |
231 | ociEntries[entry.Name] = ref
232 | }
233 |
234 | return ociEntries, nil
235 | }
236 |
237 | func doUpdateIndex(registryFile, indexFile string) error {
238 | var ociPrefix string
239 | var found bool
240 |
241 | if ociPrefix, found = os.LookupEnv(OCIRepoPrefixEnv); !found {
242 | return fmt.Errorf("environment variable with key %q not found, please set it before running this tool", OCIRepoPrefixEnv)
243 | }
244 |
245 | registryEntries, err := loadRegistryFromFile(registryFile)
246 | if err != nil {
247 | return err
248 | }
249 | ociEntries, err := rulesOciRepos(registryEntries, ociPrefix)
250 | if err != nil {
251 | return err
252 | }
253 | if err := registryEntries.Validate(); err != nil {
254 | return err
255 | }
256 |
257 | return upsertIndexFile(registryEntries, ociEntries, indexFile)
258 | }
259 |
260 | func main() {
261 | checkCmd := &cobra.Command{
262 | Use: "check ",
263 | Short: "Verify the correctness of a registry YAML file",
264 | Args: cobra.ExactArgs(1),
265 | DisableFlagsInUseLine: true,
266 | RunE: func(c *cobra.Command, args []string) error {
267 | return doCheck(args[0])
268 | },
269 | }
270 |
271 | updateIndexCmd := &cobra.Command{
272 | Use: "update-index ",
273 | Short: "Update an index file for artifacts distribution using registry data, authenticates to the registry if REGISTRY_USER, REGISTRY_TOKEN are set",
274 | Args: cobra.ExactArgs(2),
275 | DisableFlagsInUseLine: true,
276 | RunE: func(c *cobra.Command, args []string) error {
277 | return doUpdateIndex(args[0], args[1])
278 | },
279 | }
280 |
281 | pushToOCI := &cobra.Command{
282 | Use: "push-to-oci ",
283 | Short: "Push the rulesfile identified by the tag to the OCI repo identified by the env variable OCI_REPO_PREFIX, authenticating via REGISTRY_USER/REGISTRY_TOKEN and linking to the sources via GITHUB_REPO_URL",
284 | Args: cobra.ExactArgs(2),
285 | DisableFlagsInUseLine: true,
286 | RunE: func(c *cobra.Command, args []string) error {
287 | ociRepoDigest, err := doPushToOCI(args[0], args[1])
288 | if err != nil {
289 | return err
290 | }
291 | fmt.Println(*ociRepoDigest)
292 |
293 | return nil
294 | },
295 | }
296 |
297 | uploadToS3 := &cobra.Command{
298 | Use: "upload-to-s3 ",
299 | Short: "Upload the rulesfile identified by the tag to the S3 bucket S3_BUCKET with prefix S3_PREFIX",
300 | Args: cobra.ExactArgs(2),
301 | DisableFlagsInUseLine: true,
302 | RunE: func(c *cobra.Command, args []string) error {
303 | return doUploadToS3(args[0], args[1])
304 | },
305 | }
306 |
307 | rootCmd := &cobra.Command{
308 | Use: "rules-registry",
309 | Version: "0.1.0",
310 | }
311 | rootCmd.AddCommand(checkCmd)
312 | rootCmd.AddCommand(updateIndexCmd)
313 | rootCmd.AddCommand(pushToOCI)
314 | rootCmd.AddCommand(uploadToS3)
315 |
316 | if err := rootCmd.Execute(); err != nil {
317 | fmt.Printf("error: %s\n", err)
318 | os.Exit(1)
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/build/registry/config_layer.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "errors"
22 | "fmt"
23 |
24 | "github.com/falcosecurity/falcoctl/pkg/oci"
25 | )
26 |
27 | // rulesFileConfig generates the artifact configuration for a rulesfile given its path.
28 | func rulesfileConfig(name, version, filePath string) (*oci.ArtifactConfig, error) {
29 | cfg := &oci.ArtifactConfig{
30 | Name: name,
31 | Version: version,
32 | Dependencies: nil,
33 | Requirements: nil,
34 | }
35 |
36 | // Get the requirements for the given file.
37 | req, err := rulesfileRequirement(filePath)
38 | if err != nil && !errors.Is(err, ErrReqNotFound) {
39 | return nil, err
40 | }
41 | // If found add it to the requirements list.
42 | if err == nil {
43 | _ = cfg.SetRequirement(req.Name, req.Version)
44 | }
45 |
46 | if cfg.Requirements == nil {
47 | return nil, fmt.Errorf("no dependencies or requirements found for rulesfile %q", filePath)
48 | }
49 |
50 | return cfg, nil
51 | }
52 |
--------------------------------------------------------------------------------
/build/registry/go.mod:
--------------------------------------------------------------------------------
1 | module registry
2 |
3 | go 1.21
4 |
5 | toolchain go1.21.0
6 |
7 | require (
8 | github.com/aws/aws-sdk-go v1.44.288
9 | github.com/blang/semver v3.5.1+incompatible
10 | github.com/falcosecurity/falcoctl v0.6.1
11 | github.com/spf13/cobra v1.7.0
12 | github.com/stretchr/testify v1.8.4
13 | gopkg.in/yaml.v2 v2.4.0
14 | k8s.io/klog/v2 v2.100.1
15 | oras.land/oras-go/v2 v2.2.1
16 | )
17 |
18 | require (
19 | atomicgo.dev/cursor v0.2.0 // indirect
20 | atomicgo.dev/keyboard v0.2.9 // indirect
21 | atomicgo.dev/schedule v0.1.0 // indirect
22 | cloud.google.com/go/compute v1.23.0 // indirect
23 | cloud.google.com/go/compute/metadata v0.2.3 // indirect
24 | github.com/containerd/console v1.0.3 // indirect
25 | github.com/davecgh/go-spew v1.1.1 // indirect
26 | github.com/docker/cli v24.0.5+incompatible // indirect
27 | github.com/docker/docker v24.0.5+incompatible // indirect
28 | github.com/docker/docker-credential-helpers v0.8.0 // indirect
29 | github.com/fsnotify/fsnotify v1.6.0 // indirect
30 | github.com/go-logr/logr v1.2.4 // indirect
31 | github.com/golang/protobuf v1.5.3 // indirect
32 | github.com/gookit/color v1.5.4 // indirect
33 | github.com/hashicorp/hcl v1.0.0 // indirect
34 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
35 | github.com/jmespath/go-jmespath v0.4.0 // indirect
36 | github.com/lithammer/fuzzysearch v1.1.8 // indirect
37 | github.com/magiconair/properties v1.8.7 // indirect
38 | github.com/mattn/go-isatty v0.0.19 // indirect
39 | github.com/mattn/go-runewidth v0.0.15 // indirect
40 | github.com/mitchellh/mapstructure v1.5.0 // indirect
41 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
42 | github.com/opencontainers/go-digest v1.0.0 // indirect
43 | github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
44 | github.com/oras-project/oras-credentials-go v0.3.0 // indirect
45 | github.com/pelletier/go-toml/v2 v2.0.9 // indirect
46 | github.com/pkg/errors v0.9.1 // indirect
47 | github.com/pmezard/go-difflib v1.0.0 // indirect
48 | github.com/pterm/pterm v0.12.67 // indirect
49 | github.com/rivo/uniseg v0.4.4 // indirect
50 | github.com/sirupsen/logrus v1.9.3 // indirect
51 | github.com/spf13/afero v1.9.5 // indirect
52 | github.com/spf13/cast v1.5.1 // indirect
53 | github.com/spf13/jwalterweatherman v1.1.0 // indirect
54 | github.com/spf13/pflag v1.0.5 // indirect
55 | github.com/spf13/viper v1.16.0 // indirect
56 | github.com/subosito/gotenv v1.6.0 // indirect
57 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
58 | golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
59 | golang.org/x/net v0.14.0 // indirect
60 | golang.org/x/oauth2 v0.11.0 // indirect
61 | golang.org/x/sync v0.3.0 // indirect
62 | golang.org/x/sys v0.11.0 // indirect
63 | golang.org/x/term v0.11.0 // indirect
64 | golang.org/x/text v0.12.0 // indirect
65 | google.golang.org/appengine v1.6.7 // indirect
66 | google.golang.org/protobuf v1.31.0 // indirect
67 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
68 | gopkg.in/ini.v1 v1.67.0 // indirect
69 | gopkg.in/yaml.v3 v3.0.1 // indirect
70 | gotest.tools/v3 v3.5.0 // indirect
71 | )
72 |
--------------------------------------------------------------------------------
/build/registry/index.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "path/filepath"
22 | "strings"
23 |
24 | "github.com/falcosecurity/falcoctl/pkg/index/index"
25 | "github.com/falcosecurity/falcoctl/pkg/oci"
26 | )
27 |
28 | const (
29 | GHOrg = "falcosecurity"
30 | )
31 |
32 | func pluginRulesToIndexEntry(rf Rulesfile, registry, repo string) *index.Entry {
33 | return &index.Entry{
34 | Name: rf.Name,
35 | Type: string(oci.Rulesfile),
36 | Registry: registry,
37 | Repository: repo,
38 | Description: rf.Description,
39 | Home: rf.URL,
40 | Keywords: appendIfNotPresent(rf.Keywords, rf.Name),
41 | License: rf.License,
42 | Maintainers: rf.Maintainers,
43 | Sources: []string{rf.URL},
44 | Signature: rf.Signature,
45 | }
46 | }
47 |
48 | func upsertIndex(r *Registry, ociArtifacts map[string]string, i *index.Index) {
49 | for _, rf := range r.Rulesfiles {
50 | // We only publish falcosecurity artifacts that have been uploaded to the repo.
51 | ref, ociRulesFound := ociArtifacts[rf.Name]
52 |
53 | // Build registry and repo starting from the reference.
54 | tokens := strings.Split(ref, "/")
55 | ociRegistry := tokens[0]
56 | ociRepo := filepath.Join(tokens[1:]...)
57 | if ociRulesFound {
58 | i.Upsert(pluginRulesToIndexEntry(rf, ociRegistry, ociRepo))
59 | }
60 | }
61 | }
62 |
63 | func upsertIndexFile(r *Registry, ociArtifacts map[string]string, indexPath string) error {
64 | i := index.New(GHOrg)
65 |
66 | if err := i.Read(indexPath); err != nil {
67 | return err
68 | }
69 |
70 | upsertIndex(r, ociArtifacts, i)
71 |
72 | return i.Write(indexPath)
73 | }
74 |
75 | // Add new item to a slice if not present.
76 | func appendIfNotPresent(keywords []string, kw string) []string {
77 | // If the keyword already exist do nothing.
78 | for i := range keywords {
79 | if keywords[i] == kw {
80 | return keywords
81 | }
82 | }
83 |
84 | // Add the keyword
85 | return append(keywords, kw)
86 | }
87 |
--------------------------------------------------------------------------------
/build/registry/index_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "reflect"
22 | "testing"
23 |
24 | "github.com/falcosecurity/falcoctl/pkg/index/index"
25 | "github.com/stretchr/testify/assert"
26 | )
27 |
28 | func Test_upsertIndex(t *testing.T) {
29 | tests := []struct {
30 | name string
31 | registryPath string
32 | ociArtifacts map[string]string
33 | indexPath string
34 | expectedIndexPath string
35 | }{
36 | {
37 | "missing",
38 | "testdata/registry.yaml",
39 | map[string]string{"falco": "ghcr.io/falcosecurity/rules/falco"},
40 | "testdata/index1.yaml",
41 | "testdata/index_expected1.yaml",
42 | },
43 | {
44 | "already_present",
45 | "testdata/registry.yaml",
46 | map[string]string{"falco": "ghcr.io/falcosecurity/rules/falco"},
47 | "testdata/index2.yaml",
48 | "testdata/index2.yaml",
49 | },
50 | }
51 | for _, tt := range tests {
52 | t.Run(tt.name, func(t *testing.T) {
53 | t.Parallel()
54 |
55 | i := index.New(GHOrg)
56 | assert.NoError(t, i.Read(tt.indexPath))
57 | expectedIndex := index.New(GHOrg)
58 | assert.NoError(t, expectedIndex.Read(tt.expectedIndexPath))
59 |
60 | r, err := loadRegistryFromFile(tt.registryPath)
61 | assert.NoError(t, err)
62 |
63 | upsertIndex(r, tt.ociArtifacts, i)
64 |
65 | if !reflect.DeepEqual(i, expectedIndex) {
66 | t.Errorf("index() = %#v, want %v", i, expectedIndex)
67 | }
68 | })
69 | }
70 | }
71 |
72 | func TestPluginRulesToIndexEntrySignature(t *testing.T) {
73 | t.Parallel()
74 |
75 | signature := &index.Signature{
76 | Cosign: &index.CosignSignature{},
77 | }
78 |
79 | expected := signature
80 |
81 | p := Rulesfile{Signature: signature}
82 |
83 | entry := pluginRulesToIndexEntry(p, "", "")
84 | if !reflect.DeepEqual(entry.Signature, expected) {
85 | t.Fatalf("Index entry signature: expected %#v, got %v", expected, entry.Signature)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/build/registry/oci.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "context"
22 | "fmt"
23 |
24 | "k8s.io/klog/v2"
25 | "oras.land/oras-go/v2/registry/remote"
26 |
27 | "github.com/falcosecurity/falcoctl/pkg/oci"
28 | ocipusher "github.com/falcosecurity/falcoctl/pkg/oci/pusher"
29 | )
30 |
31 | // pushCompressedRulesfile publishes rulesfile as OCI artifact and returns its digest.
32 | // It possibly returns an error.
33 | func pushCompressedRulesfile(
34 | ociClient remote.Client,
35 | filePath, repoRef, repoGit string,
36 | tags []string,
37 | config *oci.ArtifactConfig) (*string, error) {
38 | klog.Infof("Processing compressed rulesfile %q for repo %q and tags %s...", filePath, repoRef, tags)
39 |
40 | pusher := ocipusher.NewPusher(ociClient, false, nil)
41 | artifact, err := pusher.Push(context.Background(), oci.Rulesfile, repoRef,
42 | ocipusher.WithTags(tags...),
43 | ocipusher.WithFilepaths([]string{filePath}),
44 | ocipusher.WithAnnotationSource(repoGit),
45 | ocipusher.WithArtifactConfig(*config))
46 |
47 | if err != nil {
48 | return nil, fmt.Errorf("an error occurred while pushing: %w", err)
49 | }
50 |
51 | return &artifact.Digest, nil
52 | }
53 |
--------------------------------------------------------------------------------
/build/registry/registry.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "fmt"
22 | "os"
23 | "regexp"
24 |
25 | "github.com/falcosecurity/falcoctl/pkg/index/index"
26 | "gopkg.in/yaml.v2"
27 | )
28 |
29 | var (
30 | rgxName = regexp.MustCompile(`^[a-z]+[a-z0-9-_]*$`)
31 | )
32 |
33 | type Rulesfile struct {
34 | Name string `yaml:"name"`
35 | Description string `yaml:"description"`
36 | Authors string `yaml:"authors"`
37 | Contact string `yaml:"contact"`
38 | Maintainers []struct {
39 | Email string `yaml:"email"`
40 | Name string `yaml:"name"`
41 | } `yaml:"maintainers"`
42 | Keywords []string `yaml:"keywords"`
43 | Path string `yaml:"path"`
44 | URL string `yaml:"url"`
45 | License string `yaml:"license"`
46 | Reserved bool `yaml:"reserved"`
47 | Archived bool `yaml:"archived"`
48 | Signature *index.Signature `yaml:"signature,omitempty"`
49 | }
50 |
51 | type Registry struct {
52 | Rulesfiles []Rulesfile `yaml:"rulesfiles"`
53 | }
54 |
55 | // Validate returns nil if the Registry is valid, and an error otherwise.
56 | func (r *Registry) Validate() error {
57 | names := make(map[string]bool)
58 | for _, p := range r.Rulesfiles {
59 | if !rgxName.MatchString(p.Name) {
60 | return fmt.Errorf("rulesfile name does follow the naming convention: '%s'", p.Name)
61 | }
62 | if _, ok := names[p.Name]; ok {
63 | return fmt.Errorf("rulesfile name is not unique: '%s'", p.Name)
64 | }
65 | names[p.Name] = true
66 | }
67 |
68 | return nil
69 | }
70 |
71 | // RulesfileByName returns the rulesfile in the index with the specified name, or nil if not found
72 | func (r *Registry) RulesfileByName(name string) *Rulesfile {
73 | for _, rf := range r.Rulesfiles {
74 | if rf.Reserved || rf.Archived {
75 | continue
76 | }
77 | if rf.Name == name {
78 | return &rf
79 | }
80 | }
81 | return nil
82 | }
83 |
84 | func loadRegistryFromFile(fname string) (*Registry, error) {
85 | yamlFile, err := os.ReadFile(fname)
86 | if err != nil {
87 | return nil, err
88 | }
89 |
90 | var registry Registry
91 |
92 | err = yaml.Unmarshal(yamlFile, ®istry)
93 | if err != nil {
94 | return nil, err
95 | }
96 |
97 | return ®istry, nil
98 | }
99 |
--------------------------------------------------------------------------------
/build/registry/requirements.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "bufio"
22 | "errors"
23 | "fmt"
24 | "os"
25 | "strconv"
26 | "strings"
27 |
28 | "github.com/blang/semver"
29 | "github.com/falcosecurity/falcoctl/pkg/oci"
30 | )
31 |
32 | const (
33 | rulesEngineAnchor = "- required_engine_version"
34 | engineVersionKey = "engine_version_semver"
35 | )
36 |
37 | // ErrReqNotFound error when the requirements are not found in the rulesfile.
38 | var ErrReqNotFound = errors.New("requirements not found")
39 |
40 | // rulesfileRequirement given a rulesfile in yaml format it scans it and extracts its requirements.
41 | func rulesfileRequirement(filePath string) (*oci.ArtifactRequirement, error) {
42 | var requirement string
43 | // Open the file.
44 | file, err := os.Open(filePath)
45 | if err != nil {
46 | return nil, fmt.Errorf("unable to open file %q: %v", filePath, file)
47 | }
48 |
49 | defer file.Close()
50 |
51 | // Prepare the file to be read line by line.
52 | fileScanner := bufio.NewScanner(file)
53 | fileScanner.Split(bufio.ScanLines)
54 |
55 | for fileScanner.Scan() {
56 | if strings.HasPrefix(fileScanner.Text(), rulesEngineAnchor) {
57 | requirement = fileScanner.Text()
58 | break
59 | }
60 | }
61 |
62 | if requirement == "" {
63 | return nil, fmt.Errorf("requirements for rulesfile %q: %w", filePath, ErrReqNotFound)
64 | }
65 |
66 | // Split the requirement and parse the version to semVer.
67 | tokens := strings.Split(fileScanner.Text(), ":")
68 | version := strings.TrimSpace(tokens[1])
69 | reqVer, err := semver.Parse(version)
70 | if err != nil {
71 | // If the version is not a valid semver, we try to parse it as a numeric value.
72 | minor, err := strconv.ParseUint(version, 10, 64)
73 | if err != nil {
74 | return nil, fmt.Errorf("unable to parse requirement %q: expected a numeric value or a valid semver string", version)
75 | }
76 | reqVer = semver.Version{
77 | Major: 0,
78 | Minor: minor,
79 | Patch: 0,
80 | }
81 | }
82 |
83 | return &oci.ArtifactRequirement{
84 | Name: engineVersionKey,
85 | Version: reqVer.String(),
86 | }, nil
87 | }
88 |
--------------------------------------------------------------------------------
/build/registry/requirements_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2024 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "testing"
22 |
23 | "github.com/stretchr/testify/assert"
24 | )
25 |
26 | func TestRulesFilesRequirement(t *testing.T) {
27 | req, err := rulesfileRequirement("testdata/rules-failed-req.yaml")
28 | assert.Error(t, err)
29 |
30 | req, err = rulesfileRequirement("testdata/rules-numeric-req.yaml")
31 | assert.NoError(t, err)
32 | assert.Equal(t, "0.15.0", req.Version)
33 | assert.Equal(t, "engine_version_semver", req.Name)
34 |
35 | req, err = rulesfileRequirement("testdata/rules-semver-req.yaml")
36 | assert.NoError(t, err)
37 | assert.Equal(t, "0.31.0", req.Version)
38 | assert.Equal(t, "engine_version_semver", req.Name)
39 | }
40 |
--------------------------------------------------------------------------------
/build/registry/s3.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "bytes"
22 | "os"
23 |
24 | "github.com/aws/aws-sdk-go/aws"
25 | "github.com/aws/aws-sdk-go/aws/session"
26 | "github.com/aws/aws-sdk-go/service/s3"
27 | )
28 |
29 | func s3UploadFile(session *session.Session, bucket string, filePath string, key string) error {
30 | upFile, err := os.Open(filePath)
31 | if err != nil {
32 | return err
33 | }
34 | defer upFile.Close()
35 |
36 | upFileInfo, _ := upFile.Stat()
37 | fileSize := upFileInfo.Size()
38 | fileBuffer := make([]byte, fileSize)
39 | upFile.Read(fileBuffer)
40 |
41 | _, err = s3.New(session).PutObject(&s3.PutObjectInput{
42 | Bucket: aws.String(bucket),
43 | Key: aws.String(key),
44 | Body: bytes.NewReader(fileBuffer),
45 | ContentLength: aws.Int64(fileSize),
46 | ContentDisposition: aws.String("attachment"),
47 | ServerSideEncryption: aws.String("AES256"),
48 | })
49 | return err
50 | }
51 |
--------------------------------------------------------------------------------
/build/registry/tag.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "fmt"
22 | "regexp"
23 |
24 | "github.com/blang/semver"
25 | )
26 |
27 | var (
28 | // see: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
29 | // note: we have a capturing group for the plugin name prefix, so that we can use
30 | // it to specify the right make release target
31 | versionRegexp = regexp.MustCompile(`^([a-z]+[a-z0-9_\-]*)-((0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))?)$`)
32 | )
33 |
34 | type rulesfileNameSemver struct {
35 | Name string
36 | Semver semver.Version
37 | }
38 |
39 | func (rn *rulesfileNameSemver) Version() string {
40 | return rn.Semver.String()
41 | }
42 |
43 | func parseGitTag(tag string) (*rulesfileNameSemver, error) {
44 | sm := versionRegexp.FindStringSubmatch(tag)
45 | if len(sm) == 0 {
46 | return nil, fmt.Errorf("tag %s could not be matched to a rulesfile name-version", tag)
47 | }
48 |
49 | rv := &rulesfileNameSemver{
50 | Name: sm[1],
51 | }
52 |
53 | sv, err := semver.Parse(sm[2])
54 | if err != nil {
55 | return nil, err
56 | }
57 |
58 | rv.Semver = sv
59 |
60 | return rv, nil
61 | }
62 |
63 | func isLatestSemver(newSemver semver.Version, existingSemvers []semver.Version) bool {
64 | for _, esv := range existingSemvers {
65 | if esv.GT(newSemver) {
66 | return false
67 | }
68 | }
69 |
70 | return true
71 | }
72 |
73 | func isLatestSemverForMinor(newSemver semver.Version, existingSemvers []semver.Version) bool {
74 | for _, esv := range existingSemvers {
75 | if esv.Minor == newSemver.Minor && esv.Major == newSemver.Major && esv.GT(newSemver) {
76 | return false
77 | }
78 | }
79 |
80 | return true
81 | }
82 |
83 | func isLatestSemverForMajor(newSemver semver.Version, existingSemvers []semver.Version) bool {
84 | for _, esv := range existingSemvers {
85 | if esv.Major == newSemver.Major && esv.GT(newSemver) {
86 | return false
87 | }
88 | }
89 |
90 | return true
91 | }
92 |
93 | // ociTagsToUpdate returns the MAJOR.MINOR tag to update if any, the latest tag if any and the new tag to update
94 | // in OCI registry given a new (already semver) tag and a list of existing tags in the OCI repo
95 | func ociTagsToUpdate(newTag string, existingTags []string) []string {
96 | newSemver := semver.MustParse(newTag)
97 | tagsToUpdate := []string{newSemver.String()}
98 |
99 | if len(newSemver.Pre) > 0 {
100 | // pre-release version, do not update anything else
101 | return tagsToUpdate
102 | }
103 |
104 | var existingFinalSemvers []semver.Version
105 | for _, tag := range existingTags {
106 | if sv, err := semver.Parse(tag); err == nil {
107 | // ignore prereleases
108 | if len(sv.Pre) == 0 {
109 | existingFinalSemvers = append(existingFinalSemvers, sv)
110 | }
111 | }
112 | }
113 |
114 | if isLatestSemverForMinor(newSemver, existingFinalSemvers) {
115 | tagsToUpdate = append(tagsToUpdate, fmt.Sprintf("%d.%d", newSemver.Major, newSemver.Minor))
116 | }
117 |
118 | if isLatestSemverForMajor(newSemver, existingFinalSemvers) {
119 | tagsToUpdate = append(tagsToUpdate, fmt.Sprintf("%d", newSemver.Major))
120 | }
121 |
122 | if isLatestSemver(newSemver, existingFinalSemvers) {
123 | tagsToUpdate = append(tagsToUpdate, "latest")
124 | }
125 |
126 | return tagsToUpdate
127 | }
128 |
--------------------------------------------------------------------------------
/build/registry/tag_test.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "reflect"
22 | "sort"
23 | "testing"
24 |
25 | "github.com/blang/semver"
26 | )
27 |
28 | func Test_parseTag(t *testing.T) {
29 | tests := []struct {
30 | name string
31 | tag string
32 | want rulesfileNameSemver
33 | wantErr bool
34 | }{
35 | {"rc", "k8saudit-0.4.0-rc1", rulesfileNameSemver{
36 | Name: "k8saudit",
37 | Semver: semver.MustParse("0.4.0-rc1"),
38 | }, false},
39 | {"eks", "k8saudit-extended-0.1.1", rulesfileNameSemver{
40 | Name: "k8saudit-extended",
41 | Semver: semver.MustParse("0.1.1"),
42 | }, false},
43 | {"underscore", "dummy_c-eks-1.2.3-rc4", rulesfileNameSemver{
44 | Name: "dummy_c-eks",
45 | Semver: semver.MustParse("1.2.3-rc4"),
46 | }, false},
47 | }
48 | for _, tt := range tests {
49 | t.Run(tt.name, func(t *testing.T) {
50 | got, err := parseGitTag(tt.tag)
51 | if (err != nil) != tt.wantErr {
52 | t.Errorf("parseGitTag() error = %v, wantErr %v", err, tt.wantErr)
53 | return
54 | }
55 | if !reflect.DeepEqual(*got, tt.want) {
56 | t.Errorf("parseGitTag() got = %v, want %v", got, tt.want)
57 | }
58 | })
59 | }
60 | }
61 |
62 | func Test_ociTagsToUpdate(t *testing.T) {
63 | tests := []struct {
64 | name string
65 | newTag string
66 | existingTags []string
67 | want []string
68 | }{
69 | {"latest", "0.3.2", []string{"0.1.1", "0.2.0", "0.3.1"}, []string{"0", "0.3.2", "0.3", "latest"}},
70 | {"latest_1", "1.0.0", []string{"0.1.1", "0.2.0", "0.3.1"}, []string{"1", "1.0.0", "1.0", "latest"}},
71 | {"older", "0.1.1", []string{"0.1.2", "0.2.0", "0.3.1"}, []string{"0.1.1"}},
72 | {"latest_in_line", "0.1.3", []string{"0.1.2", "0.2.0", "0.3.1"}, []string{"0.1.3", "0.1"}},
73 | {"version_1", "1.0.2", []string{"0.1.2", "0.2.0", "1.0.0", "2.0.0", "2.0.2"}, []string{"1", "1.0", "1.0.2"}},
74 | {"prerelease", "0.1.4-rc1", []string{"0.1.2", "0.1.3"}, []string{"0.1.4-rc1"}},
75 | {"latest_with_prerelease", "1.0.2", []string{"1.0.0", "1.0.1", "2.0.0-rc1"}, []string{"1", "1.0", "1.0.2", "latest"}},
76 | {"not_latest_with_prerelease", "1.0.2", []string{"1.0.0", "1.0.1", "2.0.0-rc1", "2.0.0"}, []string{"1", "1.0", "1.0.2"}},
77 | }
78 | for _, tt := range tests {
79 | t.Run(tt.name, func(t *testing.T) {
80 | actual := ociTagsToUpdate(tt.newTag, tt.existingTags)
81 | expected := tt.want
82 |
83 | sort.Strings(actual)
84 | sort.Strings(expected)
85 |
86 | if !reflect.DeepEqual(actual, expected) {
87 | t.Errorf("ociTagsToUpdate() = %v, want %v", actual, expected)
88 | }
89 | })
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/build/registry/targz.go:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Apache-2.0
2 | /*
3 | Copyright (C) 2023 The Falco Authors.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | */
17 |
18 | package main
19 |
20 | import (
21 | "archive/tar"
22 | "compress/gzip"
23 | "os"
24 | "path"
25 | )
26 |
27 | func tarGzSingleFile(outputPath string, fileName string) error {
28 | var file *os.File
29 | var err error
30 | var writer *gzip.Writer
31 |
32 | if file, err = os.OpenFile(outputPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644); err != nil {
33 | return err
34 | }
35 | defer file.Close()
36 |
37 | if writer, err = gzip.NewWriterLevel(file, gzip.DefaultCompression); err != nil {
38 | return err
39 | }
40 | defer writer.Close()
41 |
42 | tw := tar.NewWriter(writer)
43 | defer tw.Close()
44 |
45 | body, err := os.ReadFile(fileName)
46 | if err != nil {
47 | return err
48 | }
49 |
50 | hdr := &tar.Header{
51 | Name: path.Base(fileName),
52 | Mode: int64(0644),
53 | Size: int64(len(body)),
54 | }
55 | if err := tw.WriteHeader(hdr); err != nil {
56 | return err
57 | }
58 | if _, err := tw.Write(body); err != nil {
59 | return err
60 | }
61 |
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/build/registry/testdata/index1.yaml:
--------------------------------------------------------------------------------
1 | - name: cloudtrail
2 | type: plugin
3 | registry: ghcr.io
4 | repository: falcosecurity/plugins/plugin/cloudtrail
5 | description: Reads Cloudtrail JSON logs from files/S3 and injects as events
6 | home: https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail
7 | keywords:
8 | - audit
9 | - user-activity
10 | - api-usage
11 | - aws
12 | license: Apache-2.0
13 | maintainers:
14 | - email: cncf-falco-dev@lists.cncf.io
15 | name: The Falco Authors
16 | sources:
17 | - https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail
18 | - name: cloudtrail-rules
19 | type: rulesfile
20 | registry: ghcr.io
21 | repository: falcosecurity/plugins/ruleset/cloudtrail
22 | description: Reads Cloudtrail JSON logs from files/S3 and injects as events
23 | home: https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail
24 | keywords:
25 | - audit
26 | - user-activity
27 | - api-usage
28 | - aws
29 | - cloudtrail-rules
30 | license: Apache-2.0
31 | maintainers:
32 | - email: cncf-falco-dev@lists.cncf.io
33 | name: The Falco Authors
34 | sources:
35 | - https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail/rules
36 |
--------------------------------------------------------------------------------
/build/registry/testdata/index2.yaml:
--------------------------------------------------------------------------------
1 | - name: cloudtrail
2 | type: plugin
3 | registry: ghcr.io
4 | repository: falcosecurity/plugins/plugin/cloudtrail
5 | description: Reads Cloudtrail JSON logs from files/S3 and injects as events
6 | home: https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail
7 | keywords:
8 | - audit
9 | - user-activity
10 | - api-usage
11 | - aws
12 | license: Apache-2.0
13 | maintainers:
14 | - email: cncf-falco-dev@lists.cncf.io
15 | name: The Falco Authors
16 | sources:
17 | - https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail
18 | - name: cloudtrail-rules
19 | type: rulesfile
20 | registry: ghcr.io
21 | repository: falcosecurity/plugins/ruleset/cloudtrail
22 | description: Reads Cloudtrail JSON logs from files/S3 and injects as events
23 | home: https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail
24 | keywords:
25 | - audit
26 | - user-activity
27 | - api-usage
28 | - aws
29 | - cloudtrail-rules
30 | license: Apache-2.0
31 | maintainers:
32 | - email: cncf-falco-dev@lists.cncf.io
33 | name: The Falco Authors
34 | sources:
35 | - https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail/rules
36 | - name: falco
37 | type: rulesfile
38 | registry: ghcr.io
39 | repository: falcosecurity/rules/falco
40 | description: Falco rules that are loaded by default
41 | home: https://github.com/falcosecurity/rules/blob/main/rules/falco_rules.yaml
42 | keywords:
43 | - falco
44 | license: apache-2.0
45 | maintainers:
46 | - email: cncf-falco-dev@lists.cncf.io
47 | name: The Falco Authors
48 | sources:
49 | - https://github.com/falcosecurity/rules/blob/main/rules/falco_rules.yaml
50 | signature:
51 | cosign:
52 | certificate-oidc-issuer: https://token.actions.githubusercontent.com
53 | certificate-identity-regexp: https://github.com/falcosecurity/rules/
54 |
--------------------------------------------------------------------------------
/build/registry/testdata/index_expected1.yaml:
--------------------------------------------------------------------------------
1 | - name: cloudtrail
2 | type: plugin
3 | registry: ghcr.io
4 | repository: falcosecurity/plugins/plugin/cloudtrail
5 | description: Reads Cloudtrail JSON logs from files/S3 and injects as events
6 | home: https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail
7 | keywords:
8 | - audit
9 | - user-activity
10 | - api-usage
11 | - aws
12 | license: Apache-2.0
13 | maintainers:
14 | - email: cncf-falco-dev@lists.cncf.io
15 | name: The Falco Authors
16 | sources:
17 | - https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail
18 | - name: cloudtrail-rules
19 | type: rulesfile
20 | registry: ghcr.io
21 | repository: falcosecurity/plugins/ruleset/cloudtrail
22 | description: Reads Cloudtrail JSON logs from files/S3 and injects as events
23 | home: https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail
24 | keywords:
25 | - audit
26 | - user-activity
27 | - api-usage
28 | - aws
29 | - cloudtrail-rules
30 | license: Apache-2.0
31 | maintainers:
32 | - email: cncf-falco-dev@lists.cncf.io
33 | name: The Falco Authors
34 | sources:
35 | - https://github.com/falcosecurity/plugins/tree/master/plugins/cloudtrail/rules
36 | - name: falco
37 | type: rulesfile
38 | registry: ghcr.io
39 | repository: falcosecurity/rules/falco
40 | description: Falco rules that are loaded by default
41 | home: https://github.com/falcosecurity/rules/blob/main/rules/falco_rules.yaml
42 | keywords:
43 | - falco
44 | license: apache-2.0
45 | maintainers:
46 | - email: cncf-falco-dev@lists.cncf.io
47 | name: The Falco Authors
48 | sources:
49 | - https://github.com/falcosecurity/rules/blob/main/rules/falco_rules.yaml
50 | signature:
51 | cosign:
52 | certificate-oidc-issuer: https://token.actions.githubusercontent.com
53 | certificate-identity-regexp: https://github.com/falcosecurity/rules/
54 |
--------------------------------------------------------------------------------
/build/registry/testdata/registry.yaml:
--------------------------------------------------------------------------------
1 | rulesfiles:
2 | - name: falco
3 | description: Falco rules that are loaded by default
4 | authors: The Falco Authors
5 | contact: https://falco.org/community
6 | maintainers:
7 | - name: The Falco Authors
8 | email: cncf-falco-dev@lists.cncf.io
9 | path: rules/falco_rules.yaml
10 | license: apache-2.0
11 | keywords:
12 | - falco
13 | url: https://github.com/falcosecurity/rules/blob/main/rules/falco_rules.yaml
14 | signature:
15 | cosign:
16 | certificate-oidc-issuer: https://token.actions.githubusercontent.com
17 | certificate-identity-regexp: https://github.com/falcosecurity/rules/
18 | - name: applications
19 | description: Application rules
20 | authors: The Falco Authors
21 | contact: https://falco.org/community
22 | maintainers:
23 | - name: The Falco Authors
24 | email: cncf-falco-dev@lists.cncf.io
25 | path: rules/application_rules.yaml
26 | url: https://github.com/falcosecurity/rules/blob/main/rules/application_rules.yaml
27 | license: apache-2.0
28 | signature:
29 | cosign:
30 | certificate-oidc-issuer: https://token.actions.githubusercontent.com
31 | certificate-identity-regexp: https://github.com/falcosecurity/rules/
32 |
--------------------------------------------------------------------------------
/build/registry/testdata/rules-failed-req.yaml:
--------------------------------------------------------------------------------
1 | - required_engine_version: test
--------------------------------------------------------------------------------
/build/registry/testdata/rules-numeric-req.yaml:
--------------------------------------------------------------------------------
1 | - required_engine_version: 15
--------------------------------------------------------------------------------
/build/registry/testdata/rules-semver-req.yaml:
--------------------------------------------------------------------------------
1 | - required_engine_version: 0.31.0
--------------------------------------------------------------------------------
/docs/images/announce.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/docs/images/announce.png
--------------------------------------------------------------------------------
/docs/images/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/docs/images/arrow.png
--------------------------------------------------------------------------------
/docs/images/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/docs/images/cross.png
--------------------------------------------------------------------------------
/docs/images/insight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/docs/images/insight.png
--------------------------------------------------------------------------------
/docs/images/setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/docs/images/setting.png
--------------------------------------------------------------------------------
/docs/images/start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falcosecurity/rules/0116b8608ca7a1d44205044f077e4eccbe6487dc/docs/images/start.png
--------------------------------------------------------------------------------
/mkdocs.yml:
--------------------------------------------------------------------------------
1 | site_name: Falcosecurity Rules
2 | site_url: https://github.com/falcosecurity/rules
3 | nav:
4 | - Home: index.md
5 |
6 | theme: material
7 |
--------------------------------------------------------------------------------
/registry.yaml:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: Apache-2.0
2 | #
3 | # Copyright (C) 2023 The Falco Authors.
4 | #
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | rulesfiles:
20 | - name: falco-rules
21 | description: Falco rules that are loaded by default
22 | authors: The Falco Authors
23 | contact: https://falco.org/community
24 | maintainers:
25 | - name: The Falco Authors
26 | email: cncf-falco-dev@lists.cncf.io
27 | path: rules/falco_rules.yaml
28 | license: apache-2.0
29 | url: https://github.com/falcosecurity/rules/blob/main/rules/falco_rules.yaml
30 | signature:
31 | cosign:
32 | certificate-oidc-issuer: https://token.actions.githubusercontent.com
33 | certificate-identity-regexp: https://github.com/falcosecurity/rules/
34 | - name: falco-incubating-rules
35 | description: Falco incubating rules
36 | authors: The Falco Authors
37 | contact: https://falco.org/community
38 | maintainers:
39 | - name: The Falco Authors
40 | email: cncf-falco-dev@lists.cncf.io
41 | path: rules/falco-incubating_rules.yaml
42 | license: apache-2.0
43 | url: https://github.com/falcosecurity/rules/blob/main/rules/falco-incubating_rules.yaml
44 | signature:
45 | cosign:
46 | certificate-oidc-issuer: https://token.actions.githubusercontent.com
47 | certificate-identity-regexp: https://github.com/falcosecurity/rules/
48 | - name: falco-sandbox-rules
49 | description: Falco sandbox rules
50 | authors: The Falco Authors
51 | contact: https://falco.org/community
52 | maintainers:
53 | - name: The Falco Authors
54 | email: cncf-falco-dev@lists.cncf.io
55 | path: rules/falco-sandbox_rules.yaml
56 | license: apache-2.0
57 | url: https://github.com/falcosecurity/rules/blob/main/rules/falco-sandbox_rules.yaml
58 | signature:
59 | cosign:
60 | certificate-oidc-issuer: https://token.actions.githubusercontent.com
61 | certificate-identity-regexp: https://github.com/falcosecurity/rules/
62 | - name: falco-deprecated-rules
63 | description: Falco deprecated rules kept as examples but are no longer maintained
64 | authors: The Falco Authors
65 | contact: https://falco.org/community
66 | maintainers:
67 | - name: The Falco Authors
68 | email: cncf-falco-dev@lists.cncf.io
69 | path: rules/falco-deprecated_rules.yaml
70 | license: apache-2.0
71 | url: https://github.com/falcosecurity/rules/blob/main/rules/falco-deprecated_rules.yaml
72 | - name: application-rules
73 | archived: true
74 | description: This rules files has been archived and is no longer maintained
75 | authors: The Falco Authors
76 | contact: https://falco.org/community
77 | maintainers:
78 | - name: The Falco Authors
79 | email: cncf-falco-dev@lists.cncf.io
80 | path: archive/application_rules.yaml
81 | url: https://github.com/falcosecurity/rules/blob/main/archive/application_rules.yaml
82 | license: apache-2.0
83 |
--------------------------------------------------------------------------------
/rules/OWNERS:
--------------------------------------------------------------------------------
1 | approvers:
2 | - darryk10
3 | - loresuso
4 |
--------------------------------------------------------------------------------