Compare commits

..

12 Commits

Author SHA1 Message Date
Doug Burks
484aa7b207 Merge pull request #8336 from Security-Onion-Solutions/hotfix/2.3.140
Hotfix/2.3.140
2022-07-19 16:13:47 -04:00
Mike Reeves
6986448239 Merge pull request #8333 from Security-Onion-Solutions/2.3.140hotfix
2.3.140 Hotfix
2022-07-19 14:47:50 -04:00
Mike Reeves
dd48d66c1c 2.3.140 Hotfix 2022-07-19 14:39:44 -04:00
Mike Reeves
440f4e75c1 Merge pull request #8332 from Security-Onion-Solutions/dev
Merge Hotfix
2022-07-19 13:30:20 -04:00
weslambert
c795a70e9c Merge pull request #8329 from Security-Onion-Solutions/fix/elastalert_stop_check_enabled
Check to ensure Elastalert is enabled and suppress missing container error output
2022-07-19 13:27:35 -04:00
weslambert
340dbe8547 Check to see if Elastalert is enabled before trying to run 'so-elastalert-stop'. Also suppress error output for when so-elastalert container is not present. 2022-07-19 13:25:09 -04:00
Mike Reeves
52a5e743e9 Merge pull request #8327 from Security-Onion-Solutions/TOoSmOotH-patch-1
Update HOTFIX
2022-07-19 11:17:13 -04:00
Wes Lambert
5ceff52796 Move Elastalert indices check to function and call from beginning of soup and during pre-upgrade to 2.3.140 2022-07-19 14:54:39 +00:00
Wes Lambert
f3a0ab0b2d Perform Elastalert index check twice 2022-07-19 14:48:19 +00:00
Wes Lambert
4a7c994b66 Revise Elastalert index check deletion logic 2022-07-19 14:31:45 +00:00
Mike Reeves
07b8785f3d Update soup 2022-07-19 10:23:10 -04:00
Mike Reeves
9a1092ab01 Update HOTFIX 2022-07-19 10:21:36 -04:00
2407 changed files with 482512 additions and 346362 deletions

View File

@@ -536,11 +536,11 @@ secretGroup = 4
[allowlist] [allowlist]
description = "global allow lists" description = "global allow lists"
regexes = ['''219-09-9999''', '''078-05-1120''', '''(9[0-9]{2}|666)-\d{2}-\d{4}''', '''RPM-GPG-KEY.*''', '''.*:.*StrelkaHexDump.*''', '''.*:.*PLACEHOLDER.*''', '''ssl_.*password''', '''integration_key\s=\s"so-logs-"'''] regexes = ['''219-09-9999''', '''078-05-1120''', '''(9[0-9]{2}|666)-\d{2}-\d{4}''']
paths = [ paths = [
'''gitleaks.toml''', '''gitleaks.toml''',
'''(.*?)(jpg|gif|doc|pdf|bin|svg|socket)$''', '''(.*?)(jpg|gif|doc|pdf|bin|svg|socket)$''',
'''(go.mod|go.sum)$''', '''(go.mod|go.sum)$''',
'''salt/nginx/files/enterprise-attack.json''',
'''(.*?)whl$''' '''salt/nginx/files/enterprise-attack.json'''
] ]

View File

@@ -1,202 +0,0 @@
body:
- type: markdown
attributes:
value: |
⚠️ This category is solely for conversations related to Security Onion 2.4 ⚠️
If your organization needs more immediate, enterprise grade professional support, with one-on-one virtual meetings and screensharing, contact us via our website: https://securityonion.com/support
- type: dropdown
attributes:
label: Version
description: Which version of Security Onion 2.4.x are you asking about?
options:
-
- 2.4.10
- 2.4.20
- 2.4.30
- 2.4.40
- 2.4.50
- 2.4.60
- 2.4.70
- 2.4.80
- 2.4.90
- 2.4.100
- 2.4.110
- 2.4.111
- 2.4.120
- 2.4.130
- 2.4.140
- 2.4.141
- 2.4.150
- 2.4.160
- 2.4.170
- 2.4.180
- 2.4.190
- 2.4.200
- Other (please provide detail below)
validations:
required: true
- type: dropdown
attributes:
label: Installation Method
description: How did you install Security Onion?
options:
-
- Security Onion ISO image
- Cloud image (Amazon, Azure, Google)
- Network installation on Red Hat derivative like Oracle, Rocky, Alma, etc. (unsupported)
- Network installation on Ubuntu (unsupported)
- Network installation on Debian (unsupported)
- Other (please provide detail below)
validations:
required: true
- type: dropdown
attributes:
label: Description
description: >
Is this discussion about installation, configuration, upgrading, or other?
options:
-
- installation
- configuration
- upgrading
- other (please provide detail below)
validations:
required: true
- type: dropdown
attributes:
label: Installation Type
description: >
When you installed, did you choose Import, Eval, Standalone, Distributed, or something else?
options:
-
- Import
- Eval
- Standalone
- Distributed
- other (please provide detail below)
validations:
required: true
- type: dropdown
attributes:
label: Location
description: >
Is this deployment in the cloud, on-prem with Internet access, or airgap?
options:
-
- cloud
- on-prem with Internet access
- airgap
- other (please provide detail below)
validations:
required: true
- type: dropdown
attributes:
label: Hardware Specs
description: >
Does your hardware meet or exceed the minimum requirements for your installation type as shown at https://docs.securityonion.net/en/2.4/hardware.html?
options:
-
- Meets minimum requirements
- Exceeds minimum requirements
- Does not meet minimum requirements
- other (please provide detail below)
validations:
required: true
- type: input
attributes:
label: CPU
description: How many CPU cores do you have?
validations:
required: true
- type: input
attributes:
label: RAM
description: How much RAM do you have?
validations:
required: true
- type: input
attributes:
label: Storage for /
description: How much storage do you have for the / partition?
validations:
required: true
- type: input
attributes:
label: Storage for /nsm
description: How much storage do you have for the /nsm partition?
validations:
required: true
- type: dropdown
attributes:
label: Network Traffic Collection
description: >
Are you collecting network traffic from a tap or span port?
options:
-
- tap
- span port
- other (please provide detail below)
validations:
required: true
- type: dropdown
attributes:
label: Network Traffic Speeds
description: >
How much network traffic are you monitoring?
options:
-
- Less than 1Gbps
- 1Gbps to 10Gbps
- more than 10Gbps
validations:
required: true
- type: dropdown
attributes:
label: Status
description: >
Does SOC Grid show all services on all nodes as running OK?
options:
-
- Yes, all services on all nodes are running OK
- No, one or more services are failed (please provide detail below)
validations:
required: true
- type: dropdown
attributes:
label: Salt Status
description: >
Do you get any failures when you run "sudo salt-call state.highstate"?
options:
-
- Yes, there are salt failures (please provide detail below)
- No, there are no failures
validations:
required: true
- type: dropdown
attributes:
label: Logs
description: >
Are there any additional clues in /opt/so/log/?
options:
-
- Yes, there are additional clues in /opt/so/log/ (please provide detail below)
- No, there are no additional clues
validations:
required: true
- type: textarea
attributes:
label: Detail
description: Please read our discussion guidelines at https://github.com/Security-Onion-Solutions/securityonion/discussions/1720 and then provide detailed information to help us help you.
placeholder: |-
STOP! Before typing, please read our discussion guidelines at https://github.com/Security-Onion-Solutions/securityonion/discussions/1720 in their entirety!
If your organization needs more immediate, enterprise grade professional support, with one-on-one virtual meetings and screensharing, contact us via our website: https://securityonion.com/support
validations:
required: true
- type: checkboxes
attributes:
label: Guidelines
options:
- label: I have read the discussion guidelines at https://github.com/Security-Onion-Solutions/securityonion/discussions/1720 and assert that I have followed the guidelines.
required: true

12
.github/ISSUE_TEMPLATE vendored Normal file
View File

@@ -0,0 +1,12 @@
PLEASE STOP AND READ THIS INFORMATION!
If you are creating an issue just to ask a question, you will likely get faster and better responses by posting to our discussions forum instead:
https://securityonion.net/discuss
If you think you have found a possible bug or are observing a behavior that you weren't expecting, use the discussion forum to start a conversation about it instead of creating an issue.
If you are very familiar with the latest version of the product and are confident you have found a bug in Security Onion, you can continue with creating an issue here, but please make sure you have done the following:
- duplicated the issue on a fresh installation of the latest version
- provide information about your system and how you installed Security Onion
- include relevant log files
- include reproduction steps

View File

@@ -1,38 +0,0 @@
---
name: Bug report
about: This option is for experienced community members to report a confirmed, reproducible bug
title: ''
labels: ''
assignees: ''
---
PLEASE STOP AND READ THIS INFORMATION!
If you are creating an issue just to ask a question, you will likely get faster and better responses by posting to our discussions forum at https://securityonion.net/discuss.
If you think you have found a possible bug or are observing a behavior that you weren't expecting, use the discussion forum at https://securityonion.net/discuss to start a conversation about it instead of creating an issue.
If you are very familiar with the latest version of the product and are confident you have found a bug in Security Onion, you can continue with creating an issue here, but please make sure you have done the following:
- duplicated the issue on a fresh installation of the latest version
- provide information about your system and how you installed Security Onion
- include relevant log files
- include reproduction steps
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Security Onion Discussions
url: https://securityonion.com/discussions
about: Please ask and answer questions here

View File

@@ -1,33 +0,0 @@
name: 'Close Threads'
on:
schedule:
- cron: '50 1 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
discussions: write
concurrency:
group: lock-threads
jobs:
close-threads:
if: github.repository_owner == 'security-onion-solutions'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
days-before-issue-stale: -1
days-before-issue-close: 60
stale-issue-message: "This issue is stale because it has been inactive for an extended period. Stale issues convey that the issue, while important to someone, is not critical enough for the author, or other community members to work on, sponsor, or otherwise shepherd the issue through to a resolution."
close-issue-message: "This issue was closed because it has been stale for an extended period. It will be automatically locked in 30 days, after which no further commenting will be available."
days-before-pr-stale: 45
days-before-pr-close: 60
stale-pr-message: "This PR is stale because it has been inactive for an extended period. The longer a PR remains stale the more out of date with the main branch it becomes."
close-pr-message: "This PR was closed because it has been stale for an extended period. It will be automatically locked in 30 days. If there is still a commitment to finishing this PR re-open it before it is locked."

View File

@@ -11,14 +11,14 @@ jobs:
steps: steps:
- name: "Contributor Check" - name: "Contributor Check"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
uses: cla-assistant/github-action@v2.3.1 uses: cla-assistant/github-action@v2.1.3-beta
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }} PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
with: with:
path-to-signatures: 'signatures_v1.json' path-to-signatures: 'signatures_v1.json'
path-to-document: 'https://securityonionsolutions.com/cla' path-to-document: 'https://securityonionsolutions.com/cla'
allowlist: dependabot[bot],jertel,dougburks,TOoSmOotH,defensivedepth,m0duspwnens allowlist: dependabot[bot],jertel,dougburks,TOoSmOotH,weslambert,defensivedepth,m0duspwnens
remote-organization-name: Security-Onion-Solutions remote-organization-name: Security-Onion-Solutions
remote-repository-name: licensing remote-repository-name: licensing

View File

@@ -1,26 +0,0 @@
name: 'Lock Threads'
on:
schedule:
- cron: '50 2 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
discussions: write
concurrency:
group: lock-threads
jobs:
lock-threads:
if: github.repository_owner == 'security-onion-solutions'
runs-on: ubuntu-latest
steps:
- uses: jertel/lock-threads@main
with:
include-discussion-currently-open: true
discussion-inactive-days: 90
issue-inactive-days: 30
pr-inactive-days: 30

View File

@@ -1,10 +1,6 @@
name: python-test name: python-test
on: on: [push, pull_request]
pull_request:
paths:
- "salt/sensoroni/files/analyzers/**"
- "salt/manager/tools/sbin/**"
jobs: jobs:
build: build:
@@ -13,8 +9,8 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["3.13"] python-version: ["3.10"]
python-code-path: ["salt/sensoroni/files/analyzers", "salt/manager/tools/sbin"] python-code-path: ["salt/sensoroni/files/analyzers"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -32,4 +28,4 @@ jobs:
flake8 ${{ matrix.python-code-path }} --show-source --max-complexity=12 --doctests --max-line-length=200 --statistics flake8 ${{ matrix.python-code-path }} --show-source --max-complexity=12 --doctests --max-line-length=200 --statistics
- name: Test with pytest - name: Test with pytest
run: | run: |
PYTHONPATH=${{ matrix.python-code-path }} pytest ${{ matrix.python-code-path }} --cov=${{ matrix.python-code-path }} --doctest-modules --cov-report=term --cov-fail-under=100 --cov-config=pytest.ini pytest ${{ matrix.python-code-path }} --cov=${{ matrix.python-code-path }} --doctest-modules --cov-report=term --cov-fail-under=100 --cov-config=${{ matrix.python-code-path }}/pytest.ini

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
# Created by https://www.gitignore.io/api/macos,windows # Created by https://www.gitignore.io/api/macos,windows
# Edit at https://www.gitignore.io/?templates=macos,windows # Edit at https://www.gitignore.io/?templates=macos,windows

View File

@@ -1,53 +0,0 @@
### 2.4.190-20251024 ISO image released on 2025/10/24
### Download and Verify
2.4.190-20251024 ISO image:
https://download.securityonion.net/file/securityonion/securityonion-2.4.190-20251024.iso
MD5: 25358481FB876226499C011FC0710358
SHA1: 0B26173C0CE136F2CA40A15046D1DFB78BCA1165
SHA256: 4FD9F62EDA672408828B3C0C446FE5EA9FF3C4EE8488A7AB1101544A3C487872
Signature for ISO image:
https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.190-20251024.iso.sig
Signing key:
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/2.4/main/KEYS
For example, here are the steps you can use on most Linux distributions to download and verify our Security Onion ISO image.
Download and import the signing key:
```
wget https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/2.4/main/KEYS -O - | gpg --import -
```
Download the signature file for the ISO:
```
wget https://github.com/Security-Onion-Solutions/securityonion/raw/2.4/main/sigs/securityonion-2.4.190-20251024.iso.sig
```
Download the ISO image:
```
wget https://download.securityonion.net/file/securityonion/securityonion-2.4.190-20251024.iso
```
Verify the downloaded ISO image using the signature file:
```
gpg --verify securityonion-2.4.190-20251024.iso.sig securityonion-2.4.190-20251024.iso
```
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
```
gpg: Signature made Thu 23 Oct 2025 07:21:46 AM EDT using RSA key ID FE507013
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: C804 A93D 36BE 0C73 3EA1 9644 7C10 60B7 FE50 7013
```
If it fails to verify, try downloading again. If it still fails to verify, try downloading from another computer or another network.
Once you've verified the ISO image, you're ready to proceed to our Installation guide:
https://docs.securityonion.net/en/2.4/installation.html

1
HOTFIX
View File

@@ -0,0 +1 @@
20220719

53
LICENSE
View File

@@ -1,53 +0,0 @@
Elastic License 2.0 (ELv2)
Acceptance
By using the software, you agree to all of the terms and conditions below.
Copyright License
The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below.
Limitations
You may not provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key.
You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensors trademarks is subject to applicable law.
Patents
The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
Notices
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms.
If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software.
No Other Rights
These terms do not imply any licenses other than those expressly granted in these terms.
Termination
If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently.
No Liability
As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.
Definitions
The licensor is the entity offering these terms, and the software is the software the licensor makes available under these terms, including any portion of it.
you refers to the individual or entity agreeing to these terms.
your company is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
your licenses are all the licenses granted to you for the software under these terms.
use means anything you do with the software requiring one of your licenses.
trademark means trademarks, service marks, and similar rights.

View File

@@ -1,50 +1,41 @@
## Security Onion 2.4 ## Security Onion 2.3.140
Security Onion 2.4 is here! Security Onion 2.3.140 is here!
## Screenshots ## Screenshots
Alerts Alerts
![Alerts](https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion-docs/2.4/images/50_alerts.png) ![Alerts](./assets/images/screenshots/alerts.png)
Dashboards Dashboards
![Dashboards](https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion-docs/2.4/images/53_dashboards.png) ![Dashboards](./assets/images/screenshots/dashboards.png)
Hunt Hunt
![Hunt](https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion-docs/2.4/images/56_hunt.png) ![Hunt](./assets/images/screenshots/hunt.png)
Detections Cases
![Detections](https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion-docs/2.4/images/57_detections.png) ![Cases](./assets/images/screenshots/cases-comments.png)
PCAP
![PCAP](https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion-docs/2.4/images/62_pcap.png)
Grid
![Grid](https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion-docs/2.4/images/75_grid.png)
Config
![Config](https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion-docs/2.4/images/87_config.png)
### Release Notes ### Release Notes
https://docs.securityonion.net/en/2.4/release-notes.html https://docs.securityonion.net/en/2.3/release-notes.html
### Requirements ### Requirements
https://docs.securityonion.net/en/2.4/hardware.html https://docs.securityonion.net/en/2.3/hardware.html
### Download ### Download
https://docs.securityonion.net/en/2.4/download.html https://docs.securityonion.net/en/2.3/download.html
### Installation ### Installation
https://docs.securityonion.net/en/2.4/installation.html https://docs.securityonion.net/en/2.3/installation.html
### FAQ ### FAQ
https://docs.securityonion.net/en/2.4/faq.html https://docs.securityonion.net/en/2.3/faq.html
### Feedback ### Feedback
https://docs.securityonion.net/en/2.4/community-support.html https://docs.securityonion.net/en/2.3/community-support.html

View File

@@ -4,12 +4,9 @@
| Version | Supported | | Version | Supported |
| ------- | ------------------ | | ------- | ------------------ |
| 2.4.x | :white_check_mark: | | 2.x.x | :white_check_mark: |
| 2.3.x | :x: |
| 16.04.x | :x: | | 16.04.x | :x: |
Security Onion 2.3 has reached End Of Life and is no longer supported.
Security Onion 16.04 has reached End Of Life and is no longer supported. Security Onion 16.04 has reached End Of Life and is no longer supported.
## Reporting a Vulnerability ## Reporting a Vulnerability

52
VERIFY_ISO.md Normal file
View File

@@ -0,0 +1,52 @@
### 2.3.140-20220719 ISO image built on 2022/07/19
### Download and Verify
2.3.140-20220719 ISO image:
https://download.securityonion.net/file/securityonion/securityonion-2.3.140-20220719.iso
MD5: 68768DF9861B93BB8CC9637C80239803
SHA1: F15421C045227B334C7044E5F7F309A2BC7AEB19
SHA256: 4736E3E80E28EFBAB1923C121A3F78DBDBCBBBF65D715924A88B2E96EB3C6093
Signature for ISO image:
https://github.com/Security-Onion-Solutions/securityonion/raw/master/sigs/securityonion-2.3.140-20220719.iso.sig
Signing key:
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/master/KEYS
For example, here are the steps you can use on most Linux distributions to download and verify our Security Onion ISO image.
Download and import the signing key:
```
wget https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/master/KEYS -O - | gpg --import -
```
Download the signature file for the ISO:
```
wget https://github.com/Security-Onion-Solutions/securityonion/raw/master/sigs/securityonion-2.3.140-20220719.iso.sig
```
Download the ISO image:
```
wget https://download.securityonion.net/file/securityonion/securityonion-2.3.140-20220719.iso
```
Verify the downloaded ISO image using the signature file:
```
gpg --verify securityonion-2.3.140-20220719.iso.sig securityonion-2.3.140-20220719.iso
```
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
```
gpg: Signature made Tue 19 Jul 2022 02:00:29 PM EDT using RSA key ID FE507013
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: C804 A93D 36BE 0C73 3EA1 9644 7C10 60B7 FE50 7013
```
Once you've verified the ISO image, you're ready to proceed to our Installation guide:
https://docs.securityonion.net/en/2.3/installation.html

View File

@@ -1 +1 @@
2.4.200 2.3.140

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,8 +1,8 @@
{% import_yaml 'firewall/ports/ports.yaml' as default_portgroups %} {% import_yaml 'firewall/portgroups.yaml' as default_portgroups %}
{% set default_portgroups = default_portgroups.firewall.ports %} {% set default_portgroups = default_portgroups.firewall.aliases.ports %}
{% import_yaml 'firewall/ports/ports.local.yaml' as local_portgroups %} {% import_yaml 'firewall/portgroups.local.yaml' as local_portgroups %}
{% if local_portgroups.firewall.ports %} {% if local_portgroups.firewall.aliases.ports %}
{% set local_portgroups = local_portgroups.firewall.ports %} {% set local_portgroups = local_portgroups.firewall.aliases.ports %}
{% else %} {% else %}
{% set local_portgroups = {} %} {% set local_portgroups = {} %}
{% endif %} {% endif %}
@@ -12,6 +12,7 @@ role:
eval: eval:
fleet: fleet:
heavynode: heavynode:
helixsensor:
idh: idh:
import: import:
manager: manager:

View File

@@ -0,0 +1,82 @@
firewall:
hostgroups:
analyst:
ips:
delete:
insert:
beats_endpoint:
ips:
delete:
insert:
beats_endpoint_ssl:
ips:
delete:
insert:
elasticsearch_rest:
ips:
delete:
insert:
endgame:
ips:
delete:
insert:
fleet:
ips:
delete:
insert:
heavy_node:
ips:
delete:
insert:
idh:
ips:
delete:
insert:
manager:
ips:
delete:
insert:
minion:
ips:
delete:
insert:
node:
ips:
delete:
insert:
osquery_endpoint:
ips:
delete:
insert:
receiver:
ips:
delete:
insert:
search_node:
ips:
delete:
insert:
sensor:
ips:
delete:
insert:
strelka_frontend:
ips:
delete:
insert:
syslog:
ips:
delete:
insert:
wazuh_agent:
ips:
delete:
insert:
wazuh_api:
ips:
delete:
insert:
wazuh_authd:
ips:
delete:
insert:

View File

@@ -0,0 +1,3 @@
firewall:
aliases:
ports:

View File

@@ -1,2 +0,0 @@
firewall:
ports:

View File

@@ -41,8 +41,7 @@ file_roots:
base: base:
- /opt/so/saltstack/local/salt - /opt/so/saltstack/local/salt
- /opt/so/saltstack/default/salt - /opt/so/saltstack/default/salt
- /nsm/elastic-fleet/artifacts
- /opt/so/rules/nids
# The master_roots setting configures a master-only copy of the file_roots dictionary, # The master_roots setting configures a master-only copy of the file_roots dictionary,
# used by the state compiler. # used by the state compiler.
@@ -65,4 +64,10 @@ peer:
.*: .*:
- x509.sign_remote_certificate - x509.sign_remote_certificate
reactor:
- 'so/fleet':
- salt://reactor/fleet.sls
- 'salt/beacon/*/watch_sqlite_db//opt/so/conf/kratos/db/sqlite.db':
- salt://reactor/kratos.sls

View File

@@ -45,10 +45,12 @@ echo " rootfs: $ROOTFS" >> $local_salt_dir/pillar/data/$TYPE.sls
echo " nsmfs: $NSM" >> $local_salt_dir/pillar/data/$TYPE.sls echo " nsmfs: $NSM" >> $local_salt_dir/pillar/data/$TYPE.sls
if [ $TYPE == 'sensorstab' ]; then if [ $TYPE == 'sensorstab' ]; then
echo " monint: bond0" >> $local_salt_dir/pillar/data/$TYPE.sls echo " monint: bond0" >> $local_salt_dir/pillar/data/$TYPE.sls
salt-call state.apply grafana queue=True
fi fi
if [ $TYPE == 'evaltab' ] || [ $TYPE == 'standalonetab' ]; then if [ $TYPE == 'evaltab' ] || [ $TYPE == 'standalonetab' ]; then
echo " monint: bond0" >> $local_salt_dir/pillar/data/$TYPE.sls echo " monint: bond0" >> $local_salt_dir/pillar/data/$TYPE.sls
if [ ! $10 ]; then if [ ! $10 ]; then
salt-call state.apply grafana queue=True
salt-call state.apply utility queue=True salt-call state.apply utility queue=True
fi fi
fi fi

View File

@@ -1,34 +0,0 @@
{% set node_types = {} %}
{% for minionid, ip in salt.saltutil.runner(
'mine.get',
tgt='elasticsearch:enabled:true',
fun='network.ip_addrs',
tgt_type='pillar') | dictsort()
%}
# only add a node to the pillar if it returned an ip from the mine
{% if ip | length > 0%}
{% set hostname = minionid.split('_') | first %}
{% set node_type = minionid.split('_') | last %}
{% if node_type not in node_types.keys() %}
{% do node_types.update({node_type: {hostname: ip[0]}}) %}
{% else %}
{% if hostname not in node_types[node_type] %}
{% do node_types[node_type].update({hostname: ip[0]}) %}
{% else %}
{% do node_types[node_type][hostname].update(ip[0]) %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
elasticsearch:
nodes:
{% for node_type, values in node_types.items() %}
{{node_type}}:
{% for hostname, ip in values.items() %}
{{hostname}}:
ip: {{ip}}
{% endfor %}
{% endfor %}

View File

@@ -1,34 +0,0 @@
{% set node_types = {} %}
{% for minionid, ip in salt.saltutil.runner(
'mine.get',
tgt='G@role:so-hypervisor or G@role:so-managerhype',
fun='network.ip_addrs',
tgt_type='compound') | dictsort()
%}
# only add a node to the pillar if it returned an ip from the mine
{% if ip | length > 0%}
{% set hostname = minionid.split('_') | first %}
{% set node_type = minionid.split('_') | last %}
{% if node_type not in node_types.keys() %}
{% do node_types.update({node_type: {hostname: ip[0]}}) %}
{% else %}
{% if hostname not in node_types[node_type] %}
{% do node_types[node_type].update({hostname: ip[0]}) %}
{% else %}
{% do node_types[node_type][hostname].update(ip[0]) %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
hypervisor:
nodes:
{% for node_type, values in node_types.items() %}
{{node_type}}:
{% for hostname, ip in values.items() %}
{{hostname}}:
ip: {{ip}}
{% endfor %}
{% endfor %}

View File

@@ -1,2 +0,0 @@
kafka:
nodes:

13
pillar/logrotate/init.sls Normal file
View File

@@ -0,0 +1,13 @@
logrotate:
conf: |
daily
rotate 14
missingok
copytruncate
compress
create
extension .log
dateext
dateyesterday
group_conf: |
su root socore

42
pillar/logstash/helix.sls Normal file
View File

@@ -0,0 +1,42 @@
logstash:
pipelines:
helix:
config:
- so/0010_input_hhbeats.conf
- so/1033_preprocess_snort.conf
- so/1100_preprocess_bro_conn.conf
- so/1101_preprocess_bro_dhcp.conf
- so/1102_preprocess_bro_dns.conf
- so/1103_preprocess_bro_dpd.conf
- so/1104_preprocess_bro_files.conf
- so/1105_preprocess_bro_ftp.conf
- so/1106_preprocess_bro_http.conf
- so/1107_preprocess_bro_irc.conf
- so/1108_preprocess_bro_kerberos.conf
- so/1109_preprocess_bro_notice.conf
- so/1110_preprocess_bro_rdp.conf
- so/1111_preprocess_bro_signatures.conf
- so/1112_preprocess_bro_smtp.conf
- so/1113_preprocess_bro_snmp.conf
- so/1114_preprocess_bro_software.conf
- so/1115_preprocess_bro_ssh.conf
- so/1116_preprocess_bro_ssl.conf
- so/1117_preprocess_bro_syslog.conf
- so/1118_preprocess_bro_tunnel.conf
- so/1119_preprocess_bro_weird.conf
- so/1121_preprocess_bro_mysql.conf
- so/1122_preprocess_bro_socks.conf
- so/1123_preprocess_bro_x509.conf
- so/1124_preprocess_bro_intel.conf
- so/1125_preprocess_bro_modbus.conf
- so/1126_preprocess_bro_sip.conf
- so/1127_preprocess_bro_radius.conf
- so/1128_preprocess_bro_pe.conf
- so/1129_preprocess_bro_rfb.conf
- so/1130_preprocess_bro_dnp3.conf
- so/1131_preprocess_bro_smb_files.conf
- so/1132_preprocess_bro_smb_mapping.conf
- so/1133_preprocess_bro_ntlm.conf
- so/1134_preprocess_bro_dce_rpc.conf
- so/8001_postprocess_common_ip_augmentation.conf
- so/9997_output_helix.conf.jinja

View File

@@ -3,8 +3,6 @@ logstash:
port_bindings: port_bindings:
- 0.0.0.0:3765:3765 - 0.0.0.0:3765:3765
- 0.0.0.0:5044:5044 - 0.0.0.0:5044:5044
- 0.0.0.0:5055:5055
- 0.0.0.0:5056:5056
- 0.0.0.0:5644:5644 - 0.0.0.0:5644:5644
- 0.0.0.0:6050:6050 - 0.0.0.0:6050:6050
- 0.0.0.0:6051:6051 - 0.0.0.0:6051:6051

View File

@@ -0,0 +1,9 @@
logstash:
pipelines:
manager:
config:
- so/0009_input_beats.conf
- so/0010_input_hhbeats.conf
- so/0011_input_endgame.conf
- so/9999_output_redis.conf.jinja

View File

@@ -1,15 +1,14 @@
{% set node_types = {} %} {% set node_types = {} %}
{% set cached_grains = salt.saltutil.runner('cache.grains', tgt='*') %}
{% for minionid, ip in salt.saltutil.runner( {% for minionid, ip in salt.saltutil.runner(
'mine.get', 'mine.get',
tgt='logstash:enabled:true', tgt='G@role:so-manager or G@role:so-managersearch or G@role:so-standalone or G@role:so-node or G@role:so-heavynode or G@role:so-receiver or G@role:so-helix',
fun='network.ip_addrs', fun='network.ip_addrs',
tgt_type='pillar') | dictsort() tgt_type='compound') | dictsort()
%} %}
# only add a node to the pillar if it returned an ip from the mine {% set hostname = cached_grains[minionid]['host'] %}
{% if ip | length > 0%} {% set node_type = minionid.split('_')[1] %}
{% set hostname = minionid.split('_') | first %}
{% set node_type = minionid.split('_') | last %}
{% if node_type not in node_types.keys() %} {% if node_type not in node_types.keys() %}
{% do node_types.update({node_type: {hostname: ip[0]}}) %} {% do node_types.update({node_type: {hostname: ip[0]}}) %}
{% else %} {% else %}
@@ -19,10 +18,8 @@
{% do node_types[node_type][hostname].update(ip[0]) %} {% do node_types[node_type][hostname].update(ip[0]) %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %}
{% endfor %} {% endfor %}
logstash: logstash:
nodes: nodes:
{% for node_type, values in node_types.items() %} {% for node_type, values in node_types.items() %}

View File

@@ -0,0 +1,9 @@
logstash:
pipelines:
receiver:
config:
- so/0009_input_beats.conf
- so/0010_input_hhbeats.conf
- so/0011_input_endgame.conf
- so/9999_output_redis.conf.jinja

View File

@@ -0,0 +1,18 @@
logstash:
pipelines:
search:
config:
- so/0900_input_redis.conf.jinja
- so/9000_output_zeek.conf.jinja
- so/9002_output_import.conf.jinja
- so/9034_output_syslog.conf.jinja
- so/9050_output_filebeatmodules.conf.jinja
- so/9100_output_osquery.conf.jinja
- so/9400_output_suricata.conf.jinja
- so/9500_output_beats.conf.jinja
- so/9600_output_ossec.conf.jinja
- so/9700_output_strelka.conf.jinja
- so/9800_output_logscan.conf.jinja
- so/9801_output_rita.conf.jinja
- so/9802_output_kratos.conf.jinja
- so/9900_output_endgame.conf.jinja

View File

@@ -1,12 +1,11 @@
{% set node_types = {} %} {% set node_types = {} %}
{% set manage_alived = salt.saltutil.runner('manage.alived', show_ip=True) %} {% set manage_alived = salt.saltutil.runner('manage.alived', show_ip=True) %}
{% set manager = grains.master %}
{% set manager_type = manager.split('_')|last %}
{% for minionid, ip in salt.saltutil.runner('mine.get', tgt='*', fun='network.ip_addrs', tgt_type='glob') | dictsort() %} {% for minionid, ip in salt.saltutil.runner('mine.get', tgt='*', fun='network.ip_addrs', tgt_type='glob') | dictsort() %}
{% set hostname = minionid.split('_')[0] %} {% set hostname = minionid.split('_')[0] %}
{% set node_type = minionid.split('_')[1] %} {% set node_type = minionid.split('_')[1] %}
{% set is_alive = False %} {% set is_alive = False %}
# only add a node to the pillar if it returned an ip from the mine
{% if ip | length > 0%}
{% if minionid in manage_alived.keys() %} {% if minionid in manage_alived.keys() %}
{% if ip[0] == manage_alived[minionid] %} {% if ip[0] == manage_alived[minionid] %}
{% set is_alive = True %} {% set is_alive = True %}
@@ -21,19 +20,14 @@
{% do node_types[node_type][hostname].update({'ip':ip[0], 'alive':is_alive}) %} {% do node_types[node_type][hostname].update({'ip':ip[0], 'alive':is_alive}) %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %}
{% endfor %} {% endfor %}
{% if node_types %}
node_data: node_data:
{% for node_type, host_values in node_types.items() %} {% for node_type, host_values in node_types.items() %}
{{node_type}}:
{% for hostname, details in host_values.items() %} {% for hostname, details in host_values.items() %}
{{hostname}}: {{hostname}}:
ip: {{details.ip}} ip: {{details.ip}}
alive: {{ details.alive }} alive: {{ details.alive }}
role: {{node_type}}
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
{% else %}
node_data: False
{% endif %}

View File

@@ -1,34 +0,0 @@
{% set node_types = {} %}
{% for minionid, ip in salt.saltutil.runner(
'mine.get',
tgt='redis:enabled:true',
fun='network.ip_addrs',
tgt_type='pillar') | dictsort()
%}
# only add a node to the pillar if it returned an ip from the mine
{% if ip | length > 0%}
{% set hostname = minionid.split('_') | first %}
{% set node_type = minionid.split('_') | last %}
{% if node_type not in node_types.keys() %}
{% do node_types.update({node_type: {hostname: ip[0]}}) %}
{% else %}
{% if hostname not in node_types[node_type] %}
{% do node_types[node_type].update({hostname: ip[0]}) %}
{% else %}
{% do node_types[node_type][hostname].update(ip[0]) %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
redis:
nodes:
{% for node_type, values in node_types.items() %}
{{node_type}}:
{% for hostname, ip in values.items() %}
{{hostname}}:
ip: {{ip}}
{% endfor %}
{% endfor %}

View File

@@ -1,14 +0,0 @@
# Copyright Jason Ertel (github.com/jertel).
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with
# the Elastic License 2.0.
# Note: Per the Elastic License 2.0, the second limitation states:
#
# "You may not move, change, disable, or circumvent the license key functionality
# in the software, and you may not remove or obscure any functionality in the
# software that is protected by the license key."
# This file is generated by Security Onion and contains a list of license-enabled features.
features: []

View File

@@ -0,0 +1,44 @@
thresholding:
sids:
8675309:
- threshold:
gen_id: 1
type: threshold
track: by_src
count: 10
seconds: 10
- threshold:
gen_id: 1
type: limit
track: by_dst
count: 100
seconds: 30
- rate_filter:
gen_id: 1
track: by_rule
count: 50
seconds: 30
new_action: alert
timeout: 30
- suppress:
gen_id: 1
track: by_either
ip: 10.10.3.7
11223344:
- threshold:
gen_id: 1
type: limit
track: by_dst
count: 10
seconds: 10
- rate_filter:
gen_id: 1
track: by_src
count: 50
seconds: 20
new_action: pass
timeout: 60
- suppress:
gen_id: 1
track: by_src
ip: 10.10.3.0/24

View File

@@ -0,0 +1,20 @@
thresholding:
sids:
<signature id>:
- threshold:
gen_id: <generator id>
type: <threshold | limit | both>
track: <by_src | by_dst>
count: <count>
seconds: <seconds>
- rate_filter:
gen_id: <generator id>
track: <by_src | by_dst | by_rule | by_both>
count: <count>
seconds: <seconds>
new_action: <alert | pass>
timeout: <seconds>
- suppress:
gen_id: <generator id>
track: <by_src | by_dst | by_either>
ip: <ip | subnet>

View File

@@ -1,336 +1,136 @@
base: base:
'*': '*':
- global.soc_global
- global.adv_global
- docker.soc_docker
- docker.adv_docker
- influxdb.token
- logrotate.soc_logrotate
- logrotate.adv_logrotate
- ntp.soc_ntp
- ntp.adv_ntp
- patch.needs_restarting - patch.needs_restarting
- patch.soc_patch - logrotate
- patch.adv_patch
- sensoroni.soc_sensoroni
- sensoroni.adv_sensoroni
- telegraf.soc_telegraf
- telegraf.adv_telegraf
- versionlock.soc_versionlock
- versionlock.adv_versionlock
- soc.license
'* and not *_desktop': '* and not *_eval and not *_import':
- firewall.soc_firewall
- firewall.adv_firewall
- nginx.soc_nginx
- nginx.adv_nginx
'salt-cloud:driver:libvirt':
- match: grain
- vm.soc_vm
- vm.adv_vm
'*_manager or *_managersearch or *_managerhype':
- match: compound
- node_data.ips
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
- elasticsearch.auth
{% endif %}
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
- kibana.secrets
{% endif %}
- secrets
- manager.soc_manager
- manager.adv_manager
- idstools.soc_idstools
- idstools.adv_idstools
- logstash.nodes - logstash.nodes
- logstash.soc_logstash
- logstash.adv_logstash '*_eval or *_helixsensor or *_heavynode or *_sensor or *_standalone or *_import':
- soc.soc_soc - match: compound
- soc.adv_soc - zeek
- kibana.soc_kibana
- kibana.adv_kibana '*_managersearch or *_heavynode':
- kratos.soc_kratos - match: compound
- kratos.adv_kratos - logstash
- hydra.soc_hydra - logstash.manager
- hydra.adv_hydra - logstash.search
- redis.nodes - elasticsearch.index_templates
- redis.soc_redis
- redis.adv_redis '*_manager':
- influxdb.soc_influxdb - logstash
- influxdb.adv_influxdb - logstash.manager
- elasticsearch.nodes - elasticsearch.index_templates
- elasticsearch.soc_elasticsearch
- elasticsearch.adv_elasticsearch '*_manager or *_managersearch':
- elasticfleet.soc_elasticfleet - match: compound
- elasticfleet.adv_elasticfleet - data.*
- elastalert.soc_elastalert {% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
- elastalert.adv_elastalert - elasticsearch.auth
- backup.soc_backup {% endif %}
- backup.adv_backup {% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
- kibana.secrets
{% endif %}
- secrets
- global
- minions.{{ grains.id }} - minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
- kafka.nodes
- kafka.soc_kafka
- kafka.adv_kafka
- hypervisor.nodes
- hypervisor.soc_hypervisor
- hypervisor.adv_hypervisor
- stig.soc_stig
'*_sensor': '*_sensor':
- zeeklogs
- healthcheck.sensor - healthcheck.sensor
- strelka.soc_strelka - global
- strelka.adv_strelka
- zeek.soc_zeek
- zeek.adv_zeek
- bpf.soc_bpf
- bpf.adv_bpf
- pcap.soc_pcap
- pcap.adv_pcap
- suricata.soc_suricata
- suricata.adv_suricata
- minions.{{ grains.id }} - minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
- stig.soc_stig
'*_eval': '*_eval':
- node_data.ips - data.*
- zeeklogs
- secrets - secrets
- healthcheck.eval - healthcheck.eval
- elasticsearch.index_templates - elasticsearch.index_templates
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %} {% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
- elasticsearch.auth - elasticsearch.auth
{% endif %} {% endif %}
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %} {% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
- kibana.secrets - kibana.secrets
{% endif %} {% endif %}
- kratos.soc_kratos - global
- kratos.adv_kratos
- elasticsearch.soc_elasticsearch
- elasticsearch.adv_elasticsearch
- elasticfleet.soc_elasticfleet
- elasticfleet.adv_elasticfleet
- elastalert.soc_elastalert
- elastalert.adv_elastalert
- manager.soc_manager
- manager.adv_manager
- idstools.soc_idstools
- idstools.adv_idstools
- soc.soc_soc
- soc.adv_soc
- kibana.soc_kibana
- kibana.adv_kibana
- strelka.soc_strelka
- strelka.adv_strelka
- hydra.soc_hydra
- hydra.adv_hydra
- redis.soc_redis
- redis.adv_redis
- influxdb.soc_influxdb
- influxdb.adv_influxdb
- backup.soc_backup
- backup.adv_backup
- zeek.soc_zeek
- zeek.adv_zeek
- bpf.soc_bpf
- bpf.adv_bpf
- pcap.soc_pcap
- pcap.adv_pcap
- suricata.soc_suricata
- suricata.adv_suricata
- minions.{{ grains.id }} - minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
'*_standalone': '*_standalone':
- node_data.ips - logstash
- logstash.nodes - logstash.manager
- logstash.soc_logstash - logstash.search
- logstash.adv_logstash
- elasticsearch.index_templates - elasticsearch.index_templates
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %} {% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
- elasticsearch.auth - elasticsearch.auth
{% endif %} {% endif %}
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %} {% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
- kibana.secrets - kibana.secrets
{% endif %} {% endif %}
- data.*
- zeeklogs
- secrets - secrets
- healthcheck.standalone - healthcheck.standalone
- idstools.soc_idstools - global
- idstools.adv_idstools - minions.{{ grains.id }}
- kratos.soc_kratos
- kratos.adv_kratos '*_node':
- hydra.soc_hydra - global
- hydra.adv_hydra
- redis.nodes
- redis.soc_redis
- redis.adv_redis
- influxdb.soc_influxdb
- influxdb.adv_influxdb
- elasticsearch.nodes
- elasticsearch.soc_elasticsearch
- elasticsearch.adv_elasticsearch
- elasticfleet.soc_elasticfleet
- elasticfleet.adv_elasticfleet
- elastalert.soc_elastalert
- elastalert.adv_elastalert
- manager.soc_manager
- manager.adv_manager
- soc.soc_soc
- soc.adv_soc
- kibana.soc_kibana
- kibana.adv_kibana
- strelka.soc_strelka
- strelka.adv_strelka
- backup.soc_backup
- backup.adv_backup
- zeek.soc_zeek
- zeek.adv_zeek
- bpf.soc_bpf
- bpf.adv_bpf
- pcap.soc_pcap
- pcap.adv_pcap
- suricata.soc_suricata
- suricata.adv_suricata
- minions.{{ grains.id }} - minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
- stig.soc_stig
- kafka.nodes
- kafka.soc_kafka
- kafka.adv_kafka
'*_heavynode': '*_heavynode':
- zeeklogs
- elasticsearch.auth - elasticsearch.auth
- logstash.nodes - global
- logstash.soc_logstash
- logstash.adv_logstash
- elasticsearch.soc_elasticsearch
- elasticsearch.adv_elasticsearch
- redis.soc_redis
- redis.adv_redis
- zeek.soc_zeek
- zeek.adv_zeek
- bpf.soc_bpf
- bpf.adv_bpf
- pcap.soc_pcap
- pcap.adv_pcap
- suricata.soc_suricata
- suricata.adv_suricata
- strelka.soc_strelka
- strelka.adv_strelka
- minions.{{ grains.id }} - minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
'*_idh': '*_helixsensor':
- idh.soc_idh - fireeye
- idh.adv_idh - zeeklogs
- logstash
- logstash.helix
- global
- minions.{{ grains.id }} - minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
'*_searchnode':
- logstash.nodes
- logstash.soc_logstash
- logstash.adv_logstash
- elasticsearch.nodes
- elasticsearch.soc_elasticsearch
- elasticsearch.adv_elasticsearch
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
- elasticsearch.auth
{% endif %}
- redis.nodes
- redis.soc_redis
- redis.adv_redis
- minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
- stig.soc_stig
- kafka.nodes
- kafka.soc_kafka
- kafka.adv_kafka
'*_receiver':
- logstash.nodes
- logstash.soc_logstash
- logstash.adv_logstash
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
- elasticsearch.auth
{% endif %}
- redis.soc_redis
- redis.adv_redis
- minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
- kafka.nodes
- kafka.soc_kafka
- stig.soc_stig
- elasticfleet.soc_elasticfleet
- elasticfleet.adv_elasticfleet
'*_import':
- node_data.ips
- secrets
- elasticsearch.index_templates
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
- elasticsearch.auth
{% endif %}
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
- kibana.secrets
{% endif %}
- kratos.soc_kratos
- kratos.adv_kratos
- elasticsearch.soc_elasticsearch
- elasticsearch.adv_elasticsearch
- elasticfleet.soc_elasticfleet
- elasticfleet.adv_elasticfleet
- elastalert.soc_elastalert
- elastalert.adv_elastalert
- manager.soc_manager
- manager.adv_manager
- soc.soc_soc
- soc.adv_soc
- kibana.soc_kibana
- kibana.adv_kibana
- backup.soc_backup
- backup.adv_backup
- hydra.soc_hydra
- hydra.adv_hydra
- redis.soc_redis
- redis.adv_redis
- influxdb.soc_influxdb
- influxdb.adv_influxdb
- zeek.soc_zeek
- zeek.adv_zeek
- bpf.soc_bpf
- bpf.adv_bpf
- pcap.soc_pcap
- pcap.adv_pcap
- suricata.soc_suricata
- suricata.adv_suricata
- strelka.soc_strelka
- strelka.adv_strelka
- minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
'*_fleet': '*_fleet':
- node_data.ips - data.*
- backup.soc_backup - secrets
- backup.adv_backup - global
- logstash.nodes
- logstash.soc_logstash
- logstash.adv_logstash
- elasticfleet.soc_elasticfleet
- elasticfleet.adv_elasticfleet
- minions.{{ grains.id }} - minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
- stig.soc_stig
'*_hypervisor': '*_idh':
- data.*
- global
- minions.{{ grains.id }} - minions.{{ grains.id }}
- minions.adv_{{ grains.id }}
- stig.soc_stig
'*_desktop': '*_searchnode':
- logstash
- logstash.search
- elasticsearch.index_templates
- elasticsearch.auth
- global
- minions.{{ grains.id }} - minions.{{ grains.id }}
- minions.adv_{{ grains.id }} - data.nodestab
- stig.soc_stig
'*_receiver':
- logstash
- logstash.receiver
- elasticsearch.auth
- global
- minions.{{ grains.id }}
'*_import':
- zeeklogs
- secrets
- elasticsearch.index_templates
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/elasticsearch/auth.sls') %}
- elasticsearch.auth
{% endif %}
{% if salt['file.file_exists']('/opt/so/saltstack/local/pillar/kibana/secrets.sls') %}
- kibana.secrets
{% endif %}
- global
- minions.{{ grains.id }}
'*_workstation':
- minions.{{ grains.id }}

View File

@@ -1 +1,55 @@
zeek: zeek:
zeekctl:
MailTo: root@localhost
MailConnectionSummary: 1
MinDiskSpace: 5
MailHostUpDown: 1
LogRotationInterval: 3600
LogExpireInterval: 0
StatsLogEnable: 1
StatsLogExpireInterval: 0
StatusCmdShowAll: 0
CrashExpireInterval: 0
SitePolicyScripts: local.zeek
LogDir: /nsm/zeek/logs
SpoolDir: /nsm/zeek/spool
CfgDir: /opt/zeek/etc
CompressLogs: 1
local:
'@load':
- misc/loaded-scripts
- tuning/defaults
- misc/capture-loss
- misc/stats
- frameworks/software/vulnerable
- frameworks/software/version-changes
- protocols/ftp/software
- protocols/smtp/software
- protocols/ssh/software
- protocols/http/software
- protocols/dns/detect-external-names
- protocols/ftp/detect
- protocols/conn/known-hosts
- protocols/conn/known-services
- protocols/ssl/known-certs
- protocols/ssl/validate-certs
- protocols/ssl/log-hostcerts-only
- protocols/ssh/geo-data
- protocols/ssh/detect-bruteforcing
- protocols/ssh/interesting-hostnames
- protocols/http/detect-sqli
- frameworks/files/hash-all-files
- frameworks/files/detect-MHR
- policy/frameworks/notice/extend-email/hostnames
- ja3
- hassh
- intel
- cve-2020-0601
- securityonion/bpfconf
- securityonion/communityid
- securityonion/file-extraction
'@load-sigs':
- frameworks/signatures/detect-windows-shells
redef:
- LogAscii::use_json = T;
- CaptureLoss::watch_interval = 5 mins;

30
pyci.sh
View File

@@ -1,30 +0,0 @@
#!/bin/bash
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <python_script_dir>"
echo "Runs tests on all *_test.py files in the given directory."
exit 1
fi
HOME_DIR=$(dirname "$0")
TARGET_DIR=${1:-.}
PATH=$PATH:/usr/local/bin
if [ ! -d .venv ]; then
python -m venv .venv
fi
source .venv/bin/activate
if ! pip install flake8 pytest pytest-cov pyyaml; then
echo "Unable to install dependencies."
exit 1
fi
flake8 "$TARGET_DIR" "--config=${HOME_DIR}/pytest.ini"
python3 -m pytest "--cov-config=${HOME_DIR}/pytest.ini" "--cov=$TARGET_DIR" --doctest-modules --cov-report=term --cov-fail-under=100 "$TARGET_DIR"

View File

@@ -1,91 +0,0 @@
#!/opt/saltstack/salt/bin/python3
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
#
# Note: Per the Elastic License 2.0, the second limitation states:
#
# "You may not move, change, disable, or circumvent the license key functionality
# in the software, and you may not remove or obscure any functionality in the
# software that is protected by the license key."
"""
Salt execution module for hypervisor operations.
This module provides functions for managing hypervisor configurations,
including VM file management.
"""
import json
import logging
import os
log = logging.getLogger(__name__)
__virtualname__ = 'hypervisor'
def __virtual__():
"""
Only load this module if we're on a system that can manage hypervisors.
"""
return __virtualname__
def remove_vm_from_vms_file(vms_file_path, vm_hostname, vm_role):
"""
Remove a VM entry from the hypervisorVMs file.
Args:
vms_file_path (str): Path to the hypervisorVMs file
vm_hostname (str): Hostname of the VM to remove (without role suffix)
vm_role (str): Role of the VM
Returns:
dict: Result dictionary with success status and message
CLI Example:
salt '*' hypervisor.remove_vm_from_vms_file /opt/so/saltstack/local/salt/hypervisor/hosts/hypervisor1VMs node1 nsm
"""
try:
# Check if file exists
if not os.path.exists(vms_file_path):
msg = f"VMs file not found: {vms_file_path}"
log.error(msg)
return {'result': False, 'comment': msg}
# Read current VMs
with open(vms_file_path, 'r') as f:
content = f.read().strip()
vms = json.loads(content) if content else []
# Find and remove the VM entry
original_count = len(vms)
vms = [vm for vm in vms if not (vm.get('hostname') == vm_hostname and vm.get('role') == vm_role)]
if len(vms) < original_count:
# VM was found and removed, write back to file
with open(vms_file_path, 'w') as f:
json.dump(vms, f, indent=2)
# Set socore:socore ownership (939:939)
os.chown(vms_file_path, 939, 939)
msg = f"Removed VM {vm_hostname}_{vm_role} from {vms_file_path}"
log.info(msg)
return {'result': True, 'comment': msg}
else:
msg = f"VM {vm_hostname}_{vm_role} not found in {vms_file_path}"
log.warning(msg)
return {'result': False, 'comment': msg}
except json.JSONDecodeError as e:
msg = f"Failed to parse JSON in {vms_file_path}: {str(e)}"
log.error(msg)
return {'result': False, 'comment': msg}
except Exception as e:
msg = f"Failed to remove VM {vm_hostname}_{vm_role} from {vms_file_path}: {str(e)}"
log.error(msg)
return {'result': False, 'comment': msg}

View File

@@ -3,14 +3,14 @@ import subprocess
def check(): def check():
osfam = __grains__['os_family'] os = __grains__['os']
retval = 'False' retval = 'False'
if osfam == 'Debian': if os == 'Ubuntu':
if path.exists('/var/run/reboot-required'): if path.exists('/var/run/reboot-required'):
retval = 'True' retval = 'True'
elif osfam == 'RedHat': elif os == 'CentOS':
cmd = 'needs-restarting -r > /dev/null 2>&1' cmd = 'needs-restarting -r > /dev/null 2>&1'
try: try:

View File

@@ -1,335 +0,0 @@
#!py
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
"""
Salt module for managing QCOW2 image configurations and VM hardware settings. This module provides functions
for modifying network configurations within QCOW2 images, adjusting virtual machine hardware settings, and
creating virtual storage volumes. It serves as a Salt interface to the so-qcow2-modify-network,
so-kvm-modify-hardware, and so-kvm-create-volume scripts.
The module offers three main capabilities:
1. Network Configuration: Modify network settings (DHCP/static IP) within QCOW2 images
2. Hardware Configuration: Adjust VM hardware settings (CPU, memory, PCI passthrough)
3. Volume Management: Create and attach virtual storage volumes for NSM data
This module is intended to work with Security Onion's virtualization infrastructure and is typically
used in conjunction with salt-cloud for VM provisioning and management.
"""
import logging
import subprocess
import shlex
log = logging.getLogger(__name__)
__virtualname__ = 'qcow2'
def __virtual__():
return __virtualname__
def modify_network_config(image, interface, mode, vm_name, ip4=None, gw4=None, dns4=None, search4=None):
'''
Usage:
salt '*' qcow2.modify_network_config image=<path> interface=<iface> mode=<mode> vm_name=<name> [ip4=<addr>] [gw4=<addr>] [dns4=<servers>] [search4=<domain>]
Options:
image
Path to the QCOW2 image file that will be modified
interface
Network interface name to configure (e.g., 'enp1s0')
mode
Network configuration mode, either 'dhcp4' or 'static4'
vm_name
Full name of the VM (hostname_role)
ip4
IPv4 address with CIDR notation (e.g., '192.168.1.10/24')
Required when mode='static4'
gw4
IPv4 gateway address (e.g., '192.168.1.1')
Required when mode='static4'
dns4
Comma-separated list of IPv4 DNS servers (e.g., '8.8.8.8,8.8.4.4')
Optional for both DHCP and static configurations
search4
DNS search domain for IPv4 (e.g., 'example.local')
Optional for both DHCP and static configurations
Examples:
1. **Configure DHCP:**
```bash
salt '*' qcow2.modify_network_config image='/nsm/libvirt/images/sool9/sool9.qcow2' interface='enp1s0' mode='dhcp4'
```
This configures enp1s0 to use DHCP for IP assignment
2. **Configure Static IP:**
```bash
salt '*' qcow2.modify_network_config image='/nsm/libvirt/images/sool9/sool9.qcow2' interface='enp1s0' mode='static4' ip4='192.168.1.10/24' gw4='192.168.1.1' dns4='192.168.1.1,8.8.8.8' search4='example.local'
```
This sets a static IP configuration with DNS servers and search domain
Notes:
- The QCOW2 image must be accessible and writable by the salt minion
- The image should not be in use by a running VM when modified
- Network changes take effect on next VM boot
- Requires so-qcow2-modify-network script to be installed
Description:
This function modifies network configuration within a QCOW2 image file by executing
the so-qcow2-modify-network script. It supports both DHCP and static IPv4 configuration.
The script mounts the image, modifies the network configuration files, and unmounts
safely. All operations are logged for troubleshooting purposes.
Exit Codes:
0: Success
1: Invalid parameters or configuration
2: Image access or mounting error
3: Network configuration error
4: System command error
255: Unexpected error
Logging:
- All operations are logged to the salt minion log
- Log entries are prefixed with 'qcow2 module:'
- Error conditions include detailed error messages and stack traces
- Success/failure status is logged for verification
'''
cmd = ['/usr/sbin/so-qcow2-modify-network', '-I', image, '-i', interface, '-n', vm_name]
if mode.lower() == 'dhcp4':
cmd.append('--dhcp4')
elif mode.lower() == 'static4':
cmd.append('--static4')
if not ip4 or not gw4:
raise ValueError('Both ip4 and gw4 are required for static configuration.')
cmd.extend(['--ip4', ip4, '--gw4', gw4])
if dns4:
cmd.extend(['--dns4', dns4])
if search4:
cmd.extend(['--search4', search4])
else:
raise ValueError("Invalid mode '{}'. Expected 'dhcp4' or 'static4'.".format(mode))
log.info('qcow2 module: Executing command: {}'.format(' '.join(shlex.quote(arg) for arg in cmd)))
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
ret = {
'retcode': result.returncode,
'stdout': result.stdout,
'stderr': result.stderr
}
if result.returncode != 0:
log.error('qcow2 module: Script execution failed with return code {}: {}'.format(result.returncode, result.stderr))
else:
log.info('qcow2 module: Script executed successfully.')
return ret
except Exception as e:
log.error('qcow2 module: An error occurred while executing the script: {}'.format(e))
raise
def modify_hardware_config(vm_name, cpu=None, memory=None, pci=None, start=False):
'''
Usage:
salt '*' qcow2.modify_hardware_config vm_name=<name> [cpu=<count>] [memory=<size>] [pci=<id>] [pci=<id>] [start=<bool>]
Options:
vm_name
Name of the virtual machine to modify
cpu
Number of virtual CPUs to assign (positive integer)
Optional - VM's current CPU count retained if not specified
memory
Amount of memory to assign in MiB (positive integer)
Optional - VM's current memory size retained if not specified
pci
PCI hardware ID(s) to passthrough to the VM (e.g., '0000:c7:00.0')
Can be specified multiple times for multiple devices
Optional - no PCI passthrough if not specified
start
Boolean flag to start the VM after modification
Optional - defaults to False
Examples:
1. **Modify CPU and Memory:**
```bash
salt '*' qcow2.modify_hardware_config vm_name='sensor1' cpu=4 memory=8192
```
This assigns 4 CPUs and 8GB memory to the VM
2. **Enable PCI Passthrough:**
```bash
salt '*' qcow2.modify_hardware_config vm_name='sensor1' pci='0000:c7:00.0' pci='0000:c4:00.0' start=True
```
This configures PCI passthrough and starts the VM
3. **Complete Hardware Configuration:**
```bash
salt '*' qcow2.modify_hardware_config vm_name='sensor1' cpu=8 memory=16384 pci='0000:c7:00.0' start=True
```
This sets CPU, memory, PCI passthrough, and starts the VM
Notes:
- VM must be stopped before modification unless only the start flag is set
- Memory is specified in MiB (1024 = 1GB)
- PCI devices must be available and not in use by the host
- CPU count should align with host capabilities
- Requires so-kvm-modify-hardware script to be installed
Description:
This function modifies the hardware configuration of a KVM virtual machine using
the so-kvm-modify-hardware script. It can adjust CPU count, memory allocation,
and PCI device passthrough. Changes are applied to the VM's libvirt configuration.
The VM can optionally be started after modifications are complete.
Exit Codes:
0: Success
1: Invalid parameters
2: VM state error (running when should be stopped)
3: Hardware configuration error
4: System command error
255: Unexpected error
Logging:
- All operations are logged to the salt minion log
- Log entries are prefixed with 'qcow2 module:'
- Hardware configuration changes are logged
- Errors include detailed messages and stack traces
- Final status of modification is logged
'''
cmd = ['/usr/sbin/so-kvm-modify-hardware', '-v', vm_name]
if cpu is not None:
if isinstance(cpu, int) and cpu > 0:
cmd.extend(['-c', str(cpu)])
else:
raise ValueError('cpu must be a positive integer.')
if memory is not None:
if isinstance(memory, int) and memory > 0:
cmd.extend(['-m', str(memory)])
else:
raise ValueError('memory must be a positive integer.')
if pci:
# Handle PCI IDs (can be a single device or comma-separated list)
if isinstance(pci, str):
devices = [dev.strip() for dev in pci.split(',') if dev.strip()]
elif isinstance(pci, list):
devices = pci
else:
devices = [pci]
# Add each device with its own -p flag
for device in devices:
cmd.extend(['-p', str(device)])
if start:
cmd.append('-s')
log.info('qcow2 module: Executing command: {}'.format(' '.join(shlex.quote(arg) for arg in cmd)))
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
ret = {
'retcode': result.returncode,
'stdout': result.stdout,
'stderr': result.stderr
}
if result.returncode != 0:
log.error('qcow2 module: Script execution failed with return code {}: {}'.format(result.returncode, result.stderr))
else:
log.info('qcow2 module: Script executed successfully.')
return ret
except Exception as e:
log.error('qcow2 module: An error occurred while executing the script: {}'.format(e))
raise
def create_volume_config(vm_name, size_gb, start=False):
'''
Usage:
salt '*' qcow2.create_volume_config vm_name=<name> size_gb=<size> [start=<bool>]
Options:
vm_name
Name of the virtual machine to attach the volume to
size_gb
Volume size in GB (positive integer)
This determines the capacity of the virtual storage volume
start
Boolean flag to start the VM after volume creation
Optional - defaults to False
Examples:
1. **Create 500GB Volume:**
```bash
salt '*' qcow2.create_volume_config vm_name='sensor1_sensor' size_gb=500
```
This creates a 500GB virtual volume for NSM storage
2. **Create 1TB Volume and Start VM:**
```bash
salt '*' qcow2.create_volume_config vm_name='sensor1_sensor' size_gb=1000 start=True
```
This creates a 1TB volume and starts the VM after attachment
Notes:
- VM must be stopped before volume creation
- Volume is created as a qcow2 image and attached to the VM
- This is an alternative to disk passthrough via modify_hardware_config
- Volume is automatically attached to the VM's libvirt configuration
- Requires so-kvm-create-volume script to be installed
- Volume files are stored in the hypervisor's VM storage directory
Description:
This function creates and attaches a virtual storage volume to a KVM virtual machine
using the so-kvm-create-volume script. It creates a qcow2 disk image of the specified
size and attaches it to the VM for NSM (Network Security Monitoring) storage purposes.
This provides an alternative to physical disk passthrough, allowing flexible storage
allocation without requiring dedicated hardware. The VM can optionally be started
after the volume is successfully created and attached.
Exit Codes:
0: Success
1: Invalid parameters
2: VM state error (running when should be stopped)
3: Volume creation error
4: System command error
255: Unexpected error
Logging:
- All operations are logged to the salt minion log
- Log entries are prefixed with 'qcow2 module:'
- Volume creation and attachment operations are logged
- Errors include detailed messages and stack traces
- Final status of volume creation is logged
'''
# Validate size_gb parameter
if not isinstance(size_gb, int) or size_gb <= 0:
raise ValueError('size_gb must be a positive integer.')
cmd = ['/usr/sbin/so-kvm-create-volume', '-v', vm_name, '-s', str(size_gb)]
if start:
cmd.append('-S')
log.info('qcow2 module: Executing command: {}'.format(' '.join(shlex.quote(arg) for arg in cmd)))
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
ret = {
'retcode': result.returncode,
'stdout': result.stdout,
'stderr': result.stderr
}
if result.returncode != 0:
log.error('qcow2 module: Script execution failed with return code {}: {}'.format(result.returncode, result.stderr))
else:
log.info('qcow2 module: Script executed successfully.')
return ret
except Exception as e:
log.error('qcow2 module: An error occurred while executing the script: {}'.format(e))
raise

View File

@@ -5,8 +5,6 @@ import logging
def status(): def status():
return __salt__['cmd.run']('/usr/sbin/so-status') return __salt__['cmd.run']('/usr/sbin/so-status')
def version():
return __salt__['cp.get_file_str']('/etc/soversion')
def mysql_conn(retry): def mysql_conn(retry):
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

File diff suppressed because it is too large Load Diff

View File

@@ -1,180 +1,324 @@
{# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one {% set ZEEKVER = salt['pillar.get']('global:mdengine', '') %}
or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at {% set WAZUH = salt['pillar.get']('global:wazuh', '0') %}
https://securityonion.net/license; you may not use this file except in compliance with the {% set PLAYBOOK = salt['pillar.get']('manager:playbook', '0') %}
Elastic License 2.0. #} {% set FREQSERVER = salt['pillar.get']('manager:freq', '0') %}
{% set DOMAINSTATS = salt['pillar.get']('manager:domainstats', '0') %}
{% set FLEETMANAGER = salt['pillar.get']('global:fleet_manager', False) %}
{% set FLEETNODE = salt['pillar.get']('global:fleet_node', False) %}
{% set ELASTALERT = salt['pillar.get']('elastalert:enabled', True) %}
{% set ELASTICSEARCH = salt['pillar.get']('elasticsearch:enabled', True) %}
{% set FILEBEAT = salt['pillar.get']('filebeat:enabled', True) %}
{% set KIBANA = salt['pillar.get']('kibana:enabled', True) %}
{% set LOGSTASH = salt['pillar.get']('logstash:enabled', True) %}
{% set CURATOR = salt['pillar.get']('curator:enabled', True) %}
{% set REDIS = salt['pillar.get']('redis:enabled', True) %}
{% set STRELKA = salt['pillar.get']('strelka:enabled', '0') %}
{% set ISAIRGAP = salt['pillar.get']('global:airgap', False) %} {% set ISAIRGAP = salt['pillar.get']('global:airgap', False) %}
{% import_yaml 'salt/minion.defaults.yaml' as saltversion %} {% import_yaml 'salt/minion.defaults.yaml' as saltversion %}
{% set saltversion = saltversion.salt.minion.version %} {% set saltversion = saltversion.salt.minion.version %}
{# Define common state groups to reduce redundancy #} {# this is the list we are returning from this map file, it gets built below #}
{% set base_states = [ {% set allowed_states= [] %}
'common',
'patch.os.schedule',
'motd',
'salt.minion-check',
'sensoroni',
'salt.lasthighstate',
'salt.minion'
] %}
{% set ssl_states = [ {% if grains.saltversion | string == saltversion | string %}
{% set allowed_states= salt['grains.filter_by']({
'so-eval': [
'salt.master',
'ca',
'ssl',
'registry',
'manager',
'nginx',
'telegraf',
'influxdb',
'grafana',
'soc',
'kratos',
'firewall',
'idstools',
'suricata.manager',
'healthcheck',
'pcap',
'suricata',
'utility',
'schedule',
'soctopus',
'tcpreplay',
'docker_clean',
'learn'
],
'so-heavynode': [
'ssl',
'nginx',
'telegraf',
'firewall',
'pcap',
'suricata',
'healthcheck',
'schedule',
'tcpreplay',
'docker_clean'
],
'so-helixsensor': [
'salt.master',
'ca',
'ssl',
'registry',
'telegraf',
'firewall',
'idstools',
'suricata.manager',
'zeek',
'redis',
'elasticsearch',
'logstash',
'schedule',
'tcpreplay',
'docker_clean'
],
'so-fleet': [
'ssl',
'nginx',
'telegraf',
'firewall',
'mysql',
'redis',
'fleet',
'fleet.install_package',
'filebeat',
'schedule',
'docker_clean'
],
'so-idh': [
'ssl',
'telegraf',
'firewall',
'fleet.install_package',
'filebeat',
'idh',
'schedule',
'docker_clean'
],
'so-import': [
'salt.master',
'ca',
'ssl',
'registry',
'manager',
'nginx',
'soc',
'kratos',
'firewall',
'idstools',
'suricata.manager',
'pcap',
'utility',
'suricata',
'zeek',
'schedule',
'tcpreplay',
'docker_clean',
'learn'
],
'so-manager': [
'salt.master',
'ca',
'ssl',
'registry',
'manager',
'nginx',
'telegraf',
'influxdb',
'grafana',
'soc',
'kratos',
'firewall',
'idstools',
'suricata.manager',
'utility',
'schedule',
'soctopus',
'docker_clean',
'learn'
],
'so-managersearch': [
'salt.master',
'ca',
'ssl',
'registry',
'nginx',
'telegraf',
'influxdb',
'grafana',
'soc',
'kratos',
'firewall',
'manager',
'idstools',
'suricata.manager',
'utility',
'schedule',
'soctopus',
'docker_clean',
'learn'
],
'so-node': [
'ssl',
'nginx',
'telegraf',
'firewall',
'schedule',
'docker_clean'
],
'so-standalone': [
'salt.master',
'ca',
'ssl',
'registry',
'manager',
'nginx',
'telegraf',
'influxdb',
'grafana',
'soc',
'kratos',
'firewall',
'idstools',
'suricata.manager',
'pcap',
'suricata',
'healthcheck',
'utility',
'schedule',
'soctopus',
'tcpreplay',
'docker_clean',
'learn'
],
'so-sensor': [
'ssl',
'telegraf',
'firewall',
'nginx',
'pcap',
'suricata',
'healthcheck',
'wazuh',
'filebeat',
'schedule',
'tcpreplay',
'docker_clean'
],
'so-receiver': [
'ssl', 'ssl',
'telegraf', 'telegraf',
'firewall', 'firewall',
'schedule', 'schedule',
'docker_clean' 'docker_clean'
] %} ],
'so-workstation': [
],
}, grain='role') %}
{% set manager_states = [ {% if FILEBEAT and grains.role in ['so-helixsensor', 'so-eval', 'so-manager', 'so-standalone', 'so-node', 'so-managersearch', 'so-heavynode', 'so-import', 'so-receiver'] %}
'salt.master', {% do allowed_states.append('filebeat') %}
'ca',
'registry',
'manager',
'nginx',
'influxdb',
'soc',
'kratos',
'hydra',
'elasticfleet',
'elastic-fleet-package-registry',
'idstools',
'suricata.manager',
'utility'
] %}
{% set sensor_states = [
'pcap',
'suricata',
'healthcheck',
'tcpreplay',
'zeek',
'strelka'
] %}
{% set kafka_states = [
'kafka'
] %}
{% set stig_states = [
'stig'
] %}
{% set elastic_stack_states = [
'elasticsearch',
'elasticsearch.auth',
'kibana',
'kibana.secrets',
'elastalert',
'logstash',
'redis'
] %}
{# Initialize the allowed_states list #}
{% set allowed_states = [] %}
{% if grains.saltversion | string == saltversion | string %}
{# Map role-specific states #}
{% set role_states = {
'so-eval': (
ssl_states +
manager_states +
sensor_states +
elastic_stack_states | reject('equalto', 'logstash') | list
),
'so-heavynode': (
ssl_states +
sensor_states +
['elasticagent', 'elasticsearch', 'logstash', 'redis', 'nginx']
),
'so-idh': (
ssl_states +
['idh']
),
'so-import': (
ssl_states +
manager_states +
sensor_states | reject('equalto', 'strelka') | reject('equalto', 'healthcheck') | list +
['elasticsearch', 'elasticsearch.auth', 'kibana', 'kibana.secrets', 'strelka.manager']
),
'so-manager': (
ssl_states +
manager_states +
['salt.cloud', 'libvirt.packages', 'libvirt.ssh.users', 'strelka.manager'] +
stig_states +
kafka_states +
elastic_stack_states
),
'so-managerhype': (
ssl_states +
manager_states +
['salt.cloud', 'strelka.manager', 'hypervisor', 'libvirt'] +
stig_states +
kafka_states +
elastic_stack_states
),
'so-managersearch': (
ssl_states +
manager_states +
['salt.cloud', 'libvirt.packages', 'libvirt.ssh.users', 'strelka.manager'] +
stig_states +
kafka_states +
elastic_stack_states
),
'so-searchnode': (
ssl_states +
['kafka.ca', 'kafka.ssl', 'elasticsearch', 'logstash', 'nginx'] +
stig_states
),
'so-standalone': (
ssl_states +
manager_states +
['salt.cloud', 'libvirt.packages', 'libvirt.ssh.users'] +
sensor_states +
stig_states +
kafka_states +
elastic_stack_states
),
'so-sensor': (
ssl_states +
sensor_states +
['nginx'] +
stig_states
),
'so-fleet': (
ssl_states +
stig_states +
['logstash', 'nginx', 'healthcheck', 'elasticfleet']
),
'so-receiver': (
ssl_states +
kafka_states +
stig_states +
['logstash', 'redis']
),
'so-hypervisor': (
ssl_states +
stig_states +
['hypervisor', 'libvirt']
),
'so-desktop': (
['ssl', 'docker_clean', 'telegraf'] +
stig_states
)
} %}
{# Get states for the current role #}
{% if grains.role in role_states %}
{% set allowed_states = role_states[grains.role] %}
{% endif %} {% endif %}
{# Add base states that apply to all roles #} {% if ((FLEETMANAGER or FLEETNODE) or PLAYBOOK != 0) and grains.role in ['so-eval', 'so-manager', 'so-managersearch', 'so-standalone'] %}
{% for state in base_states %} {% do allowed_states.append('mysql') %}
{% do allowed_states.append(state) %} {% endif %}
{% endfor %}
{% if (FLEETMANAGER or FLEETNODE) and grains.role in ['so-sensor', 'so-eval', 'so-manager', 'so-standalone', 'so-node', 'so-managersearch', 'so-heavynode', 'so-receiver'] %}
{% do allowed_states.append('fleet.install_package') %}
{% endif %}
{% if (FLEETMANAGER or FLEETNODE) and grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-managersearch', 'so-heavynode'] %}
{% do allowed_states.append('fleet') %}
{% endif %}
{% if (FLEETMANAGER or FLEETNODE) and grains.role in ['so-eval'] %}
{% do allowed_states.append('redis') %}
{% endif %}
{%- if ZEEKVER != 'SURICATA' and grains.role in ['so-sensor', 'so-eval', 'so-standalone', 'so-heavynode'] %}
{% do allowed_states.append('zeek') %}
{%- endif %}
{% if STRELKA and grains.role in ['so-sensor', 'so-eval', 'so-standalone', 'so-heavynode'] %}
{% do allowed_states.append('strelka') %}
{% endif %}
{% if WAZUH and grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-node', 'so-managersearch', 'so-heavynode', 'so-receiver','so-idh']%}
{% do allowed_states.append('wazuh') %}
{% endif %}
{% if ELASTICSEARCH and grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-node', 'so-managersearch', 'so-heavynode', 'so-import'] %}
{% do allowed_states.append('elasticsearch') %}
{% endif %}
{% if ELASTICSEARCH and grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-managersearch', 'so-import'] %}
{% do allowed_states.append('elasticsearch.auth') %}
{% endif %}
{% if KIBANA and grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-managersearch', 'so-import'] %}
{% do allowed_states.append('kibana') %}
{% do allowed_states.append('kibana.secrets') %}
{% endif %}
{% if grains.role in ['so-eval', 'so-standalone', 'so-node', 'so-managersearch', 'so-heavynode', 'so-manager'] %}
{% do allowed_states.append('curator') %}
{% endif %}
{% if ELASTALERT and grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-managersearch'] %}
{% do allowed_states.append('elastalert') %}
{% endif %}
{% if (PLAYBOOK !=0) and grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-managersearch'] %}
{% do allowed_states.append('playbook') %}
{% endif %}
{% if (PLAYBOOK !=0) and grains.role in ['so-eval'] %}
{% do allowed_states.append('redis') %}
{% endif %}
{% if (FREQSERVER !=0) and grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-managersearch'] %}
{% do allowed_states.append('freqserver') %}
{% endif %}
{% if (DOMAINSTATS !=0) and grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-managersearch'] %}
{% do allowed_states.append('domainstats') %}
{% endif %}
{% if LOGSTASH and grains.role in ['so-helixsensor', 'so-manager', 'so-standalone', 'so-node', 'so-managersearch', 'so-heavynode', 'so-receiver'] %}
{% do allowed_states.append('logstash') %}
{% endif %}
{% if REDIS and grains.role in ['so-manager', 'so-standalone', 'so-managersearch', 'so-heavynode', 'so-receiver'] %}
{% do allowed_states.append('redis') %}
{% endif %}
{% if grains.os == 'CentOS' %}
{% if not ISAIRGAP %}
{% do allowed_states.append('yum') %}
{% endif %}
{% do allowed_states.append('yum.packages') %}
{% endif %}
{# all nodes on the right salt version can run the following states #}
{% do allowed_states.append('common') %}
{% do allowed_states.append('patch.os.schedule') %}
{% do allowed_states.append('motd') %}
{% do allowed_states.append('salt.minion-check') %}
{% do allowed_states.append('sensoroni') %}
{% do allowed_states.append('salt.lasthighstate') %}
{% endif %} {% endif %}
{# Add airgap state if needed #}
{% if ISAIRGAP %} {% if ISAIRGAP %}
{% do allowed_states.append('airgap') %} {% do allowed_states.append('airgap') %}
{% endif %} {% endif %}
{# all nodes can always run salt.minion state #}
{% do allowed_states.append('salt.minion') %}

View File

@@ -1,34 +0,0 @@
{% from 'backup/map.jinja' import BACKUP_MERGED %}
# Lock permissions on the backup directory
backupdir:
file.directory:
- name: /nsm/backup
- user: 0
- group: 0
- makedirs: True
- mode: 700
config_backup_script:
file.managed:
- name: /usr/sbin/so-config-backup
- user: root
- group: root
- mode: 755
- template: jinja
- source: salt://backup/tools/sbin/so-config-backup.jinja
- defaults:
BACKUPLOCATIONS: {{ BACKUP_MERGED.locations }}
DESTINATION: {{ BACKUP_MERGED.destination }}
# Add config backup
so_config_backup:
cron.present:
- name: /usr/sbin/so-config-backup > /dev/null 2>&1
- identifier: so_config_backup
- user: root
- minute: '1'
- hour: '0'
- daymonth: '*'
- month: '*'
- dayweek: '*'

View File

@@ -1,8 +0,0 @@
backup:
locations:
- /opt/so/saltstack/local
- /etc/pki
- /etc/salt
- /nsm/kratos
- /nsm/hydra
destination: "/nsm/backup"

View File

@@ -1,2 +0,0 @@
{% import_yaml 'backup/defaults.yaml' as BACKUP_DEFAULTS %}
{% set BACKUP_MERGED = salt['pillar.get']('backup', BACKUP_DEFAULTS.backup, merge=true, merge_nested_lists=true) %}

View File

@@ -1,10 +0,0 @@
backup:
locations:
description: List of locations to back up to the destination.
helpLink: backup.html
global: True
destination:
description: Directory to store the configuration backups in.
helpLink: backup.html
global: True

View File

@@ -1,41 +0,0 @@
#!/bin/bash
#
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
. /usr/sbin/so-common
TODAY=$(date '+%Y_%m_%d')
BACKUPDIR={{ DESTINATION }}
BACKUPFILE="$BACKUPDIR/so-config-backup-$TODAY.tar"
MAXBACKUPS=7
EXCLUSIONS=(
"--exclude=/opt/so/saltstack/local/salt/elasticfleet/files/so_agent-installers"
)
# Create backup dir if it does not exist
mkdir -p /nsm/backup
# If we haven't already written a backup file for today, let's do so
if [ ! -f $BACKUPFILE ]; then
# Create empty backup file
tar -cf $BACKUPFILE -T /dev/null
# Loop through all paths defined in global.sls, and append them to backup file
{%- for LOCATION in BACKUPLOCATIONS %}
tar -rf $BACKUPFILE "${EXCLUSIONS[@]}" {{ LOCATION }}
{%- endfor %}
fi
# Find oldest backup files and remove them
NUMBACKUPS=$(find /nsm/backup/ -type f -name "so-config-backup*" | wc -l)
while [ "$NUMBACKUPS" -gt "$MAXBACKUPS" ]; do
OLDESTBACKUP=$(find /nsm/backup/ -type f -name "so-config-backup*" -type f -printf '%T+ %p\n' | sort | head -n 1 | awk -F" " '{print $2}')
rm -f $OLDESTBACKUP
NUMBACKUPS=$(find /nsm/backup/ -type f -name "so-config-backup*" | wc -l)
done

View File

@@ -1,4 +0,0 @@
bpf:
pcap: []
suricata: []
zeek: []

View File

@@ -1,10 +0,0 @@
{% macro remove_comments(bpfmerged, app) %}
{# remove comments from the bpf #}
{% for bpf in bpfmerged[app] %}
{% if bpf.strip().startswith('#') %}
{% do bpfmerged[app].pop(loop.index0) %}
{% endif %}
{% endfor %}
{% endmacro %}

View File

@@ -1,21 +0,0 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% set PCAP_BPF_STATUS = 0 %}
{% set STENO_BPF_COMPILED = "" %}
{% if GLOBALS.pcap_engine == "TRANSITION" %}
{% set PCAPBPF = ["ip and host 255.255.255.1 and port 1"] %}
{% else %}
{% import_yaml 'bpf/defaults.yaml' as BPFDEFAULTS %}
{% set BPFMERGED = salt['pillar.get']('bpf', BPFDEFAULTS.bpf, merge=True) %}
{% import 'bpf/macros.jinja' as MACROS %}
{{ MACROS.remove_comments(BPFMERGED, 'pcap') }}
{% set PCAPBPF = BPFMERGED.pcap %}
{% endif %}
{% if PCAPBPF %}
{% set PCAP_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ PCAPBPF|join(" "), cwd='/root') %}
{% if PCAP_BPF_CALC['retcode'] == 0 %}
{% set PCAP_BPF_STATUS = 1 %}
{% set STENO_BPF_COMPILED = ",\\\"--filter=" + PCAP_BPF_CALC['stdout'] + "\\\"" %}
{% endif %}
{% endif %}

View File

@@ -1,16 +0,0 @@
bpf:
pcap:
description: List of BPF filters to apply to the PCAP engine.
multiline: True
forcedType: "[]string"
helpLink: bpf.html
suricata:
description: List of BPF filters to apply to Suricata. This will apply to alerts and, if enabled, to metadata and PCAP logs generated by Suricata.
multiline: True
forcedType: "[]string"
helpLink: bpf.html
zeek:
description: List of BPF filters to apply to Zeek.
multiline: True
forcedType: "[]string"
helpLink: bpf.html

View File

@@ -1,16 +0,0 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% import_yaml 'bpf/defaults.yaml' as BPFDEFAULTS %}
{% set BPFMERGED = salt['pillar.get']('bpf', BPFDEFAULTS.bpf, merge=True) %}
{% set SURICATA_BPF_STATUS = 0 %}
{% import 'bpf/macros.jinja' as MACROS %}
{{ MACROS.remove_comments(BPFMERGED, 'suricata') }}
{% set SURICATABPF = BPFMERGED.suricata %}
{% if SURICATABPF %}
{% set SURICATA_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ SURICATABPF|join(" "), cwd='/root') %}
{% if SURICATA_BPF_CALC['retcode'] == 0 %}
{% set SURICATA_BPF_STATUS = 1 %}
{% endif %}
{% endif %}

View File

@@ -1,16 +0,0 @@
{% from 'vars/globals.map.jinja' import GLOBALS %}
{% import_yaml 'bpf/defaults.yaml' as BPFDEFAULTS %}
{% set BPFMERGED = salt['pillar.get']('bpf', BPFDEFAULTS.bpf, merge=True) %}
{% set ZEEK_BPF_STATUS = 0 %}
{% import 'bpf/macros.jinja' as MACROS %}
{{ MACROS.remove_comments(BPFMERGED, 'zeek') }}
{% set ZEEKBPF = BPFMERGED.zeek %}
{% if ZEEKBPF %}
{% set ZEEK_BPF_CALC = salt['cmd.run_all']('/usr/sbin/so-bpf-compile ' ~ GLOBALS.sensor.interface ~ ' ' ~ ZEEKBPF|join(" "), cwd='/root') %}
{% if ZEEK_BPF_CALC['retcode'] == 0 %}
{% set ZEEK_BPF_STATUS = 1 %}
{% endif %}
{% endif %}

View File

@@ -1,3 +1,6 @@
mine_functions:
x509.get_pem_entries: [/etc/pki/ca.crt]
x509_signing_policies: x509_signing_policies:
filebeat: filebeat:
- minions: '*' - minions: '*'
@@ -10,7 +13,7 @@ x509_signing_policies:
- keyUsage: "digitalSignature, nonRepudiation" - keyUsage: "digitalSignature, nonRepudiation"
- subjectKeyIdentifier: hash - subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid,issuer:always - authorityKeyIdentifier: keyid,issuer:always
- days_valid: 9 - days_valid: 820
- copypath: /etc/pki/issued_certs/ - copypath: /etc/pki/issued_certs/
registry: registry:
- minions: '*' - minions: '*'
@@ -24,7 +27,7 @@ x509_signing_policies:
- subjectKeyIdentifier: hash - subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid,issuer:always - authorityKeyIdentifier: keyid,issuer:always
- extendedKeyUsage: serverAuth - extendedKeyUsage: serverAuth
- days_valid: 9 - days_valid: 820
- copypath: /etc/pki/issued_certs/ - copypath: /etc/pki/issued_certs/
managerssl: managerssl:
- minions: '*' - minions: '*'
@@ -34,11 +37,11 @@ x509_signing_policies:
- ST: Utah - ST: Utah
- L: Salt Lake City - L: Salt Lake City
- basicConstraints: "critical CA:false" - basicConstraints: "critical CA:false"
- keyUsage: "critical keyEncipherment digitalSignature" - keyUsage: "critical keyEncipherment"
- subjectKeyIdentifier: hash - subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid,issuer:always - authorityKeyIdentifier: keyid,issuer:always
- extendedKeyUsage: serverAuth - extendedKeyUsage: serverAuth
- days_valid: 9 - days_valid: 820
- copypath: /etc/pki/issued_certs/ - copypath: /etc/pki/issued_certs/
influxdb: influxdb:
- minions: '*' - minions: '*'
@@ -52,9 +55,9 @@ x509_signing_policies:
- subjectKeyIdentifier: hash - subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid,issuer:always - authorityKeyIdentifier: keyid,issuer:always
- extendedKeyUsage: serverAuth - extendedKeyUsage: serverAuth
- days_valid: 9 - days_valid: 820
- copypath: /etc/pki/issued_certs/ - copypath: /etc/pki/issued_certs/
elasticfleet: fleet:
- minions: '*' - minions: '*'
- signing_private_key: /etc/pki/ca.key - signing_private_key: /etc/pki/ca.key
- signing_cert: /etc/pki/ca.crt - signing_cert: /etc/pki/ca.crt
@@ -62,22 +65,9 @@ x509_signing_policies:
- ST: Utah - ST: Utah
- L: Salt Lake City - L: Salt Lake City
- basicConstraints: "critical CA:false" - basicConstraints: "critical CA:false"
- keyUsage: "digitalSignature, nonRepudiation" - keyUsage: "critical keyEncipherment"
- subjectKeyIdentifier: hash - subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid,issuer:always - authorityKeyIdentifier: keyid,issuer:always
- days_valid: 9 - extendedKeyUsage: serverAuth
- copypath: /etc/pki/issued_certs/ - days_valid: 820
kafka:
- minions: '*'
- signing_private_key: /etc/pki/ca.key
- signing_cert: /etc/pki/ca.crt
- C: US
- ST: Utah
- L: Salt Lake City
- basicConstraints: "critical CA:false"
- keyUsage: "digitalSignature, keyEncipherment"
- subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid,issuer:always
- extendedKeyUsage: "serverAuth, clientAuth"
- days_valid: 9
- copypath: /etc/pki/issued_certs/ - copypath: /etc/pki/issued_certs/

View File

@@ -1,16 +1,10 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
{% from 'allowed_states.map.jinja' import allowed_states %} {% from 'allowed_states.map.jinja' import allowed_states %}
{% if sls in allowed_states %} {% if sls in allowed_states %}
{% from 'vars/globals.map.jinja' import GLOBALS %}
include: include:
- ca.dirs - ca.dirs
{% set manager = salt['grains.get']('master') %}
/etc/salt/minion.d/signing_policies.conf: /etc/salt/minion.d/signing_policies.conf:
file.managed: file.managed:
- source: salt://ca/files/signing_policies.conf - source: salt://ca/files/signing_policies.conf
@@ -18,8 +12,9 @@ include:
pki_private_key: pki_private_key:
x509.private_key_managed: x509.private_key_managed:
- name: /etc/pki/ca.key - name: /etc/pki/ca.key
- keysize: 4096 - bits: 4096
- passphrase: - passphrase:
- cipher: aes_256_cbc
- backup: True - backup: True
{% if salt['file.file_exists']('/etc/pki/ca.key') -%} {% if salt['file.file_exists']('/etc/pki/ca.key') -%}
- prereq: - prereq:
@@ -30,7 +25,7 @@ pki_public_ca_crt:
x509.certificate_managed: x509.certificate_managed:
- name: /etc/pki/ca.crt - name: /etc/pki/ca.crt
- signing_private_key: /etc/pki/ca.key - signing_private_key: /etc/pki/ca.key
- CN: {{ GLOBALS.manager }} - CN: {{ manager }}
- C: US - C: US
- ST: Utah - ST: Utah
- L: Salt Lake City - L: Salt Lake City
@@ -38,9 +33,9 @@ pki_public_ca_crt:
- keyUsage: "critical cRLSign, keyCertSign" - keyUsage: "critical cRLSign, keyCertSign"
- extendedkeyUsage: "serverAuth, clientAuth" - extendedkeyUsage: "serverAuth, clientAuth"
- subjectKeyIdentifier: hash - subjectKeyIdentifier: hash
- authorityKeyIdentifier: keyid:always, issuer - authorityKeyIdentifier: keyid,issuer:always
- days_valid: 11 - days_valid: 3650
- days_remaining: 7 - days_remaining: 0
- backup: True - backup: True
- replace: False - replace: False
- require: - require:
@@ -50,12 +45,6 @@ pki_public_ca_crt:
attempts: 5 attempts: 5
interval: 30 interval: 30
mine_update_ca_crt:
module.run:
- mine.update: []
- onchanges:
- x509: pki_public_ca_crt
cakeyperms: cakeyperms:
file.managed: file.managed:
- replace: False - replace: False

View File

@@ -0,0 +1,2 @@
#!/bin/bash
/usr/sbin/logrotate -f /opt/so/conf/log-rotate.conf > /dev/null 2>&1

View File

@@ -0,0 +1,2 @@
#!/bin/bash
/usr/sbin/logrotate -f /opt/so/conf/sensor-rotate.conf > /dev/null 2>&1

View File

@@ -0,0 +1,79 @@
The following GUI tools are available on the analyst workstation:
chromium
url: https://www.chromium.org/Home
To run chromium, click Applications > Internet > Chromium Web Browser
Wireshark
url: https://www.wireshark.org/
To run Wireshark, click Applications > Internet > Wireshark Network Analyzer
NetworkMiner
url: https://www.netresec.com
To run NetworkMiner, click Applications > Internet > NetworkMiner
The following CLI tools are available on the analyst workstation:
bit-twist
url: http://bittwist.sourceforge.net
To run bit-twist, open a terminal and type: bittwist -h
chaosreader
url: http://chaosreader.sourceforge.net
To run chaosreader, open a terminal and type: chaosreader -h
dnsiff
url: https://www.monkey.org/~dugsong/dsniff/
To run dsniff, open a terminal and type: dsniff -h
foremost
url: http://foremost.sourceforge.net
To run foremost, open a terminal and type: foremost -h
hping3
url: http://www.hping.org/hping3.html
To run hping3, open a terminal and type: hping3 -h
netsed
url: http://silicone.homelinux.org/projects/netsed/
To run netsed, open a terminal and type: netsed -h
ngrep
url: https://github.com/jpr5/ngrep
To run ngrep, open a terminal and type: ngrep -h
scapy
url: http://www.secdev.org/projects/scapy/
To run scapy, open a terminal and type: scapy
ssldump
url: http://www.rtfm.com/ssldump/
To run ssldump, open a terminal and type: ssldump -h
sslsplit
url: https://github.com/droe/sslsplit
To run sslsplit, open a terminal and type: sslsplit -h
tcpdump
url: http://www.tcpdump.org
To run tcpdump, open a terminal and type: tcpdump -h
tcpflow
url: https://github.com/simsong/tcpflow
To run tcpflow, open a terminal and type: tcpflow -h
tcpstat
url: https://frenchfries.net/paul/tcpstat/
To run tcpstat, open a terminal and type: tcpstat -h
tcptrace
url: http://www.tcptrace.org
To run tcptrace, open a terminal and type: tcptrace -h
tcpxtract
url: http://tcpxtract.sourceforge.net/
To run tcpxtract, open a terminal and type: tcpxtract -h
whois
url: http://www.linux.it/~md/software/
To run whois, open a terminal and type: whois -h

View File

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 269 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 319 KiB

View File

@@ -1,12 +1,12 @@
{%- set DOCKERRANGE = salt['pillar.get']('docker:range', '172.17.0.0/24') %}
{%- set DOCKERBIND = salt['pillar.get']('docker:bip', '172.17.0.1/24') %}
{ {
"registry-mirrors": [ "registry-mirrors": [ "https://:5000" ],
"https://:5000" "bip": "{{ DOCKERBIND }}",
],
"bip": "172.17.0.1/24",
"default-address-pools": [ "default-address-pools": [
{ {
"base": "172.17.0.0/24", "base" : "{{ DOCKERRANGE }}",
"size": 24 "size" : 24
} }
] ]
} }

View File

@@ -0,0 +1,37 @@
{%- set logrotate_conf = salt['pillar.get']('logrotate:conf') %}
{%- set group_conf = salt['pillar.get']('logrotate:group_conf') %}
/opt/so/log/aptcacher-ng/*.log
/opt/so/log/idstools/*.log
/opt/so/log/nginx/*.log
/opt/so/log/soc/*.log
/opt/so/log/kratos/*.log
/opt/so/log/kibana/*.log
/opt/so/log/influxdb/*.log
/opt/so/log/elastalert/*.log
/opt/so/log/soctopus/*.log
/opt/so/log/curator/*.log
/opt/so/log/fleet/*.log
/opt/so/log/suricata/*.log
/opt/so/log/mysql/*.log
/opt/so/log/telegraf/*.log
/opt/so/log/redis/*.log
/opt/so/log/sensoroni/*.log
/opt/so/log/stenographer/*.log
/opt/so/log/salt/so-salt-minion-check
/opt/so/log/salt/minion
/opt/so/log/salt/master
/opt/so/log/logscan/*.log
/nsm/idh/*.log
{
{{ logrotate_conf | indent(width=4) }}
}
# Playbook's log directory needs additional configuration
# because Playbook requires a more permissive directory
/opt/so/log/playbook/*.log
{
{{ logrotate_conf | indent(width=4) }}
{{ group_conf | indent(width=4) }}
}

View File

@@ -0,0 +1,22 @@
/opt/so/log/sensor_clean.log
{
daily
rotate 2
missingok
nocompress
create
sharedscripts
}
/nsm/strelka/log/strelka.log
{
daily
rotate 14
missingok
copytruncate
compress
create
extension .log
dateext
dateyesterday
}

View File

@@ -3,3 +3,4 @@ filetype plugin indent on
" Sets .sls files to use YAML syntax highlighting " Sets .sls files to use YAML syntax highlighting
autocmd BufNewFile,BufRead *.sls set syntax=yaml autocmd BufNewFile,BufRead *.sls set syntax=yaml
set number

View File

@@ -1,21 +0,0 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
{% set nsm_exists = salt['file.directory_exists']('/nsm') %}
{% if nsm_exists %}
{% set nsm_total = salt['cmd.shell']('df -BG /nsm | tail -1 | awk \'{print $2}\'') %}
nsm_total:
grains.present:
- name: nsm_total
- value: {{ nsm_total }}
{% else %}
nsm_missing:
test.succeed_without_changes:
- name: /nsm does not exist, skipping grain assignment
{% endif %}

View File

@@ -1,30 +1,25 @@
{% from 'allowed_states.map.jinja' import allowed_states %} {% from 'allowed_states.map.jinja' import allowed_states %}
{% if sls in allowed_states %} {% if sls in allowed_states %}
{% from 'vars/globals.map.jinja' import GLOBALS %} {% set role = grains.id.split('_') | last %}
{% from 'elasticsearch/auth.map.jinja' import ELASTICAUTH with context %}
include: include:
- common.grains - common.soup_scripts
- common.packages {% if grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-managersearch', 'so-import'] %}
{% if GLOBALS.role in GLOBALS.manager_roles %}
- manager.elasticsearch # needed for elastic_curl_config state - manager.elasticsearch # needed for elastic_curl_config state
- manager.kibana
{% endif %} {% endif %}
net.core.wmem_default:
sysctl.present:
- value: 26214400
# Users are not a fan of console messages
kernel.printk:
sysctl.present:
- value: "3 4 1 3"
# Remove variables.txt from /tmp - This is temp # Remove variables.txt from /tmp - This is temp
rmvariablesfile: rmvariablesfile:
file.absent: file.absent:
- name: /tmp/variables.txt - name: /tmp/variables.txt
dockergroup:
group.present:
- name: docker
- gid: 920
# Add socore Group # Add socore Group
socoregroup: socoregroup:
group.present: group.present:
@@ -43,15 +38,15 @@ socore:
soconfperms: soconfperms:
file.directory: file.directory:
- name: /opt/so/conf - name: /opt/so/conf
- user: 939 - uid: 939
- group: 939 - gid: 939
- dir_mode: 770 - dir_mode: 770
sostatusconf: sostatusconf:
file.directory: file.directory:
- name: /opt/so/conf/so-status - name: /opt/so/conf/so-status
- user: 939 - uid: 939
- group: 939 - gid: 939
- dir_mode: 770 - dir_mode: 770
so-status.conf: so-status.conf:
@@ -59,11 +54,12 @@ so-status.conf:
- name: /opt/so/conf/so-status/so-status.conf - name: /opt/so/conf/so-status/so-status.conf
- unless: ls /opt/so/conf/so-status/so-status.conf - unless: ls /opt/so/conf/so-status/so-status.conf
socore_opso_perms: sosaltstackperms:
file.directory: file.directory:
- name: /opt/so - name: /opt/so/saltstack
- user: 939 - uid: 939
- group: 939 - gid: 939
- dir_mode: 770
so_log_perms: so_log_perms:
file.directory: file.directory:
@@ -92,6 +88,92 @@ vimconfig:
- source: salt://common/files/vimrc - source: salt://common/files/vimrc
- replace: False - replace: False
# Install common packages
{% if grains['os'] != 'CentOS' %}
commonpkgs:
pkg.installed:
- skip_suggestions: True
- pkgs:
- apache2-utils
- wget
- ntpdate
- jq
- python3-docker
- curl
- ca-certificates
- software-properties-common
- apt-transport-https
- openssl
- netcat
- python3-mysqldb
- sqlite3
- libssl-dev
- python3-dateutil
- python3-m2crypto
- python3-mysqldb
- python3-packaging
- python3-lxml
- git
- vim
heldpackages:
pkg.installed:
- pkgs:
{% if grains['oscodename'] == 'bionic' %}
- containerd.io: 1.4.4-1
- docker-ce: 5:20.10.5~3-0~ubuntu-bionic
- docker-ce-cli: 5:20.10.5~3-0~ubuntu-bionic
- docker-ce-rootless-extras: 5:20.10.5~3-0~ubuntu-bionic
{% elif grains['oscodename'] == 'focal' %}
- containerd.io: 1.4.9-1
- docker-ce: 5:20.10.8~3-0~ubuntu-focal
- docker-ce-cli: 5:20.10.5~3-0~ubuntu-focal
- docker-ce-rootless-extras: 5:20.10.5~3-0~ubuntu-focal
{% endif %}
- hold: True
- update_holds: True
{% else %}
commonpkgs:
pkg.installed:
- skip_suggestions: True
- pkgs:
- wget
- ntpdate
- bind-utils
- jq
- tcpdump
- httpd-tools
- net-tools
- curl
- sqlite
- mariadb-devel
- nmap-ncat
- python3
- python36-docker
- python36-dateutil
- python36-m2crypto
- python36-mysql
- python36-packaging
- python36-lxml
- yum-utils
- device-mapper-persistent-data
- lvm2
- openssl
- git
- vim-enhanced
heldpackages:
pkg.installed:
- pkgs:
- containerd.io: 1.4.4-3.1.el7
- docker-ce: 3:20.10.5-3.el7
- docker-ce-cli: 1:20.10.5-3.el7
- docker-ce-rootless-extras: 20.10.5-3.el7
- hold: True
- update_holds: True
{% endif %}
# Always keep these packages up to date # Always keep these packages up to date
alwaysupdated: alwaysupdated:
@@ -106,8 +188,7 @@ alwaysupdated:
Etc/UTC: Etc/UTC:
timezone.system timezone.system
# Sync curl configuration for Elasticsearch authentication {% if salt['pillar.get']('elasticsearch:auth:enabled', False) %}
{% if GLOBALS.is_manager or GLOBALS.role in ['so-heavynode', 'so-searchnode'] %}
elastic_curl_config: elastic_curl_config:
file.managed: file.managed:
- name: /opt/so/conf/elasticsearch/curl.config - name: /opt/so/conf/elasticsearch/curl.config
@@ -115,82 +196,87 @@ elastic_curl_config:
- mode: 600 - mode: 600
- show_changes: False - show_changes: False
- makedirs: True - makedirs: True
{% if GLOBALS.role in GLOBALS.manager_roles %} {% if grains.role in ['so-eval', 'so-manager', 'so-standalone', 'so-managersearch', 'so-import'] %}
- require: - require:
- file: elastic_curl_config_distributed - file: elastic_curl_config_distributed
{% endif %} {% endif %}
{% endif %} {% endif %}
# Sync some Utilities
common_sbin: utilsyncscripts:
file.recurse: file.recurse:
- name: /usr/sbin - name: /usr/sbin
- source: salt://common/tools/sbin - user: root
- user: 939 - group: root
- group: 939
- file_mode: 755
- show_changes: False
{% if GLOBALS.role == 'so-heavynode' %}
- exclude_pat:
- so-pcap-import
{% endif %}
common_sbin_jinja:
file.recurse:
- name: /usr/sbin
- source: salt://common/tools/sbin_jinja
- user: 939
- group: 939
- file_mode: 755 - file_mode: 755
- template: jinja - template: jinja
- show_changes: False - source: salt://common/tools/sbin
{% if GLOBALS.role == 'so-heavynode' %} - defaults:
ELASTICCURL: 'curl'
- context:
ELASTICCURL: {{ ELASTICAUTH.elasticcurl }}
- exclude_pat: - exclude_pat:
- so-import-pcap - so-common
{% endif %} - so-firewall
- so-image-common
- soup
{% if GLOBALS.role == 'so-heavynode' %} {% if role in ['eval', 'standalone', 'sensor', 'heavynode'] %}
remove_so-pcap-import_heavynode:
file.absent:
- name: /usr/sbin/so-pcap-import
remove_so-import-pcap_heavynode:
file.absent:
- name: /usr/sbin/so-import-pcap
{% endif %}
{% if not GLOBALS.is_manager%}
# prior to 2.4.50 these scripts were in common/tools/sbin on the manager because of soup and distributed to non managers
# these two states remove the scripts from non manager nodes
remove_soup:
file.absent:
- name: /usr/sbin/soup
remove_so-firewall:
file.absent:
- name: /usr/sbin/so-firewall
{% endif %}
so-status_script:
file.managed:
- name: /usr/sbin/so-status
- source: salt://common/tools/sbin/so-status
- mode: 755
{% if GLOBALS.role in GLOBALS.sensor_roles %}
# Add sensor cleanup # Add sensor cleanup
so-sensor-clean: /usr/sbin/so-sensor-clean:
cron.present: cron.present:
- name: /usr/sbin/so-sensor-clean
- identifier: so-sensor-clean
- user: root - user: root
- minute: '*' - minute: '*'
- hour: '*' - hour: '*'
- daymonth: '*' - daymonth: '*'
- month: '*' - month: '*'
- dayweek: '*' - dayweek: '*'
sensorrotatescript:
file.managed:
- name: /usr/local/bin/sensor-rotate
- source: salt://common/cron/sensor-rotate
- mode: 755
sensorrotateconf:
file.managed:
- name: /opt/so/conf/sensor-rotate.conf
- source: salt://common/files/sensor-rotate.conf
- mode: 644
/usr/local/bin/sensor-rotate:
cron.present:
- user: root
- minute: '1'
- hour: '0'
- daymonth: '*'
- month: '*'
- dayweek: '*'
{% endif %} {% endif %}
commonlogrotatescript:
file.managed:
- name: /usr/local/bin/common-rotate
- source: salt://common/cron/common-rotate
- mode: 755
commonlogrotateconf:
file.managed:
- name: /opt/so/conf/log-rotate.conf
- source: salt://common/files/log-rotate.conf
- template: jinja
- mode: 644
/usr/local/bin/common-rotate:
cron.present:
- user: root
- minute: '1'
- hour: '0'
- daymonth: '*'
- month: '*'
- dayweek: '*'
# Create the status directory # Create the status directory
sostatusdir: sostatusdir:
file.directory: file.directory:
@@ -203,13 +289,10 @@ sostatus_log:
file.managed: file.managed:
- name: /opt/so/log/sostatus/status.log - name: /opt/so/log/sostatus/status.log
- mode: 644 - mode: 644
- replace: False
# Install sostatus check cron. This is used to populate Grid. # Install sostatus check cron
so-status_check_cron: '/usr/sbin/so-status -q; echo $? > /opt/so/log/sostatus/status.log 2>&1':
cron.present: cron.present:
- name: '/usr/sbin/so-status -j > /opt/so/log/sostatus/status.log 2>&1'
- identifier: so-status_check_cron
- user: root - user: root
- minute: '*/1' - minute: '*/1'
- hour: '*' - hour: '*'
@@ -217,21 +300,36 @@ so-status_check_cron:
- month: '*' - month: '*'
- dayweek: '*' - dayweek: '*'
# This cronjob/script runs a check if the node needs restarted, but should be used for future status checks as well {% if role in ['eval', 'manager', 'managersearch', 'standalone'] %}
common_status_check_cron: # Install cron job to determine size of influxdb for telegraf
'du -s -k /nsm/influxdb | cut -f1 > /opt/so/log/telegraf/influxdb_size.log 2>&1':
cron.present: cron.present:
- name: '/usr/sbin/so-common-status-check > /dev/null 2>&1'
- identifier: common_status_check
- user: root - user: root
- minute: '*/10' - minute: '*/1'
- hour: '*'
- daymonth: '*'
- month: '*'
- dayweek: '*'
remove_post_setup_cron: # Lock permissions on the backup directory
cron.absent: backupdir:
- name: 'PATH=$PATH:/usr/sbin salt-call state.highstate' file.directory:
- identifier: post_setup_cron - name: /nsm/backup
- user: 0
{% if GLOBALS.role not in ['eval', 'manager', 'managersearch', 'standalone'] %} - group: 0
- makedirs: True
- mode: 700
# Add config backup
/usr/sbin/so-config-backup > /dev/null 2>&1:
cron.present:
- user: root
- minute: '1'
- hour: '0'
- daymonth: '*'
- month: '*'
- dayweek: '*'
{% else %}
soversionfile: soversionfile:
file.managed: file.managed:
- name: /etc/soversion - name: /etc/soversion
@@ -241,8 +339,34 @@ soversionfile:
{% endif %} {% endif %}
{% if GLOBALS.so_model and GLOBALS.so_model not in ['SO2AMI01', 'SO2AZI01', 'SO2GCI01'] %} # Manager daemon.json
{% if GLOBALS.os == 'OEL' %} docker_daemon:
file.managed:
- source: salt://common/files/daemon.json
- name: /etc/docker/daemon.json
- template: jinja
# Make sure Docker is always running
docker:
service.running:
- enable: True
- watch:
- file: docker_daemon
# Reserve OS ports for Docker proxy in case boot settings are not already applied/present
# 55000 = Wazuh, 57314 = Strelka, 47760-47860 = Zeek
dockerapplyports:
cmd.run:
- name: if [ ! -s /etc/sysctl.d/99-reserved-ports.conf ]; then sysctl -w net.ipv4.ip_local_reserved_ports="55000,57314,47760-47860"; fi
# Reserve OS ports for Docker proxy
dockerreserveports:
file.managed:
- source: salt://common/files/99-reserved-ports.conf
- name: /etc/sysctl.d/99-reserved-ports.conf
{% if salt['grains.get']('sosmodel', '') %}
{% if grains['os'] == 'CentOS' %}
# Install Raid tools # Install Raid tools
raidpkgs: raidpkgs:
pkg.installed: pkg.installed:
@@ -253,10 +377,8 @@ raidpkgs:
{% endif %} {% endif %}
# Install raid check cron # Install raid check cron
so-raid-status: /usr/sbin/so-raid-status > /dev/null 2>&1:
cron.present: cron.present:
- name: '/usr/sbin/so-raid-status > /dev/null 2>&1'
- identifier: so-raid-status
- user: root - user: root
- minute: '*/15' - minute: '*/15'
- hour: '*' - hour: '*'
@@ -264,7 +386,8 @@ so-raid-status:
- month: '*' - month: '*'
- dayweek: '*' - dayweek: '*'
{% endif %} {% endif %}
{% else %} {% else %}
{{sls}}_state_not_allowed: {{sls}}_state_not_allowed:

View File

@@ -1,88 +0,0 @@
# we cannot import GLOBALS from vars/globals.map.jinja in this state since it is called in setup.virt.init
# since it is early in setup of a new VM, the pillars imported in GLOBALS are not yet defined
{% if grains.os_family == 'Debian' %}
commonpkgs:
pkg.installed:
- skip_suggestions: True
- pkgs:
- apache2-utils
- wget
- ntpdate
- jq
- curl
- ca-certificates
- software-properties-common
- apt-transport-https
- openssl
- netcat-openbsd
- sqlite3
- libssl-dev
- procps
- python3-dateutil
- python3-docker
- python3-packaging
- python3-lxml
- git
- rsync
- vim
- tar
- unzip
- bc
{% if grains.oscodename != 'focal' %}
- python3-rich
{% endif %}
{% if grains.oscodename == 'focal' %}
# since Ubuntu requires and internet connection we can use pip to install modules
python3-pip:
pkg.installed
python-rich:
pip.installed:
- name: rich
- target: /usr/local/lib/python3.8/dist-packages/
- require:
- pkg: python3-pip
{% endif %}
{% endif %}
{% if grains.os_family == 'RedHat' %}
remove_mariadb:
pkg.removed:
- name: mariadb-devel
commonpkgs:
pkg.installed:
- skip_suggestions: True
- pkgs:
- python3-dnf-plugin-versionlock
- bc
- curl
- device-mapper-persistent-data
- fuse
- fuse-libs
- fuse-overlayfs
- fuse-common
- fuse3
- fuse3-libs
- git
- httpd-tools
- jq
- lvm2
- net-tools
- nmap-ncat
- procps-ng
- python3-docker
- python3-m2crypto
- python3-packaging
- python3-pyyaml
- python3-rich
- rsync
- sqlite
- tcpdump
- unzip
- wget
- yum-utils
{% endif %}

View File

@@ -1,142 +1,13 @@
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one # Sync some Utilities
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at soup_scripts:
# https://securityonion.net/license; you may not use this file except in compliance with the file.recurse:
# Elastic License 2.0. - name: /usr/sbin
- user: root
{% if '2.4' in salt['cp.get_file_str']('/etc/soversion') %} - group: root
- file_mode: 755
{% import_yaml '/opt/so/saltstack/local/pillar/global/soc_global.sls' as SOC_GLOBAL %} - source: salt://common/tools/sbin
{% if SOC_GLOBAL.global.airgap %} - include_pat:
{% set UPDATE_DIR='/tmp/soagupdate/SecurityOnion' %} - so-common
{% else %} - so-firewall
{% set UPDATE_DIR='/tmp/sogh/securityonion' %} - so-image-common
{% endif %} - soup
{% set SOVERSION = salt['file.read']('/etc/soversion').strip() %}
remove_common_soup:
file.absent:
- name: /opt/so/saltstack/default/salt/common/tools/sbin/soup
remove_common_so-firewall:
file.absent:
- name: /opt/so/saltstack/default/salt/common/tools/sbin/so-firewall
# This section is used to put the scripts in place in the Salt file system
# in case a state run tries to overwrite what we do in the next section.
copy_so-common_common_tools_sbin:
file.copy:
- name: /opt/so/saltstack/default/salt/common/tools/sbin/so-common
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common
- force: True
- preserve: True
copy_so-image-common_common_tools_sbin:
file.copy:
- name: /opt/so/saltstack/default/salt/common/tools/sbin/so-image-common
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common
- force: True
- preserve: True
copy_soup_manager_tools_sbin:
file.copy:
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/soup
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup
- force: True
- preserve: True
copy_so-firewall_manager_tools_sbin:
file.copy:
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-firewall
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall
- force: True
- preserve: True
copy_so-yaml_manager_tools_sbin:
file.copy:
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-yaml.py
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py
- force: True
- preserve: True
copy_so-repo-sync_manager_tools_sbin:
file.copy:
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-repo-sync
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync
- preserve: True
copy_bootstrap-salt_manager_tools_sbin:
file.copy:
- name: /opt/so/saltstack/default/salt/salt/scripts/bootstrap-salt.sh
- source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh
- preserve: True
# This section is used to put the new script in place so that it can be called during soup.
# It is faster than calling the states that normally manage them to put them in place.
copy_so-common_sbin:
file.copy:
- name: /usr/sbin/so-common
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common
- force: True
- preserve: True
copy_so-image-common_sbin:
file.copy:
- name: /usr/sbin/so-image-common
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common
- force: True
- preserve: True
copy_soup_sbin:
file.copy:
- name: /usr/sbin/soup
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup
- force: True
- preserve: True
copy_so-firewall_sbin:
file.copy:
- name: /usr/sbin/so-firewall
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall
- force: True
- preserve: True
copy_so-yaml_sbin:
file.copy:
- name: /usr/sbin/so-yaml.py
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py
- force: True
- preserve: True
copy_so-repo-sync_sbin:
file.copy:
- name: /usr/sbin/so-repo-sync
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync
- force: True
- preserve: True
copy_bootstrap-salt_sbin:
file.copy:
- name: /usr/sbin/bootstrap-salt.sh
- source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh
- force: True
- preserve: True
{# this is added in 2.4.120 to remove salt repo files pointing to saltproject.io to accomodate the move to broadcom and new bootstrap-salt script #}
{% if salt['pkg.version_cmp'](SOVERSION, '2.4.120') == -1 %}
{% set saltrepofile = '/etc/yum.repos.d/salt.repo' %}
{% if grains.os_family == 'Debian' %}
{% set saltrepofile = '/etc/apt/sources.list.d/salt.list' %}
{% endif %}
remove_saltproject_io_repo_manager:
file.absent:
- name: {{ saltrepofile }}
{% endif %}
{% else %}
fix_23_soup_sbin:
cmd.run:
- name: curl -s -f -o /usr/sbin/soup https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/2.3/main/salt/common/tools/sbin/soup
fix_23_soup_salt:
cmd.run:
- name: curl -s -f -o /opt/so/saltstack/defalt/salt/common/tools/sbin/soup https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/2.3/main/salt/common/tools/sbin/soup
{% endif %}

207
salt/common/tools/sbin/so-allow Executable file
View File

@@ -0,0 +1,207 @@
#!/usr/bin/env python3
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import ipaddress
import textwrap
import os
import subprocess
import sys
import argparse
import re
from lxml import etree as ET
from datetime import datetime as dt
from datetime import timezone as tz
LOCAL_SALT_DIR='/opt/so/saltstack/local'
WAZUH_CONF='/nsm/wazuh/etc/ossec.conf'
VALID_ROLES = {
'a': { 'role': 'analyst','desc': 'Analyst - 80/tcp, 443/tcp' },
'b': { 'role': 'beats_endpoint', 'desc': 'Logstash Beat - 5044/tcp' },
'e': { 'role': 'elasticsearch_rest', 'desc': 'Elasticsearch REST API - 9200/tcp' },
'f': { 'role': 'strelka_frontend', 'desc': 'Strelka frontend - 57314/tcp' },
'o': { 'role': 'osquery_endpoint', 'desc': 'Osquery endpoint - 8090/tcp' },
's': { 'role': 'syslog', 'desc': 'Syslog device - 514/tcp/udp' },
'w': { 'role': 'wazuh_agent', 'desc': 'Wazuh agent - 1514/tcp/udp' },
'p': { 'role': 'wazuh_api', 'desc': 'Wazuh API - 55000/tcp' },
'r': { 'role': 'wazuh_authd', 'desc': 'Wazuh registration service - 1515/tcp' }
}
def validate_ip_cidr(ip_cidr: str) -> bool:
try:
ipaddress.ip_address(ip_cidr)
except ValueError:
try:
ipaddress.ip_network(ip_cidr)
except ValueError:
return False
return True
def role_prompt() -> str:
print()
print('Choose the role for the IP or Range you would like to allow')
print()
for role in VALID_ROLES:
print(f'[{role}] - {VALID_ROLES[role]["desc"]}')
print()
role = input('Please enter your selection: ')
if role in VALID_ROLES.keys():
return VALID_ROLES[role]['role']
else:
print(f'Invalid role \'{role}\', please try again.', file=sys.stderr)
sys.exit(1)
def ip_prompt() -> str:
ip = input('Enter a single ip address or range to allow (ex: 10.10.10.10 or 10.10.0.0/16): ')
if validate_ip_cidr(ip):
return ip
else:
print(f'Invalid IP address or CIDR block \'{ip}\', please try again.', file=sys.stderr)
sys.exit(1)
def wazuh_enabled() -> bool:
file = f'{LOCAL_SALT_DIR}/pillar/global.sls'
with open(file, 'r') as pillar:
if 'wazuh: 1' in pillar.read():
return True
return False
def root_to_str(root: ET.ElementTree) -> str:
return ET.tostring(root, encoding='unicode', method='xml', xml_declaration=False, pretty_print=True)
def add_wl(ip):
parser = ET.XMLParser(remove_blank_text=True)
with open(WAZUH_CONF, 'rb') as wazuh_conf:
tree = ET.parse(wazuh_conf, parser)
root = tree.getroot()
source_comment = ET.Comment(f'Address {ip} added by /usr/sbin/so-allow on {dt.utcnow().replace(tzinfo=tz.utc).strftime("%a %b %e %H:%M:%S %Z %Y")}')
new_global = ET.Element("global")
new_wl = ET.SubElement(new_global, 'white_list')
new_wl.text = ip
root.append(source_comment)
root.append(new_global)
with open(WAZUH_CONF, 'w') as add_out:
add_out.write(root_to_str(root))
def apply(role: str, ip: str) -> int:
firewall_cmd = ['so-firewall', 'includehost', role, ip]
salt_cmd = ['salt-call', 'state.apply', '-l', 'quiet', 'firewall', 'queue=True']
restart_wazuh_cmd = ['so-wazuh-restart']
print(f'Adding {ip} to the {role} role. This can take a few seconds...')
cmd = subprocess.run(firewall_cmd)
if cmd.returncode == 0:
cmd = subprocess.run(salt_cmd, stdout=subprocess.DEVNULL)
else:
return cmd.returncode
if cmd.returncode == 0:
if wazuh_enabled() and role=='analyst':
try:
add_wl(ip)
print(f'Added whitelist entry for {ip} from {WAZUH_CONF}', file=sys.stderr)
except Exception as e:
print(f'Failed to add whitelist entry for {ip} from {WAZUH_CONF}', file=sys.stderr)
print(e)
return 1
print('Restarting OSSEC Server...')
cmd = subprocess.run(restart_wazuh_cmd)
else:
return cmd.returncode
else:
print(f'Commmand \'{" ".join(salt_cmd)}\' failed.', file=sys.stderr)
return cmd.returncode
if cmd.returncode != 0:
print('Failed to restart OSSEC server.')
return cmd.returncode
def main():
if os.geteuid() != 0:
print('You must run this script as root', file=sys.stderr)
sys.exit(1)
main_parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent(f'''\
additional information:
To use this script in interactive mode call it with no arguments
'''
))
group = main_parser.add_argument_group(title='roles')
group.add_argument('-a', dest='roles', action='append_const', const=VALID_ROLES['a']['role'], help="Analyst - 80/tcp, 443/tcp")
group.add_argument('-b', dest='roles', action='append_const', const=VALID_ROLES['b']['role'], help="Logstash Beat - 5044/tcp")
group.add_argument('-e', dest='roles', action='append_const', const=VALID_ROLES['e']['role'], help="Elasticsearch REST API - 9200/tcp")
group.add_argument('-f', dest='roles', action='append_const', const=VALID_ROLES['f']['role'], help="Strelka frontend - 57314/tcp")
group.add_argument('-o', dest='roles', action='append_const', const=VALID_ROLES['o']['role'], help="Osquery endpoint - 8090/tcp")
group.add_argument('-s', dest='roles', action='append_const', const=VALID_ROLES['s']['role'], help="Syslog device - 514/tcp/udp")
group.add_argument('-w', dest='roles', action='append_const', const=VALID_ROLES['w']['role'], help="Wazuh agent - 1514/tcp/udp")
group.add_argument('-p', dest='roles', action='append_const', const=VALID_ROLES['p']['role'], help="Wazuh API - 55000/tcp")
group.add_argument('-r', dest='roles', action='append_const', const=VALID_ROLES['r']['role'], help="Wazuh registration service - 1515/tcp")
ip_g = main_parser.add_argument_group(title='allow')
ip_g.add_argument('-i', help="IP or CIDR block to disallow connections from, requires at least one role argument", metavar='', dest='ip')
args = main_parser.parse_args(sys.argv[1:])
if args.roles is None:
role = role_prompt()
ip = ip_prompt()
try:
return_code = apply(role, ip)
except Exception as e:
print(f'Unexpected exception occurred: {e}', file=sys.stderr)
return_code = e.errno
sys.exit(return_code)
elif args.roles is not None and args.ip is None:
if os.environ.get('IP') is None:
main_parser.print_help()
sys.exit(1)
else:
args.ip = os.environ['IP']
if validate_ip_cidr(args.ip):
try:
for role in args.roles:
return_code = apply(role, args.ip)
if return_code > 0:
break
except Exception as e:
print(f'Unexpected exception occurred: {e}', file=sys.stderr)
return_code = e.errno
else:
print(f'Invalid IP address or CIDR block \'{args.ip}\', please try again.', file=sys.stderr)
return_code = 1
sys.exit(return_code)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
sys.exit(1)

View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
echo ""
echo "Hosts/Networks that have access to login to the Security Onion Console:"
so-firewall includedhosts analyst

View File

@@ -0,0 +1,100 @@
#!/bin/bash
# Copyright 2014-2022 Security Onion Solutions, LLC
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
doc_workstation_url="https://docs.securityonion.net/en/2.3/analyst-vm.html"
{# we only want the script to install the workstation if it is CentOS -#}
{% if grains.os == 'CentOS' -%}
{# if this is a manager -#}
{% if grains.master == grains.id.split('_')|first -%}
source /usr/sbin/so-common
pillar_file="/opt/so/saltstack/local/pillar/minions/{{grains.id}}.sls"
if [ -f "$pillar_file" ]; then
if ! grep -q "^workstation:$" "$pillar_file"; then
FIRSTPASS=yes
while [[ $INSTALL != "yes" ]] && [[ $INSTALL != "no" ]]; do
if [[ "$FIRSTPASS" == "yes" ]]; then
echo "###########################################"
echo "## ** W A R N I N G ** ##"
echo "## _______________________________ ##"
echo "## ##"
echo "## Installing the Security Onion ##"
echo "## analyst node on this device will ##"
echo "## make permanent changes to ##"
echo "## the system. ##"
echo "## A system reboot will be required ##"
echo "## to complete the install. ##"
echo "## ##"
echo "###########################################"
echo "Do you wish to continue? (Type the entire word 'yes' to proceed or 'no' to exit)"
FIRSTPASS=no
else
echo "Please type 'yes' to continue or 'no' to exit."
fi
read INSTALL
done
if [[ $INSTALL == "no" ]]; then
echo "Exiting analyst node installation."
exit 0
fi
# Add workstation pillar to the minion's pillar file
printf '%s\n'\
"workstation:"\
" gui:"\
" enabled: true"\
"" >> "$pillar_file"
echo "Applying the workstation state. This could take some time since there are many packages that need to be installed."
if salt-call state.apply workstation -linfo queue=True; then # make sure the state ran successfully
echo ""
echo "Analyst workstation has been installed!"
echo "Press ENTER to reboot or Ctrl-C to cancel."
read pause
reboot;
else
echo "There was an issue applying the workstation state. Please review the log above or at /opt/so/logs/salt/minion."
fi
else # workstation is already added
echo "The workstation pillar already exists in $pillar_file."
echo "To enable/disable the gui, set 'workstation:gui:enabled' to true or false in $pillar_file."
echo "Additional documentation can be found at $doc_workstation_url."
fi
else # if the pillar file doesn't exist
echo "Could not find $pillar_file and add the workstation pillar."
fi
{#- if this is not a manager #}
{% else -%}
echo "Since this is not a manager, the pillar values to enable analyst workstation must be set manually. Please view the documentation at $doc_workstation_url."
{#- endif if this is a manager #}
{% endif -%}
{#- if not CentOS #}
{%- else %}
echo "The Analyst Workstation can only be installed on CentOS. Please view the documentation at $doc_workstation_url."
{#- endif grains.os == CentOS #}
{% endif -%}
exit 0

View File

@@ -29,26 +29,9 @@ fi
interface="$1" interface="$1"
shift shift
tcpdump -i $interface -ddd $@ | tail -n+2 |
# Capture tcpdump output and exit code while read line; do
tcpdump_output=$(tcpdump -i "$interface" -ddd "$@" 2>&1)
tcpdump_exit=$?
if [ $tcpdump_exit -ne 0 ]; then
echo "$tcpdump_output" >&2
exit $tcpdump_exit
fi
# Process the output, skipping the first line
echo "$tcpdump_output" | tail -n+2 | while read -r line; do
cols=( $line ) cols=( $line )
printf "%04x%02x%02x%08x" "${cols[0]}" "${cols[1]}" "${cols[2]}" "${cols[3]}" printf "%04x%02x%02x%08x" ${cols[0]} ${cols[1]} ${cols[2]} ${cols[3]}
done done
# Check if the pipeline succeeded
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
exit 1
fi
echo "" echo ""
exit 0

View File

@@ -1,17 +1,20 @@
#!/bin/bash #!/bin/bash
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one # Copyright 2014-2022 Security Onion Solutions, LLC
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at #
# https://securityonion.net/license; you may not use this file except in compliance with the # This program is free software: you can redistribute it and/or modify
# Elastic License 2.0. # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common . /usr/sbin/so-common
cat << EOF salt-call state.highstate -l info
so-checkin will run a full salt highstate to apply all salt states. If a highstate is already running, this request will be queued and so it may pause for a few minutes before you see any more output. For more information about so-checkin and salt, please see:
https://docs.securityonion.net/en/2.4/salt.html
EOF
salt-call state.highstate -l info queue=True

View File

@@ -1,33 +1,26 @@
#!/bin/bash #!/bin/bash
# #
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one # Copyright 2014-2022 Security Onion Solutions, LLC
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at #
# https://securityonion.net/license; you may not use this file except in compliance with the # This program is free software: you can redistribute it and/or modify
# Elastic License 2.0. # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# Elastic agent is not managed by salt. Because of this we must store this base information in a # (at your option) any later version.
# script that accompanies the soup system. Since so-common is one of those special soup files, #
# and since this same logic is required during installation, it's included in this file. # This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
DEFAULT_SALT_DIR=/opt/so/saltstack/default DEFAULT_SALT_DIR=/opt/so/saltstack/default
DOC_BASE_URL="https://docs.securityonion.net/en/2.4"
if [ -z $NOROOT ]; then # Check for prerequisites
# Check for prerequisites if [ "$(id -u)" -ne 0 ]; then
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run using sudo!" echo "This script must be run using sudo!"
exit 1 exit 1
fi
fi
# Ensure /usr/sbin is in path
if ! echo "$PATH" | grep -q "/usr/sbin"; then
export PATH="$PATH:/usr/sbin"
fi
# See if a proxy is set. If so use it.
if [ -f /etc/profile.d/so-proxy.sh ]; then
. /etc/profile.d/so-proxy.sh
fi fi
# Define a banner to separate sections # Define a banner to separate sections
@@ -63,8 +56,6 @@ add_interface_bond0() {
ethtool -K "$BNIC" $i off &>/dev/null ethtool -K "$BNIC" $i off &>/dev/null
fi fi
done done
if ! [[ $is_cloud ]]; then
# Check if the bond slave connection has already been created # Check if the bond slave connection has already been created
nmcli -f name,uuid -p con | grep -q "bond0-slave-$BNIC" nmcli -f name,uuid -p con | grep -q "bond0-slave-$BNIC"
local found_int=$? local found_int=$?
@@ -82,34 +73,21 @@ add_interface_bond0() {
ethernet.mtu "$MTU" \ ethernet.mtu "$MTU" \
connection.autoconnect "yes" connection.autoconnect "yes"
fi fi
fi
ip link set dev "$BNIC" arp off multicast off allmulticast off promisc on ip link set dev "$BNIC" arp off multicast off allmulticast off promisc on
if ! [[ $is_cloud ]]; then
# Bring the slave interface up # Bring the slave interface up
if [[ $verbose == true ]]; then if [[ $verbose == true ]]; then
nmcli con up "bond0-slave-$BNIC" nmcli con up "bond0-slave-$BNIC"
else else
nmcli con up "bond0-slave-$BNIC" &>/dev/null nmcli con up "bond0-slave-$BNIC" &>/dev/null
fi fi
fi
if [ "$nic_error" != 0 ]; then if [ "$nic_error" != 0 ]; then
return "$nic_error" return "$nic_error"
fi fi
} }
airgap_playbooks() {
SRC_DIR=$1
# Copy playbooks if using airgap
mkdir -p /nsm/airgap-resources
# Purge old airgap playbooks to ensure SO only uses the latest released playbooks
rm -fr /nsm/airgap-resources/playbooks
tar xf $SRC_DIR/airgap-resources/playbooks.tgz -C /nsm/airgap-resources/
chown -R socore:socore /nsm/airgap-resources/playbooks
git config --global --add safe.directory /nsm/airgap-resources/playbooks
}
check_container() { check_container() {
docker ps | grep "$1:" > /dev/null 2>&1 docker ps | grep "$1:" > /dev/null 2>&1
return $? return $?
@@ -143,159 +121,56 @@ check_elastic_license() {
} }
check_salt_master_status() { check_salt_master_status() {
local count=0 local timeout=$1
local attempts="${1:- 10}" echo "Checking if we can talk to the salt master"
current_time="$(date '+%b %d %H:%M:%S')" salt-call state.show_top concurrent=true
echo "Checking if we can access the salt master and that it is ready at: ${current_time}"
while ! salt-call state.show_top -l error concurrent=true 1> /dev/null; do return
current_time="$(date '+%b %d %H:%M:%S')"
echo "Can't access salt master or it is not ready at: ${current_time}"
((count+=1))
if [[ $count -eq $attempts ]]; then
# 10 attempts takes about 5.5 minutes
echo "Gave up trying to access salt-master"
return 1
fi
done
current_time="$(date '+%b %d %H:%M:%S')"
echo "Successfully accessed and salt master ready at: ${current_time}"
return 0
} }
# this is only intended to be used to check the status of the minion from a salt master
check_salt_minion_status() { check_salt_minion_status() {
local minion="$1" local timeout=$1
local timeout="${2:-5}" echo "Checking if the salt minion will respond to jobs" >> "$setup_log" 2>&1
local logfile="${3:-'/dev/stdout'}" salt "$MINION_ID" test.ping -t $timeout > /dev/null 2>&1
echo "Checking if the salt minion: $minion will respond to jobs" >> "$logfile" 2>&1
salt "$minion" test.ping -t $timeout > /dev/null 2>&1
local status=$? local status=$?
if [ $status -gt 0 ]; then if [ $status -gt 0 ]; then
echo " Minion did not respond" >> "$logfile" 2>&1 echo " Minion did not respond" >> "$setup_log" 2>&1
else else
echo " Received job response from salt minion" >> "$logfile" 2>&1 echo " Received job response from salt minion" >> "$setup_log" 2>&1
fi fi
return $status return $status
} }
# Compare es versions and return the highest version
compare_es_versions() {
# Save the original IFS
local OLD_IFS="$IFS"
IFS=.
local i ver1=($1) ver2=($2)
# Restore the original IFS
IFS="$OLD_IFS"
# Determine the maximum length between the two version arrays
local max_len=${#ver1[@]}
if [[ ${#ver2[@]} -gt $max_len ]]; then
max_len=${#ver2[@]}
fi
# Compare each segment of the versions
for ((i=0; i<max_len; i++)); do
# If a segment in ver1 or ver2 is missing, set it to 0
if [[ -z ${ver1[i]} ]]; then
ver1[i]=0
fi
if [[ -z ${ver2[i]} ]]; then
ver2[i]=0
fi
if ((10#${ver1[i]} > 10#${ver2[i]})); then
echo "$1"
return 0
fi
if ((10#${ver1[i]} < 10#${ver2[i]})); then
echo "$2"
return 0
fi
done
echo "$1" # If versions are equal, return either
return 0
}
copy_new_files() { copy_new_files() {
# Define files to exclude from deletion (relative to their respective base directories)
local EXCLUDE_FILES=(
"salt/hypervisor/soc_hypervisor.yaml"
)
# Build rsync exclude arguments
local EXCLUDE_ARGS=()
for file in "${EXCLUDE_FILES[@]}"; do
EXCLUDE_ARGS+=(--exclude="$file")
done
# Copy new files over to the salt dir # Copy new files over to the salt dir
cd $UPDATE_DIR cd $UPDATE_DIR
rsync -a salt $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}" rsync -a salt $DEFAULT_SALT_DIR/
rsync -a pillar $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}" rsync -a pillar $DEFAULT_SALT_DIR/
chown -R socore:socore $DEFAULT_SALT_DIR/ chown -R socore:socore $DEFAULT_SALT_DIR/
chmod 755 $DEFAULT_SALT_DIR/pillar/firewall/addfirewall.sh
cd /tmp cd /tmp
} }
create_local_directories() {
echo "Creating local pillar and salt directories if needed"
PILLARSALTDIR=$1
local_salt_dir="/opt/so/saltstack/local"
for i in "pillar" "salt"; do
for d in $(find $PILLARSALTDIR/$i -type d); do
suffixdir=${d//$PILLARSALTDIR/}
if [ ! -d "$local_salt_dir/$suffixdir" ]; then
mkdir -p $local_salt_dir$suffixdir
fi
done
chown -R socore:socore $local_salt_dir/$i
done
}
disable_fastestmirror() { disable_fastestmirror() {
sed -i 's/enabled=1/enabled=0/' /etc/yum/pluginconf.d/fastestmirror.conf sed -i 's/enabled=1/enabled=0/' /etc/yum/pluginconf.d/fastestmirror.conf
} }
download_and_verify() {
source_url=$1
source_md5_url=$2
dest_file=$3
md5_file=$4
expand_dir=$5
if [[ -n "$expand_dir" ]]; then
mkdir -p "$expand_dir"
fi
if ! verify_md5_checksum "$dest_file" "$md5_file"; then
retry 15 10 "curl --fail --retry 5 --retry-delay 15 -L '$source_url' --output '$dest_file'" "" ""
retry 15 10 "curl --fail --retry 5 --retry-delay 15 -L '$source_md5_url' --output '$md5_file'" "" ""
if verify_md5_checksum "$dest_file" "$md5_file"; then
echo "Source file and checksum are good."
else
echo "Unable to download and verify the source file and checksum."
return 1
fi
fi
if [[ -n "$expand_dir" ]]; then
tar -xf "$dest_file" -C "$expand_dir"
fi
}
elastic_license() { elastic_license() {
read -r -d '' message <<- EOM read -r -d '' message <<- EOM
\n \n
Elastic Stack binaries and Security Onion components are only available under the Elastic License version 2 (ELv2): Starting in Elastic Stack version 7.11, the Elastic Stack binaries are only available under the Elastic License:
https://securityonion.net/license/ https://securityonion.net/elastic-license
Do you agree to the terms of ELv2? Please review the Elastic License:
https://www.elastic.co/licensing/elastic-license
If so, type AGREE to accept ELv2 and continue. Otherwise, press Enter to exit this program without making any changes. Do you agree to the terms of the Elastic License?
If so, type AGREE to accept the Elastic License and continue. Otherwise, press Enter to exit this program without making any changes.
EOM EOM
AGREED=$(whiptail --title "$whiptail_title" --inputbox \ AGREED=$(whiptail --title "$whiptail_title" --inputbox \
@@ -318,51 +193,25 @@ fail() {
exit 1 exit 1
} }
get_agent_count() {
if [ -f /opt/so/log/agents/agentstatus.log ]; then
AGENTCOUNT=$(cat /opt/so/log/agents/agentstatus.log | grep -wF active | awk '{print $2}' | sed 's/,//')
[[ -z "$AGENTCOUNT" ]] && AGENTCOUNT="0"
else
AGENTCOUNT=0
fi
}
get_elastic_agent_vars() {
local path="${1:-/opt/so/saltstack/default}"
local defaultsfile="${path}/salt/elasticsearch/defaults.yaml"
if [ -f "$defaultsfile" ]; then
ELASTIC_AGENT_TARBALL_VERSION=$(egrep " +version: " $defaultsfile | awk -F: '{print $2}' | tr -d '[:space:]')
ELASTIC_AGENT_URL="https://repo.securityonion.net/file/so-repo/prod/2.4/elasticagent/elastic-agent_SO-$ELASTIC_AGENT_TARBALL_VERSION.tar.gz"
ELASTIC_AGENT_MD5_URL="https://repo.securityonion.net/file/so-repo/prod/2.4/elasticagent/elastic-agent_SO-$ELASTIC_AGENT_TARBALL_VERSION.md5"
ELASTIC_AGENT_FILE="/nsm/elastic-fleet/artifacts/elastic-agent_SO-$ELASTIC_AGENT_TARBALL_VERSION.tar.gz"
ELASTIC_AGENT_MD5="/nsm/elastic-fleet/artifacts/elastic-agent_SO-$ELASTIC_AGENT_TARBALL_VERSION.md5"
ELASTIC_AGENT_EXPANSION_DIR=/nsm/elastic-fleet/artifacts/beats/elastic-agent
else
fail "Could not find salt/elasticsearch/defaults.yaml"
fi
}
get_random_value() { get_random_value() {
length=${1:-20} length=${1:-20}
head -c 5000 /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $length | head -n 1 head -c 5000 /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $length | head -n 1
} }
gpg_rpm_import() { gpg_rpm_import() {
if [[ $is_oracle ]]; then if [[ "$OS" == "centos" ]]; then
if [[ "$WHATWOULDYOUSAYYAHDOHERE" == "setup" ]]; then if [[ "$WHATWOULDYOUSAYYAHDOHERE" == "setup" ]]; then
local RPMKEYSLOC="../salt/repo/client/files/$OS/keys" local RPMKEYSLOC="../salt/repo/client/files/centos/keys"
else else
local RPMKEYSLOC="$UPDATE_DIR/salt/repo/client/files/$OS/keys" local RPMKEYSLOC="$UPDATE_DIR/salt/repo/client/files/centos/keys"
fi fi
RPMKEYS=('RPM-GPG-KEY-oracle' 'RPM-GPG-KEY-EPEL-9' 'SALT-PROJECT-GPG-PUBKEY-2023.pub' 'docker.pub' 'securityonion.pub')
RPMKEYS=('RPM-GPG-KEY-EPEL-7' 'GPG-KEY-WAZUH' 'docker.pub' 'SALTSTACK-GPG-KEY.pub' 'securityonion.pub')
for RPMKEY in "${RPMKEYS[@]}"; do for RPMKEY in "${RPMKEYS[@]}"; do
rpm --import $RPMKEYSLOC/$RPMKEY rpm --import $RPMKEYSLOC/$RPMKEY
echo "Imported $RPMKEY" echo "Imported $RPMKEY"
done done
elif [[ $is_rpm ]]; then
echo "Importing the security onion GPG key"
rpm --import ../salt/repo/client/files/oracle/keys/securityonion.pub
fi fi
} }
@@ -375,15 +224,12 @@ init_monitor() {
if [[ $MONITORNIC == "bond0" ]]; then if [[ $MONITORNIC == "bond0" ]]; then
BIFACES=$(lookup_bond_interfaces) BIFACES=$(lookup_bond_interfaces)
for i in rx tx sg tso ufo gso gro lro rx-vlan-offload tx-vlan-offload generic-receive-offload generic-segmentation-offload tcp-segmentation-offload; do
ethtool -K "$MONITORNIC" "$i" off;
done
else else
BIFACES=$MONITORNIC BIFACES=$MONITORNIC
fi fi
for DEVICE_IFACE in $BIFACES; do for DEVICE_IFACE in $BIFACES; do
for i in rx tx sg tso ufo gso gro lro rx-vlan-offload tx-vlan-offload generic-receive-offload generic-segmentation-offload tcp-segmentation-offload; do for i in rx tx sg tso ufo gso gro lro; do
ethtool -K "$DEVICE_IFACE" "$i" off; ethtool -K "$DEVICE_IFACE" "$i" off;
done done
ip link set dev "$DEVICE_IFACE" arp off multicast off allmulticast off promisc on ip link set dev "$DEVICE_IFACE" arp off multicast off allmulticast off promisc on
@@ -391,17 +237,31 @@ init_monitor() {
} }
is_manager_node() { is_manager_node() {
grep "role: so-" /etc/salt/grains | grep -E "manager|eval|managersearch|standalone|import" &> /dev/null # Check to see if this is a manager node
role=$(lookup_role)
is_single_node_grid && return 0
[ $role == 'manager' ] && return 0
[ $role == 'managersearch' ] && return 0
[ $role == 'helix' ] && return 0
return 1
} }
is_sensor_node() { is_sensor_node() {
# Check to see if this is a sensor node # Check to see if this is a sensor (forward) node
role=$(lookup_role)
is_single_node_grid && return 0 is_single_node_grid && return 0
grep "role: so-" /etc/salt/grains | grep -E "sensor|heavynode" &> /dev/null [ $role == 'sensor' ] && return 0
[ $role == 'heavynode' ] && return 0
[ $role == 'helix' ] && return 0
return 1
} }
is_single_node_grid() { is_single_node_grid() {
grep "role: so-" /etc/salt/grains | grep -E "eval|standalone|import" &> /dev/null role=$(lookup_role)
[ $role == 'eval' ] && return 0
[ $role == 'standalone' ] && return 0
[ $role == 'import' ] && return 0
return 1
} }
lookup_bond_interfaces() { lookup_bond_interfaces() {
@@ -429,7 +289,7 @@ lookup_salt_value() {
local="" local=""
fi fi
salt-call -lerror --no-color ${kind}.get ${group}${key} --out=${output} ${local} salt-call --no-color ${kind}.get ${group}${key} --out=${output} ${local}
} }
lookup_pillar() { lookup_pillar() {
@@ -451,25 +311,8 @@ lookup_grain() {
lookup_role() { lookup_role() {
id=$(lookup_grain id) id=$(lookup_grain id)
echo "${id##*_}" pieces=($(echo $id | tr '_' ' '))
} echo ${pieces[1]}
is_feature_enabled() {
feature=$1
enabled=$(lookup_salt_value features)
for cur in $enabled; do
if [[ "$feature" == "$cur" ]]; then
return 0
fi
done
return 1
}
read_feat() {
if [ -f /opt/so/log/sostatus/lks_enabled ]; then
lic_id=$(cat /opt/so/saltstack/local/pillar/soc/license.sls | grep license_id: | awk '{print $2}')
echo "$lic_id/$(cat /opt/so/log/sostatus/lks_enabled)/$(cat /opt/so/log/sostatus/fps_enabled)"
fi
} }
require_manager() { require_manager() {
@@ -503,10 +346,6 @@ retry() {
echo "<Start of output>" echo "<Start of output>"
echo "$output" echo "$output"
echo "<End of output>" echo "<End of output>"
if [[ $exitcode -eq 0 ]]; then
echo "Forcing exit code to 1"
exitcode=1
fi
fi fi
elif [ -n "$failedOutput" ]; then elif [ -n "$failedOutput" ]; then
if [[ "$output" =~ "$failedOutput" ]]; then if [[ "$output" =~ "$failedOutput" ]]; then
@@ -515,7 +354,7 @@ retry() {
echo "$output" echo "$output"
echo "<End of output>" echo "<End of output>"
if [[ $exitcode -eq 0 ]]; then if [[ $exitcode -eq 0 ]]; then
echo "Forcing exit code to 1" echo "The exitcode was 0, but we are setting to 1 since we found $failedOutput in the output."
exitcode=1 exitcode=1
fi fi
else else
@@ -553,82 +392,19 @@ run_check_net_err() {
fi fi
} }
wait_for_salt_minion() { set_cron_service_name() {
local minion="$1" if [[ "$OS" == "centos" ]]; then
local timeout="${2:-5}" cron_service_name="crond"
local logfile="${3:-'/dev/stdout'}" else
retry 60 5 "journalctl -u salt-minion.service | grep 'Minion is ready to receive requests'" >> "$logfile" 2>&1 || fail cron_service_name="cron"
local attempt=0
# each attempts would take about 15 seconds
local maxAttempts=20
until check_salt_minion_status "$minion" "$timeout" "$logfile"; do
attempt=$((attempt+1))
if [[ $attempt -eq $maxAttempts ]]; then
return 1
fi fi
sleep 10
done
return 0
}
salt_minion_count() {
local MINIONDIR="/opt/so/saltstack/local/pillar/minions"
MINIONCOUNT=$(ls -la $MINIONDIR/*.sls | grep -v adv_ | wc -l)
} }
set_os() { set_os() {
if [ -f /etc/redhat-release ]; then if [ -f /etc/redhat-release ]; then
if grep -q "Rocky Linux release 9" /etc/redhat-release; then
OS=rocky
OSVER=9
is_rocky=true
is_rpm=true
elif grep -q "CentOS Stream release 9" /etc/redhat-release; then
OS=centos OS=centos
OSVER=9
is_centos=true
is_rpm=true
elif grep -q "AlmaLinux release 9" /etc/redhat-release; then
OS=alma
OSVER=9
is_alma=true
is_rpm=true
elif grep -q "Red Hat Enterprise Linux release 9" /etc/redhat-release; then
if [ -f /etc/oracle-release ]; then
OS=oracle
OSVER=9
is_oracle=true
is_rpm=true
else else
OS=rhel
OSVER=9
is_rhel=true
is_rpm=true
fi
fi
cron_service_name="crond"
elif [ -f /etc/os-release ]; then
if grep -q "UBUNTU_CODENAME=focal" /etc/os-release; then
OSVER=focal
UBVER=20.04
OS=ubuntu OS=ubuntu
is_ubuntu=true
is_deb=true
elif grep -q "UBUNTU_CODENAME=jammy" /etc/os-release; then
OSVER=jammy
UBVER=22.04
OS=ubuntu
is_ubuntu=true
is_deb=true
elif grep -q "VERSION_CODENAME=bookworm" /etc/os-release; then
OSVER=bookworm
DEBVER=12
is_debian=true
OS=debian
is_deb=true
fi
cron_service_name="cron"
fi fi
} }
@@ -637,7 +413,7 @@ set_minionid() {
} }
set_palette() { set_palette() {
if [[ $is_deb ]]; then if [ "$OS" == ubuntu ]; then
update-alternatives --set newt-palette /etc/newt/palette.original update-alternatives --set newt-palette /etc/newt/palette.original
fi fi
} }
@@ -661,19 +437,6 @@ set_version() {
fi fi
} }
status () {
printf "\n=========================================================================\n$(date) | $1\n=========================================================================\n"
}
sync_options() {
set_version
set_os
salt_minion_count
get_agent_count
echo "$VERSION/$OS/$(uname -r)/$MINIONCOUNT:$AGENTCOUNT/$(read_feat)"
}
systemctl_func() { systemctl_func() {
local action=$1 local action=$1
local echo_action=$1 local echo_action=$1
@@ -697,13 +460,6 @@ has_uppercase() {
|| return 1 || return 1
} }
update_elastic_agent() {
local path="${1:-/opt/so/saltstack/default}"
get_elastic_agent_vars "$path"
echo "Checking if Elastic Agent update is necessary..."
download_and_verify "$ELASTIC_AGENT_URL" "$ELASTIC_AGENT_MD5_URL" "$ELASTIC_AGENT_FILE" "$ELASTIC_AGENT_MD5" "$ELASTIC_AGENT_EXPANSION_DIR"
}
valid_cidr() { valid_cidr() {
# Verify there is a backslash in the string # Verify there is a backslash in the string
echo "$1" | grep -qP "^[^/]+/[^/]+$" || return 1 echo "$1" | grep -qP "^[^/]+/[^/]+$" || return 1
@@ -762,18 +518,6 @@ valid_hostname() {
[[ $hostname =~ ^[a-zA-Z0-9\-]+$ ]] && [[ $hostname != 'localhost' ]] && return 0 || return 1 [[ $hostname =~ ^[a-zA-Z0-9\-]+$ ]] && [[ $hostname != 'localhost' ]] && return 0 || return 1
} }
verify_ip4() {
local ip=$1
# Is this an IP or CIDR?
if grep -qP "^[^/]+/[^/]+$" <<< $ip; then
# Looks like a CIDR
valid_ip4_cidr_mask "$ip"
else
# We know this is not a CIDR - Is it an IP?
valid_ip4 "$ip"
fi
}
valid_ip4() { valid_ip4() {
local ip=$1 local ip=$1
@@ -857,23 +601,6 @@ valid_username() {
echo "$user" | grep -qP '^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$' && return 0 || return 1 echo "$user" | grep -qP '^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$' && return 0 || return 1
} }
verify_md5_checksum() {
data_file=$1
md5_file=${2:-${data_file}.md5}
if [[ ! -f "$dest_file" || ! -f "$md5_file" ]]; then
return 2
fi
SOURCEHASH=$(md5sum "$data_file" | awk '{ print $1 }')
HASH=$(cat "$md5_file")
if [[ "$HASH" == "$SOURCEHASH" ]]; then
return 0
fi
return 1
}
wait_for_web_response() { wait_for_web_response() {
url=$1 url=$1
expected=$2 expected=$2

View File

@@ -1,103 +0,0 @@
#!/usr/bin/env python3
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at
# https://securityonion.net/license; you may not use this file except in compliance with the
# Elastic License 2.0.
import sys
import subprocess
import os
import json
sys.path.append('/opt/saltstack/salt/lib/python3.10/site-packages/')
import salt.config
import salt.loader
__opts__ = salt.config.minion_config('/etc/salt/minion')
__grains__ = salt.loader.grains(__opts__)
def check_needs_restarted():
osfam = __grains__['os_family']
val = '0'
outfile = "/opt/so/log/sostatus/needs_restarted"
if osfam == 'Debian':
if os.path.exists('/var/run/reboot-required'):
val = '1'
elif osfam == 'RedHat':
cmd = 'needs-restarting -r > /dev/null 2>&1'
try:
needs_restarting = subprocess.check_call(cmd, shell=True)
except subprocess.CalledProcessError:
val = '1'
else:
fail("Unsupported OS")
with open(outfile, 'w') as f:
f.write(val)
def check_for_fps():
feat = 'fps'
feat_full = feat.replace('ps', 'ips')
fps = 0
try:
result = subprocess.run([feat_full + '-mode-setup', '--is-enabled'], stdout=subprocess.PIPE)
if result.returncode == 0:
fps = 1
except:
fn = '/proc/sys/crypto/' + feat_full + '_enabled'
try:
with open(fn, 'r') as f:
contents = f.read()
if '1' in contents:
fps = 1
except:
# Unknown, so assume 0
fps = 0
with open('/opt/so/log/sostatus/fps_enabled', 'w') as f:
f.write(str(fps))
def check_for_lks():
feat = 'Lks'
feat_full = feat.replace('ks', 'uks')
lks = 0
result = subprocess.run(['lsblk', '-p', '-J'], check=True, stdout=subprocess.PIPE)
data = json.loads(result.stdout)
for device in data['blockdevices']:
if 'children' in device:
for gc in device['children']:
if 'children' in gc:
try:
arg = 'is' + feat_full
result = subprocess.run(['cryptsetup', arg, gc['name']], stdout=subprocess.PIPE)
if result.returncode == 0:
lks = 1
except FileNotFoundError:
for ggc in gc['children']:
if 'crypt' in ggc['type']:
lks = 1
if lks:
break
with open('/opt/so/log/sostatus/lks_enabled', 'w') as f:
f.write(str(lks))
def fail(msg):
print(msg, file=sys.stderr)
sys.exit(1)
def main():
proc = subprocess.run(['id', '-u'], stdout=subprocess.PIPE, encoding="utf-8")
if proc.stdout.strip() != "0":
fail("This program must be run as root")
# Ensure that umask is 0022 so that files created by this script have rw-r-r permissions
org_umask = os.umask(0o022)
check_needs_restarted()
check_for_fps()
check_for_lks()
# Restore umask to whatever value was set before this script was run. SXIG sets to 0077 rw---
os.umask(org_umask)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,48 @@
#!/bin/bash
#
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.. /usr/sbin/so-common
{% set BACKUPLOCATIONS = salt['pillar.get']('backup:locations', {}) %}
TODAY=$(date '+%Y_%m_%d')
BACKUPFILE="/nsm/backup/so-config-backup-$TODAY.tar"
MAXBACKUPS=7
# Create backup dir if it does not exist
mkdir -p /nsm/backup
# If we haven't already written a backup file for today, let's do so
if [ ! -f $BACKUPFILE ]; then
# Create empty backup file
tar -cf $BACKUPFILE -T /dev/null
# Loop through all paths defined in global.sls, and append them to backup file
{%- for LOCATION in BACKUPLOCATIONS %}
tar -rf $BACKUPFILE {{ LOCATION }}
{%- endfor %}
tar -rf $BACKUPFILE /etc/pki
tar -rf $BACKUPFILE /etc/salt
tar -rf $BACKUPFILE /opt/so/conf/kratos
fi
# Find oldest backup files and remove them
NUMBACKUPS=$(find /nsm/backup/ -type f -name "so-config-backup*" | wc -l)
while [ "$NUMBACKUPS" -gt "$MAXBACKUPS" ]; do
OLDESTBACKUP=$(find /nsm/backup/ -type f -name "so-config-backup*" -type f -printf '%T+ %p\n' | sort | head -n 1 | awk -F" " '{print $2}')
rm -f $OLDESTBACKUP
NUMBACKUPS=$(find /nsm/backup/ -type f -name "so-config-backup*" | wc -l)
done

View File

@@ -0,0 +1,20 @@
#!/bin/bash
#
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
echo "TheHive and its components are no longer part of Security Onion"

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
echo "TheHive and its components are no longer part of Security Onion"

View File

@@ -0,0 +1,20 @@
#!/bin/bash
#
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
echo "TheHive and its components are no longer part of Security Onion"

View File

@@ -0,0 +1,20 @@
#!/bin/bash
#
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
echo "TheHive and its components are no longer part of Security Onion"

View File

@@ -0,0 +1,20 @@
#!/bin/bash
#
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
echo "TheHive and its components are no longer part of Security Onion"

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
/usr/sbin/so-restart curator $1

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
/usr/sbin/so-start curator $1

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
/usr/sbin/so-stop curator $1

213
salt/common/tools/sbin/so-deny Executable file
View File

@@ -0,0 +1,213 @@
#!/usr/bin/env python3
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import ipaddress
import textwrap
import os
import subprocess
import sys
import argparse
import re
from lxml import etree as ET
from xml.dom import minidom
LOCAL_SALT_DIR='/opt/so/saltstack/local'
WAZUH_CONF='/nsm/wazuh/etc/ossec.conf'
VALID_ROLES = {
'a': { 'role': 'analyst','desc': 'Analyst - 80/tcp, 443/tcp' },
'b': { 'role': 'beats_endpoint', 'desc': 'Logstash Beat - 5044/tcp' },
'e': { 'role': 'elasticsearch_rest', 'desc': 'Elasticsearch REST API - 9200/tcp' },
'f': { 'role': 'strelka_frontend', 'desc': 'Strelka frontend - 57314/tcp' },
'o': { 'role': 'osquery_endpoint', 'desc': 'Osquery endpoint - 8090/tcp' },
's': { 'role': 'syslog', 'desc': 'Syslog device - 514/tcp/udp' },
'w': { 'role': 'wazuh_agent', 'desc': 'Wazuh agent - 1514/tcp/udp' },
'p': { 'role': 'wazuh_api', 'desc': 'Wazuh API - 55000/tcp' },
'r': { 'role': 'wazuh_authd', 'desc': 'Wazuh registration service - 1515/tcp' }
}
def validate_ip_cidr(ip_cidr: str) -> bool:
try:
ipaddress.ip_address(ip_cidr)
except ValueError:
try:
ipaddress.ip_network(ip_cidr)
except ValueError:
return False
return True
def role_prompt() -> str:
print()
print('Choose the role for the IP or Range you would like to deny')
print()
for role in VALID_ROLES:
print(f'[{role}] - {VALID_ROLES[role]["desc"]}')
print()
role = input('Please enter your selection: ')
if role in VALID_ROLES.keys():
return VALID_ROLES[role]['role']
else:
print(f'Invalid role \'{role}\', please try again.', file=sys.stderr)
sys.exit(1)
def ip_prompt() -> str:
ip = input('Enter a single ip address or range to deny (ex: 10.10.10.10 or 10.10.0.0/16): ')
if validate_ip_cidr(ip):
return ip
else:
print(f'Invalid IP address or CIDR block \'{ip}\', please try again.', file=sys.stderr)
sys.exit(1)
def wazuh_enabled() -> bool:
for file in os.listdir(f'{LOCAL_SALT_DIR}/pillar'):
with open(file, 'r') as pillar:
if 'wazuh: 1' in pillar.read():
return True
return False
def root_to_str(root: ET.ElementTree) -> str:
xml_str = ET.tostring(root, encoding='unicode', method='xml').replace('\n', '')
xml_str = re.sub(r'(?:(?<=>) *)', '', xml_str)
# Remove specific substrings to better format comments on intial parse/write
xml_str = re.sub(r' -', '', xml_str)
xml_str = re.sub(r' -->', ' -->', xml_str)
dom = minidom.parseString(xml_str)
return dom.toprettyxml(indent=" ")
def rem_wl(ip):
parser = ET.XMLParser(remove_blank_text=True)
with open(WAZUH_CONF, 'rb') as wazuh_conf:
tree = ET.parse(wazuh_conf, parser)
root = tree.getroot()
global_elems = root.findall(f"global/white_list[. = '{ip}']/..")
if len(global_elems) > 0:
for g_elem in global_elems:
ge_index = list(root).index(g_elem)
if ge_index > 0 and root[list(root).index(g_elem) - 1].tag == ET.Comment:
root.remove(root[ge_index - 1])
root.remove(g_elem)
with open(WAZUH_CONF, 'w') as out:
out.write(root_to_str(root))
def apply(role: str, ip: str) -> int:
firewall_cmd = ['so-firewall', 'excludehost', role, ip]
salt_cmd = ['salt-call', 'state.apply', '-l', 'quiet', 'firewall', 'queue=True']
restart_wazuh_cmd = ['so-wazuh-restart']
print(f'Removing {ip} from the {role} role. This can take a few seconds...')
cmd = subprocess.run(firewall_cmd)
if cmd.returncode == 0:
cmd = subprocess.run(salt_cmd, stdout=subprocess.DEVNULL)
else:
return cmd.returncode
if cmd.returncode == 0:
if wazuh_enabled and role=='analyst':
try:
rem_wl(ip)
print(f'Removed whitelist entry for {ip} from {WAZUH_CONF}', file=sys.stderr)
except Exception as e:
print(f'Failed to remove whitelist entry for {ip} from {WAZUH_CONF}', file=sys.stderr)
print(e)
return 1
print('Restarting OSSEC Server...')
cmd = subprocess.run(restart_wazuh_cmd)
else:
return cmd.returncode
else:
print(f'Commmand \'{" ".join(salt_cmd)}\' failed.', file=sys.stderr)
return cmd.returncode
if cmd.returncode != 0:
print('Failed to restart OSSEC server.')
return cmd.returncode
def main():
if os.geteuid() != 0:
print('You must run this script as root', file=sys.stderr)
sys.exit(1)
main_parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent(f'''\
additional information:
To use this script in interactive mode call it with no arguments
'''
))
group = main_parser.add_argument_group(title='roles')
group.add_argument('-a', dest='roles', action='append_const', const=VALID_ROLES['a']['role'], help="Analyst - 80/tcp, 443/tcp")
group.add_argument('-b', dest='roles', action='append_const', const=VALID_ROLES['b']['role'], help="Logstash Beat - 5044/tcp")
group.add_argument('-e', dest='roles', action='append_const', const=VALID_ROLES['e']['role'], help="Elasticsearch REST API - 9200/tcp")
group.add_argument('-f', dest='roles', action='append_const', const=VALID_ROLES['f']['role'], help="Strelka frontend - 57314/tcp")
group.add_argument('-o', dest='roles', action='append_const', const=VALID_ROLES['o']['role'], help="Osquery endpoint - 8090/tcp")
group.add_argument('-s', dest='roles', action='append_const', const=VALID_ROLES['s']['role'], help="Syslog device - 514/tcp/udp")
group.add_argument('-w', dest='roles', action='append_const', const=VALID_ROLES['w']['role'], help="Wazuh agent - 1514/tcp/udp")
group.add_argument('-p', dest='roles', action='append_const', const=VALID_ROLES['p']['role'], help="Wazuh API - 55000/tcp")
group.add_argument('-r', dest='roles', action='append_const', const=VALID_ROLES['r']['role'], help="Wazuh registration service - 1515/tcp")
ip_g = main_parser.add_argument_group(title='allow')
ip_g.add_argument('-i', help="IP or CIDR block to disallow connections from, requires at least one role argument", metavar='', dest='ip')
args = main_parser.parse_args(sys.argv[1:])
if args.roles is None:
role = role_prompt()
ip = ip_prompt()
try:
return_code = apply(role, ip)
except Exception as e:
print(f'Unexpected exception occurred: {e}', file=sys.stderr)
return_code = e.errno
sys.exit(return_code)
elif args.roles is not None and args.ip is None:
if os.environ.get('IP') is None:
main_parser.print_help()
sys.exit(1)
else:
args.ip = os.environ['IP']
if validate_ip_cidr(args.ip):
try:
for role in args.roles:
return_code = apply(role, args.ip)
if return_code > 0:
break
except Exception as e:
print(f'Unexpected exception occurred: {e}', file=sys.stderr)
return_code = e.errno
else:
print(f'Invalid IP address or CIDR block \'{args.ip}\', please try again.', file=sys.stderr)
return_code = 1
sys.exit(return_code)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
sys.exit(1)

View File

@@ -1,19 +1,33 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Copyright Security Onion Solutions LLC and/or licensed to Security Onion Solutions LLC under one # Copyright 2014-2022 Security Onion Solutions, LLC
# or more contributor license agreements. Licensed under the Elastic License 2.0 as shown at #
# https://securityonion.net/license; you may not use this file except in compliance with the # This program is free software: you can redistribute it and/or modify
# Elastic License 2.0. # it under the terms of the GNU General Public License as published by
import sys, argparse, re, subprocess, json # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, argparse, re, docker
from packaging.version import Version, InvalidVersion from packaging.version import Version, InvalidVersion
from itertools import groupby, chain from itertools import groupby, chain
def get_image_name(string) -> str: def get_image_name(string) -> str:
return ':'.join(string.split(':')[:-1]) return ':'.join(string.split(':')[:-1])
def get_so_image_basename(string) -> str: def get_so_image_basename(string) -> str:
return get_image_name(string).split('/so-')[-1] return get_image_name(string).split('/so-')[-1]
def get_image_version(string) -> str: def get_image_version(string) -> str:
ver = string.split(':')[-1] ver = string.split(':')[-1]
if ver == 'latest': if ver == 'latest':
@@ -29,49 +43,33 @@ def get_image_version(string) -> str:
return '999999.9.9' return '999999.9.9'
return ver return ver
def run_command(command):
process = subprocess.run(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if process.returncode != 0:
print(f"Error executing command: {command}", file=sys.stderr)
print(f"Error message: {process.stderr}", file=sys.stderr)
exit(1)
return process.stdout
def main(quiet): def main(quiet):
try: client = docker.from_env()
# Prune old/stopped containers using docker CLI
# Prune old/stopped containers
if not quiet: print('Pruning old containers') if not quiet: print('Pruning old containers')
run_command('docker container prune -f') client.containers.prune()
# Get list of images using docker CLI image_list = client.images.list(filters={ 'dangling': False })
images_json = run_command('docker images --format "{{json .}}"')
# Parse the JSON output # Map list of image objects to flattened list of tags (format: "name:version")
image_list = [] tag_list = list(chain.from_iterable(list(map(lambda x: x.attrs.get('RepoTags'), image_list))))
for line in images_json.strip().split('\n'):
if line: # Skip empty lines
image_list.append(json.loads(line))
# Extract tags in the format "name:version"
tag_list = []
for img in image_list:
# Skip dangling images
if img.get('Repository') != "<none>" and img.get('Tag') != "<none>":
tag = f"{img.get('Repository')}:{img.get('Tag')}"
# Filter to only SO images (base name begins with "so-") # Filter to only SO images (base name begins with "so-")
if re.match(r'^.*\/so-[^\/]*$', get_image_name(tag)): tag_list = list(filter(lambda x: re.match(r'^.*\/so-[^\/]*$', get_image_name(x)), tag_list))
tag_list.append(tag)
# Group tags into lists by base name (sort by same projection first) # Group tags into lists by base name (sort by same projection first)
tag_list.sort(key=lambda x: get_so_image_basename(x)) tag_list.sort(key=lambda x: get_so_image_basename(x))
grouped_tag_lists = [list(it) for k, it in groupby(tag_list, lambda x: get_so_image_basename(x))] grouped_tag_lists = [ list(it) for _, it in groupby(tag_list, lambda x: get_so_image_basename(x)) ]
no_prunable = True no_prunable = True
for t_list in grouped_tag_lists: for t_list in grouped_tag_lists:
try: try:
# Group tags by version, in case multiple images exist with the same version string # Group tags by version, in case multiple images exist with the same version string
t_list.sort(key=lambda x: Version(get_image_version(x)), reverse=True) t_list.sort(key=lambda x: Version(get_image_version(x)), reverse=True)
grouped_t_list = [list(it) for k, it in groupby(t_list, lambda x: get_image_version(x))] grouped_t_list = [ list(it) for _,it in groupby(t_list, lambda x: get_image_version(x)) ]
# Keep the 2 most current version groups # Keep the 2 most current version groups
if len(grouped_t_list) <= 2: if len(grouped_t_list) <= 2:
continue continue
@@ -81,10 +79,10 @@ def main(quiet):
for tag in group: for tag in group:
if not quiet: print(f'Removing image {tag}') if not quiet: print(f'Removing image {tag}')
try: try:
run_command(f'docker rmi -f {tag}') client.images.remove(tag, force=True)
except Exception as e: except docker.errors.ClientError as e:
print(f'Could not remove image {tag}, continuing...') print(f'Could not remove image {tag}, continuing...')
except (InvalidVersion) as e: except (docker.errors.APIError, InvalidVersion) as e:
print(f'so-{get_so_image_basename(t_list[0])}: {e}', file=sys.stderr) print(f'so-{get_so_image_basename(t_list[0])}: {e}', file=sys.stderr)
exit(1) exit(1)
except Exception as e: except Exception as e:
@@ -95,9 +93,6 @@ def main(quiet):
if no_prunable and not quiet: if no_prunable and not quiet:
print('No Security Onion images to prune') print('No Security Onion images to prune')
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
exit(1)
if __name__ == "__main__": if __name__ == "__main__":
main_parser = argparse.ArgumentParser(add_help=False) main_parser = argparse.ArgumentParser(add_help=False)

View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
. /usr/sbin/so-image-common
require_manager
update_docker_containers "refresh"

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
/usr/sbin/so-restart elastalert $1

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Copyright 2014-2022 Security Onion Solutions, LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
. /usr/sbin/so-common
/usr/sbin/so-start elastalert $1

Some files were not shown because too many files have changed in this diff Show More