mirror of
https://github.com/Security-Onion-Solutions/securityonion.git
synced 2026-11-09 14:09:58 +01:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e5a934ff8 |
No files matched your search
@@ -13,8 +13,6 @@ body:
|
|||||||
- 3.1.0
|
- 3.1.0
|
||||||
- 3.2.0
|
- 3.2.0
|
||||||
- 3.3.0
|
- 3.3.0
|
||||||
- 3.4.0
|
|
||||||
- 3.5.0
|
|
||||||
- Other (please provide detail below)
|
- Other (please provide detail below)
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ on:
|
|||||||
- "salt/sensoroni/files/analyzers/**"
|
- "salt/sensoroni/files/analyzers/**"
|
||||||
- "salt/manager/tools/sbin/**"
|
- "salt/manager/tools/sbin/**"
|
||||||
- "salt/_beacons/**"
|
- "salt/_beacons/**"
|
||||||
- "salt/elastalert/files/modules/**"
|
|
||||||
- "salt/telegraf/tools/sbin_jinja/**"
|
|
||||||
- "salt/telegraf/defaults.yaml"
|
|
||||||
- "salt/telegraf/soc_telegraf.yaml"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -19,7 +15,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.14"]
|
python-version: ["3.14"]
|
||||||
python-code-path: ["salt/sensoroni/files/analyzers", "salt/manager/tools/sbin", "salt/_beacons", "salt/elastalert/files/modules/so"]
|
python-code-path: ["salt/sensoroni/files/analyzers", "salt/manager/tools/sbin", "salt/_beacons"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -38,25 +34,3 @@ jobs:
|
|||||||
- 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
|
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
|
||||||
|
|
||||||
telegraf-collector:
|
|
||||||
# so-container-stats is a jinja template rather than an importable module, so it gets its
|
|
||||||
# own job: the test renders it the way salt does, then drives it with a faked docker engine
|
|
||||||
# and cgroup tree. No container runtime is needed.
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v3
|
|
||||||
with:
|
|
||||||
python-version: "3.14"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install flake8 pytest jinja2 pyyaml
|
|
||||||
- name: Lint with flake8
|
|
||||||
run: |
|
|
||||||
flake8 salt/telegraf/tools/sbin_jinja/so-container-stats_test.py --config=pytest.ini
|
|
||||||
- name: Test with pytest
|
|
||||||
run: |
|
|
||||||
pytest salt/telegraf/tools/sbin_jinja/so-container-stats_test.py -v
|
|
||||||
+11
-11
@@ -1,17 +1,17 @@
|
|||||||
### 3.4.0-20261102 ISO image released on 2026/11/02
|
### 3.3.0-20260911 ISO image released on 2026/09/11
|
||||||
|
|
||||||
|
|
||||||
### Download and Verify
|
### Download and Verify
|
||||||
|
|
||||||
3.4.0-20261102 ISO image:
|
3.3.0-20260911 ISO image:
|
||||||
https://download.securityonion.net/file/securityonion/securityonion-3.4.0-20261102.iso
|
https://download.securityonion.net/file/securityonion/securityonion-3.3.0-20260911.iso
|
||||||
|
|
||||||
MD5: FB0380AE996E175D6CC7E7A35D8CC178
|
MD5: 12B18433D3A2198A185892FF79CF638F
|
||||||
SHA1: D8960F1DA352710BCA209773BF0B3AE8791D0D1B
|
SHA1: 2B3C2E1FA7A78ED1F956E7EDCC12E32593C14EEE
|
||||||
SHA256: DFC4C87668836852A328EC7A0B755C8C1511FF0E43D423DEE23047A95F676D99
|
SHA256: 0938C73B76CE30EC9E4394D312C79EA7CAC721B6818541697279A6221F7D870D
|
||||||
|
|
||||||
Signature for ISO image:
|
Signature for ISO image:
|
||||||
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.4.0-20261102.iso.sig
|
https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.3.0-20260911.iso.sig
|
||||||
|
|
||||||
Signing key:
|
Signing key:
|
||||||
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/main/KEYS
|
https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/main/KEYS
|
||||||
@@ -25,22 +25,22 @@ wget https://raw.githubusercontent.com/Security-Onion-Solutions/securityonion/3/
|
|||||||
|
|
||||||
Download the signature file for the ISO:
|
Download the signature file for the ISO:
|
||||||
```
|
```
|
||||||
wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.4.0-20261102.iso.sig
|
wget https://github.com/Security-Onion-Solutions/securityonion/raw/3/main/sigs/securityonion-3.3.0-20260911.iso.sig
|
||||||
```
|
```
|
||||||
|
|
||||||
Download the ISO image:
|
Download the ISO image:
|
||||||
```
|
```
|
||||||
wget https://download.securityonion.net/file/securityonion/securityonion-3.4.0-20261102.iso
|
wget https://download.securityonion.net/file/securityonion/securityonion-3.3.0-20260911.iso
|
||||||
```
|
```
|
||||||
|
|
||||||
Verify the downloaded ISO image using the signature file:
|
Verify the downloaded ISO image using the signature file:
|
||||||
```
|
```
|
||||||
gpg --verify securityonion-3.4.0-20261102.iso.sig securityonion-3.4.0-20261102.iso
|
gpg --verify securityonion-3.3.0-20260911.iso.sig securityonion-3.3.0-20260911.iso
|
||||||
```
|
```
|
||||||
|
|
||||||
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
|
The output should show "Good signature" and the Primary key fingerprint should match what's shown below:
|
||||||
```
|
```
|
||||||
gpg: Signature made Sat 31 Oct 2026 09:27:50 AM EDT using RSA key ID FE507013
|
gpg: Signature made Fri 11 Sep 2026 11:23:56 AM EDT using RSA key ID FE507013
|
||||||
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
|
gpg: Good signature from "Security Onion Solutions, LLC <info@securityonionsolutions.com>"
|
||||||
gpg: WARNING: This key is not certified with a trusted signature!
|
gpg: WARNING: This key is not certified with a trusted signature!
|
||||||
gpg: There is no indication that the signature belongs to the owner.
|
gpg: There is no indication that the signature belongs to the owner.
|
||||||
|
|||||||
@@ -131,8 +131,6 @@ def beacon(config): # noqa: C901
|
|||||||
'setting_id': setting_id,
|
'setting_id': setting_id,
|
||||||
'node_id': node_id,
|
'node_id': node_id,
|
||||||
})
|
})
|
||||||
log.info('postgres_pillar_beacon: audit_settings id=%d setting_id=%s node_id=%s',
|
|
||||||
row_id, setting_id, node_id)
|
|
||||||
if row_id > max_id:
|
if row_id > max_id:
|
||||||
max_id = row_id
|
max_id = row_id
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
|
|
||||||
EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
|
|
||||||
IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
|
|
||||||
MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
|
|
||||||
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
|
|
||||||
IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
|
|
||||||
MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
|
|
||||||
sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
|
|
||||||
BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
|
|
||||||
Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
|
|
||||||
3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
|
|
||||||
0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
|
|
||||||
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
|
|
||||||
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
|
|
||||||
RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
|
|
||||||
TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
|
|
||||||
b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
|
|
||||||
djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
|
|
||||||
WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
|
|
||||||
fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
|
|
||||||
zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
|
|
||||||
hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
|
|
||||||
CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
|
|
||||||
+JbNR6iC8hZVdyR+EhCVBCyj
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
|
|
||||||
A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
|
|
||||||
SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
|
|
||||||
MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
|
|
||||||
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
|
|
||||||
dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
|
|
||||||
BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
|
|
||||||
HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
|
|
||||||
3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
|
|
||||||
GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
|
|
||||||
xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
|
|
||||||
aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
|
|
||||||
TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
|
|
||||||
BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
|
|
||||||
/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
|
|
||||||
kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
|
|
||||||
YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
|
|
||||||
+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
|
|
||||||
WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIFcjCCA1qgAwIBAgIKLwq3aw3LSq8nWDANBgkqhkiG9w0BAQwFADBWMQswCQYD
|
|
||||||
VQQGEwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJ
|
|
||||||
bmMxHDAaBgNVBAMTE2VtU2lnbiBSb290IENBIC0gQzIwHhcNMTgwMjE4MTgzMDAw
|
|
||||||
WhcNNDMwMjE4MTgzMDAwWjBWMQswCQYDVQQGEwJVUzETMBEGA1UECxMKZW1TaWdu
|
|
||||||
IFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxHDAaBgNVBAMTE2VtU2lnbiBSb290
|
|
||||||
IENBIC0gQzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCMfX1lA+Tb
|
|
||||||
mh9YInmRgOW97IVx4LUJf2DRZfs837Jrml+py64aVnYgWO4t6C78fgjfS7jX+c4T
|
|
||||||
inIzEquWcI+zi0fd4Sc8NDf7JONp27VWX0qwUYqzLDRCt+s7zpLcfx1ky0zVIJj6
|
|
||||||
L06uPyK3kIr9+YAsrVj+39utm6e2MBQsRNstSI3fCQYAGvoQTQ8fULauTqNWaYAk
|
|
||||||
NYFe6HUHHQPp2u1Ua00odMXiD5oRFxLcDnGAcE1I/9E9mLCdkggXijYUmico7+Xw
|
|
||||||
ZeFoPhva6eIJ5p03Lt3Du5W3EcHR0cJmmY1pyeA36JaXKWRNM9IRjYMVNCcp4jhB
|
|
||||||
2tIYiZ+LVk8bwQ9/1c23txmv3u97taZlV22NF4ttS1qq3J+MOp0oGULBzpKfRx0q
|
|
||||||
GVqbPukQNGAjOLIN8KDNQNzbR1iAl2d8H+MSoicBo4Aid8TjLWcNv48oCWL53ZrF
|
|
||||||
BMTDjaIA6frG1t4IpbnHadA7qCJJe2qpJN6n2eQKAUn6UiQDHPsSqNBlcUhQ4Y/0
|
|
||||||
Y0mU5rghm2OB9rXQS1Fb1JRCfJMNnJIm5AUB2+2RWzq5Tgz7SbSho8NsZk0UbQnF
|
|
||||||
xciqQ9uoVTAsK14Sk9oG8Q3zfsM08cdPoRb0WlIZklR6mKD7L8nH/zfGu8PIJv94
|
|
||||||
GGB9RZ9U4A69r3ePmy8MvrzfNxHKtH6svwIDAQABo0IwQDAdBgNVHQ4EFgQUs/eK
|
|
||||||
pNYPiABZ6FEXT9V+7IYigZ0wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
|
|
||||||
Af8wDQYJKoZIhvcNAQEMBQADggIBADQlpiWM0cv2nZ0H5jVsBq0x2q62Q0LwqATs
|
|
||||||
CFvyub7gxNCytRuoA8stmPOEu/lg8Igxj4FIjoyhIrWUVxyiLU7No4P+WjEUOwUT
|
|
||||||
xIpkEOtvGUQ9fiOlcGHtIZDNBlZq7WpktXAxeV55RPPsor26p2FNAMRFfZQh0sLX
|
|
||||||
hKgk8iulSSggqx8ezgPye63FaiYEi4c/dzRj3HOCnsZiwZZU02df5YpNFjxSwZvE
|
|
||||||
41cjGpsrpWMfQFI2s53RbeXp47lSAxYE4NzjBFMe+EwFuEveBCJBEAH5rvYu3pi2
|
|
||||||
orsJ424TqWEQV1tCsCkQz+Yq/Okal7yHAkKDeOXcP7oN4A+TdXc2pdqxuVCnBO0R
|
|
||||||
mWz2JpGSSeJjiTk/OPwRsPNWtwG/KXL04o2ta3jiPpJuICVtWDAc9R3auBEgJl5r
|
|
||||||
ShRmBdszG0LmzsHuZPCFSYC15RBDCOBsa8bDRJ8pBFU2Wi/CVXCACEuavgoveA4F
|
|
||||||
a5bt38o0PWxsBP+MpocCdVtDMqzQhxy9IohKuXWAGresoIvKDg3xFk6rBOrjfVwJ
|
|
||||||
elwi/xAisojHPJVQv9W1zVIoHp+EQg/4MQC21NbIX2RoioB+V3hK439b/w7deU8x
|
|
||||||
2M8cl1OG0nPfbnARl5GPM7vJgi470jto4SeMg6HMAW3Egb56tQcNLwI9U8mZnNvR
|
|
||||||
gUMrkAgL
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
|
|
||||||
VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
|
|
||||||
ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
|
|
||||||
MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO
|
|
||||||
MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv
|
|
||||||
Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN
|
|
||||||
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz
|
|
||||||
f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO
|
|
||||||
8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq
|
|
||||||
d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM
|
|
||||||
tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt
|
|
||||||
Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB
|
|
||||||
o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD
|
|
||||||
AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x
|
|
||||||
PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM
|
|
||||||
wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d
|
|
||||||
GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH
|
|
||||||
6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby
|
|
||||||
RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
|
|
||||||
iN66zB+Afko=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIFlTCCA32gAwIBAgILAIZNvw/jXtd9jtgwDQYJKoZIhvcNAQEMBQAwZzELMAkG
|
|
||||||
A1UEBhMCSU4xEzARBgNVBAsTCmVtU2lnbiBQS0kxJTAjBgNVBAoTHGVNdWRocmEg
|
|
||||||
VGVjaG5vbG9naWVzIExpbWl0ZWQxHDAaBgNVBAMTE2VtU2lnbiBSb290IENBIC0g
|
|
||||||
RzIwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBnMQswCQYDVQQGEwJJ
|
|
||||||
TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
|
|
||||||
b2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMjCCAiIw
|
|
||||||
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMNwGIWW2kHfHK+sXTNwxF07K+IV
|
|
||||||
ySTuyFM2r1v002wUfcdT+zs5OM5QbMYFFnedXQI6gCFLsjKrcaej48Zt37OyEb3i
|
|
||||||
aPs7CsP4kAyTwzKH9aZe6gXYHrJq40/ZVMNcQVI2PcIp40B/SAN2gUZ+ZaUtIOvV
|
|
||||||
jEx26/ebNaXRIsthlkOG/caB+QRwDw1tl7338Zlv0M2oTBUy4B3e7dGP5pgXH71M
|
|
||||||
jqHPCoNo+xv9f0NTBT+hUDa8h8wUtcGQq9CDeJTpjWcD2bP2AMdVG6oVpMAUeUzo
|
|
||||||
cCyglvtFdUMjggxBbw4qhau1HXPG8Ot9hwL7ZMi8tkTzrvUIxxb8G9LF/7kKeCE7
|
|
||||||
tGZaVzDTnXuifl3msR4ErHsQ4P7lVu2AIjIAhrAXoedDidb7pMcf7TABdrYUT1Jo
|
|
||||||
G/AiK+J9jO6GTjeADD4LMDSBZhHMuBK/PJ/g0kGBt+/C1L+/HURzQhJkMlRnM6Rv
|
|
||||||
XoCtfKopSlns5trZmTi971Wjbn88QXP61lGpBCUPwCjs7rpOYvSUJtI+lcbF+37q
|
|
||||||
kIqOXYkVT3cupDSpw+H89kFtj5GKY+Xny4LxY+3IvDIRiyd6ky1DPj713DI0yqve
|
|
||||||
EpsIr3A0PdwuyUI7CS1jg0NnGFT6Xxyr0xB+VDt83FJYW8v16k2pbaQ4kVxA3aXd
|
|
||||||
X9dZYyVR1S59KM75AgMBAAGjQjBAMB0GA1UdDgQWBBTt7E1FYRgo57MjKBEcTaUn
|
|
||||||
DV7s9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
|
|
||||||
AQwFAAOCAgEACFC/ilQg8KTCVBxFJW/sazomkS0kNYbEIZg4B3obqwsJ7SX98z8Z
|
|
||||||
gfzBpz0nYClwwJjWbFN1R2zY8pCEot6/dgmA8Vbq0GxhwPM5YN/SZquNyRIxO3cU
|
|
||||||
dlAcwf+vSezdVCf9wOzvSAF3q0a5ljvbdbNJNpfScQVp7UUd5sBsZk8jXO1KQ/go
|
|
||||||
/Vf/GDPnrIFmxpAIGE3sgnO8lAv9FzUaAeuv7HWe47xN9J7+bQzF93yHuIXACPTL
|
|
||||||
pQHhg2zMv5C7BAbuDHfbj1Cu294Z832yhSfBcziWGskOvl3es2EcHytbS9c9P+0z
|
|
||||||
Mpka7zGC1FHrvLb/FoduH86TeZt0QjZ6pcplNzoaxDnDvzTJ6CC2Eny+qH/APFCu
|
|
||||||
VUv5/wjwF+HPm8Pup2ARj9cEp92+0qcerfHacNq5hMeGZdbA/dzdUR/5z5zXdxAk
|
|
||||||
nl8mcfGb0eMNSTXQmmB/i4AecNnr72uYjzlaXUGYN7Nrb6XouG0pnh0/BBtWWp0U
|
|
||||||
ShIPpWEAqs7RJBj6+1ZUYXZ4ObrCw962DxhN2p19Hxw9LtuUUcLqqTPrFXYvwO4t
|
|
||||||
ouj7KJnAkaTUfXGdEaFVtFig1EA30WzJY2X1vAQ7hVnniCjgaXAGqjsU6sklNM9n
|
|
||||||
xDx5rFCCCEtj9Kh8UHjGK2QqgP5kwgttjOApQMaCoezMfK4KD7WpOXU=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIFdjCCA16gAwIBAgIQCIYhdXKnxAzDuy7l1y1ubDANBgkqhkiG9w0BAQwFADBV
|
|
||||||
MQswCQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGlt
|
|
||||||
aXRlZDEfMB0GA1UEAwwWZW1TaWduIFJvb3QgQ1MgQ0EgLSBHMjAeFw0yNDAyMDgw
|
|
||||||
ODE1MzlaFw00OTAyMDcwODE1MzlaMFUxCzAJBgNVBAYTAklOMSUwIwYDVQQKDBxl
|
|
||||||
TXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMR8wHQYDVQQDDBZlbVNpZ24gUm9v
|
|
||||||
dCBDUyBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4CVO
|
|
||||||
xuBjdLJXpkrjosA+7ygTGaYzufUJrhQOeIZEuCqeCPI8hDqIu+MIqEqOlgDFetVM
|
|
||||||
3Cor3CGBlGJzfzzlI31vyElvQq88jjCfo12Ghqcf+NAZDFduJk5+NgC0z3lfdiB6
|
|
||||||
NNzxbfVkA+X6oDNNRioVUZsVFuIzT9Qt0Sh4KwQPRFBc/wI2yGpDuKHNsLehcECx
|
|
||||||
Uw00m33xGYh9UkeL3reOmAaLYWZiO+Pd2vxPtrkXI4wyN4BMlESJ73uP0tyX9lq4
|
|
||||||
M4yN2yh3i+ETI8/jzPjxHD7QFaVkCoS+uj2YmBa3mytlqlBgyOoKueNeotX8hfzB
|
|
||||||
FNm962abpV4mN0SoC6hjkYv8IayseRPFv9VI0meQXg9OLRoNvBOqG5kw937FH0Li
|
|
||||||
q3CIzKyvdyeYRT0LJdWA2iuB9l+4zzIfCwGyT4M4VDmJuPBhrTmjXKae6AUtm1eO
|
|
||||||
q2f8gftA6J6AlbD0gRd/t8CrFssFNm+LYAEd36kWRBuolbaOfbSbEww84xNAgj4U
|
|
||||||
SPwYjtNDsCkQjtgSOaDtDOmolN+Mbk528Y+LHMlbd4T3Xmp+VIWp5+70GhBDOA91
|
|
||||||
hhtims+hHkXjRI2U7WrJLcTlcF8ykiymRbFKhwOBvTmtZNbHmDI7scU4wWUAsmW6
|
|
||||||
tWaeeoM50KBl8ai28A6yBGYMO//03Ubvqiqst80CAwEAAaNCMEAwHQYDVR0OBBYE
|
|
||||||
FOYcxqontIvyvYVkX6/0unKm0AF1MA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
|
|
||||||
BTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQCv5Asord7fdQMT30emwb+oLuDUxdmJ
|
|
||||||
DQHnzxIXoNvymDTbNHF9LqjeXc+X+XcjIV8PTp4o2I9wyVppHmcaVgRjN/G9FxeN
|
|
||||||
5tCQ97v5sdx4eAdPljK1xqg7Vhfmzcp+7rSjJG7Sn9gMlZx81kL3Oa2qZj+W5Fi6
|
|
||||||
tN4vaR9OLEyvKKHDG/eFxS/qghD1d/Clkb7UGGjBcWJlaZTWVALMKG+QCxiMoELi
|
|
||||||
oeKaoBbZoXooxfF0mWdkIl9RWNU4r9O/yt66LBE9rj49dX19L+rGg71/4bTi5ua+
|
|
||||||
+Wz60XaOc2CdHJaBSols5hWEx/LBjS7UzfU3uJfnpQXVDi0US5sFzIHIlS6kjh7h
|
|
||||||
YuF4+/9ZAl4Bx3w2TNN7pVm7I39pgYl7XN6KZRwfkAv4aRswPqhWbQfr3+qfcC93
|
|
||||||
cgTyFy2RG5YE/OfmpSs6VtYKawqsdEODz/p2nhZfSDrVDnEPvERqahttY5+gaWNc
|
|
||||||
2fbibgKMfuejTZaJ3yttPslVSko5SDs0xKU423swu34sb/fdISqtOu4NzPCZiHSG
|
|
||||||
ladaoITmSA6UWFk4fdqaLBI+xSxY5Et3S1APp5r3j/l1KrWPNbb1/+ydHN+YLbh8
|
|
||||||
hCBIecKdk6VbnnFRlulm0d2RSgpfCRrWLomN8JHCxCMVv/hBQXz3AXGM1ZlfYTyl
|
|
||||||
E1BpDby0G2eEkw==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -10,4 +10,3 @@ include:
|
|||||||
- ca.server
|
- ca.server
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- ca.trustca
|
- ca.trustca
|
||||||
- ca.trust
|
|
||||||
@@ -1,12 +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.
|
|
||||||
|
|
||||||
{# Containers mount the extracted directory rather than single files: update-ca-trust replaces
|
|
||||||
files by rename, so a file bind mount would keep pointing at the old inode. #}
|
|
||||||
{% set CATRUST = {
|
|
||||||
'dir': '/etc/pki/ca-trust/extracted:/etc/pki/ca-trust/extracted:ro',
|
|
||||||
'java': '/etc/pki/ca-trust/extracted/java/cacerts',
|
|
||||||
'pem': '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem'
|
|
||||||
} %}
|
|
||||||
@@ -1,37 +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.
|
|
||||||
|
|
||||||
ca_trust_anchor:
|
|
||||||
file.managed:
|
|
||||||
- name: /etc/pki/ca-trust/source/anchors/so-intca.crt
|
|
||||||
- source: salt://ca/files/ca.crt
|
|
||||||
- show_changes: False
|
|
||||||
- makedirs: True
|
|
||||||
|
|
||||||
ca_trust_legacy_anchor:
|
|
||||||
file.absent:
|
|
||||||
- name: /etc/pki/ca-trust/source/anchors/ca.crt
|
|
||||||
|
|
||||||
ca_trust_legacy_bundles:
|
|
||||||
file.absent:
|
|
||||||
- name: /opt/so/conf/ca
|
|
||||||
|
|
||||||
# clean removes blocklist entries that are no longer shipped
|
|
||||||
ca_trust_blocklist:
|
|
||||||
file.recurse:
|
|
||||||
- name: /etc/pki/ca-trust/source/blocklist
|
|
||||||
- source: salt://ca/files/blocklist
|
|
||||||
- clean: True
|
|
||||||
- makedirs: True
|
|
||||||
- file_mode: '0644'
|
|
||||||
- dir_mode: '0755'
|
|
||||||
|
|
||||||
ca_trust_extract:
|
|
||||||
cmd.run:
|
|
||||||
- name: update-ca-trust extract
|
|
||||||
- onchanges:
|
|
||||||
- file: ca_trust_anchor
|
|
||||||
- file: ca_trust_legacy_anchor
|
|
||||||
- file: ca_trust_blocklist
|
|
||||||
+5
-20
@@ -117,25 +117,14 @@ elastic_curl_config:
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
# A non-root owner here can chmod the directory and replace any script in it, including
|
|
||||||
# the root-owned ones. 555 is the mode the filesystem RPM ships; root ignores it anyway.
|
|
||||||
usr_sbin_perms:
|
|
||||||
file.directory:
|
|
||||||
- name: /usr/sbin
|
|
||||||
- user: root
|
|
||||||
- group: root
|
|
||||||
- mode: 555
|
|
||||||
|
|
||||||
common_sbin:
|
common_sbin:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://common/tools/sbin
|
- source: salt://common/tools/sbin
|
||||||
- user: root
|
- user: 939
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
- require:
|
|
||||||
- file: usr_sbin_perms
|
|
||||||
{% if GLOBALS.role == 'so-heavynode' %}
|
{% if GLOBALS.role == 'so-heavynode' %}
|
||||||
- exclude_pat:
|
- exclude_pat:
|
||||||
- so-pcap-import
|
- so-pcap-import
|
||||||
@@ -170,8 +159,8 @@ common_sbin_jinja:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://common/tools/sbin_jinja
|
- source: salt://common/tools/sbin_jinja
|
||||||
- user: root
|
- user: 939
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
@@ -184,8 +173,6 @@ so-status_script:
|
|||||||
file.managed:
|
file.managed:
|
||||||
- name: /usr/sbin/so-status
|
- name: /usr/sbin/so-status
|
||||||
- source: salt://common/tools/sbin/so-status
|
- source: salt://common/tools/sbin/so-status
|
||||||
- user: root
|
|
||||||
- group: root
|
|
||||||
- mode: 755
|
- mode: 755
|
||||||
|
|
||||||
{% if GLOBALS.is_sensor %}
|
{% if GLOBALS.is_sensor %}
|
||||||
@@ -217,11 +204,9 @@ sostatus_log:
|
|||||||
- replace: False
|
- replace: False
|
||||||
|
|
||||||
# Install sostatus check cron. This is used to populate Grid.
|
# Install sostatus check cron. This is used to populate Grid.
|
||||||
# telegraf reads status.log on the same minute boundary this runs, so write aside and rename
|
|
||||||
# rather than truncating the file it is reading
|
|
||||||
so-status_check_cron:
|
so-status_check_cron:
|
||||||
cron.present:
|
cron.present:
|
||||||
- name: '/usr/sbin/so-status -j > /opt/so/log/sostatus/status.log.tmp 2>&1; mv -f /opt/so/log/sostatus/status.log.tmp /opt/so/log/sostatus/status.log'
|
- name: '/usr/sbin/so-status -j > /opt/so/log/sostatus/status.log 2>&1'
|
||||||
- identifier: so-status_check_cron
|
- identifier: so-status_check_cron
|
||||||
- user: root
|
- user: root
|
||||||
- minute: '*/1'
|
- minute: '*/1'
|
||||||
|
|||||||
@@ -18,61 +18,47 @@ copy_so-common_common_tools_sbin:
|
|||||||
- name: /opt/so/saltstack/default/salt/common/tools/sbin/so-common
|
- name: /opt/so/saltstack/default/salt/common/tools/sbin/so-common
|
||||||
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common
|
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_so-image-common_common_tools_sbin:
|
copy_so-image-common_common_tools_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /opt/so/saltstack/default/salt/common/tools/sbin/so-image-common
|
- name: /opt/so/saltstack/default/salt/common/tools/sbin/so-image-common
|
||||||
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common
|
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_soup_manager_tools_sbin:
|
copy_soup_manager_tools_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/soup
|
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/soup
|
||||||
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup
|
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_so-firewall_manager_tools_sbin:
|
copy_so-firewall_manager_tools_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-firewall
|
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-firewall
|
||||||
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall
|
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_so-yaml_manager_tools_sbin:
|
copy_so-yaml_manager_tools_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-yaml.py
|
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-yaml.py
|
||||||
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py
|
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_so-repo-sync_manager_tools_sbin:
|
copy_so-repo-sync_manager_tools_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-repo-sync
|
- name: /opt/so/saltstack/default/salt/manager/tools/sbin/so-repo-sync
|
||||||
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync
|
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_bootstrap-salt_manager_tools_sbin:
|
copy_bootstrap-salt_manager_tools_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /opt/so/saltstack/default/salt/salt/scripts/bootstrap-salt.sh
|
- name: /opt/so/saltstack/default/salt/salt/scripts/bootstrap-salt.sh
|
||||||
- source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh
|
- source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 644
|
|
||||||
|
|
||||||
# This section is used to put the new script in place so that it can be called during soup.
|
# 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.
|
# It is faster than calling the states that normally manage them to put them in place.
|
||||||
@@ -81,60 +67,46 @@ copy_so-common_sbin:
|
|||||||
- name: /usr/sbin/so-common
|
- name: /usr/sbin/so-common
|
||||||
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common
|
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-common
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_so-image-common_sbin:
|
copy_so-image-common_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /usr/sbin/so-image-common
|
- name: /usr/sbin/so-image-common
|
||||||
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common
|
- source: {{UPDATE_DIR}}/salt/common/tools/sbin/so-image-common
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_soup_sbin:
|
copy_soup_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /usr/sbin/soup
|
- name: /usr/sbin/soup
|
||||||
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup
|
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/soup
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_so-firewall_sbin:
|
copy_so-firewall_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /usr/sbin/so-firewall
|
- name: /usr/sbin/so-firewall
|
||||||
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall
|
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-firewall
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_so-yaml_sbin:
|
copy_so-yaml_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /usr/sbin/so-yaml.py
|
- name: /usr/sbin/so-yaml.py
|
||||||
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py
|
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-yaml.py
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_so-repo-sync_sbin:
|
copy_so-repo-sync_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /usr/sbin/so-repo-sync
|
- name: /usr/sbin/so-repo-sync
|
||||||
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync
|
- source: {{UPDATE_DIR}}/salt/manager/tools/sbin/so-repo-sync
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
|
|
||||||
copy_bootstrap-salt_sbin:
|
copy_bootstrap-salt_sbin:
|
||||||
file.copy:
|
file.copy:
|
||||||
- name: /usr/sbin/bootstrap-salt.sh
|
- name: /usr/sbin/bootstrap-salt.sh
|
||||||
- source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh
|
- source: {{UPDATE_DIR}}/salt/salt/scripts/bootstrap-salt.sh
|
||||||
- force: True
|
- force: True
|
||||||
- user: root
|
- preserve: True
|
||||||
- group: root
|
|
||||||
- mode: 755
|
|
||||||
@@ -240,8 +240,7 @@ copy_new_files() {
|
|||||||
cd $UPDATE_DIR
|
cd $UPDATE_DIR
|
||||||
rsync -a salt $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}"
|
rsync -a salt $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}"
|
||||||
rsync -a pillar $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}"
|
rsync -a pillar $DEFAULT_SALT_DIR/ --delete "${EXCLUDE_ARGS[@]}"
|
||||||
# Root-executed code; SOC only needs to read it. Local dirs stay socore-owned.
|
chown -R socore:socore $DEFAULT_SALT_DIR/
|
||||||
chown -R root:root $DEFAULT_SALT_DIR/
|
|
||||||
cd /tmp
|
cd /tmp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import sys
|
|||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import tempfile
|
|
||||||
|
|
||||||
sys.path.append('/opt/saltstack/salt/lib/python3.10/site-packages/')
|
sys.path.append('/opt/saltstack/salt/lib/python3.10/site-packages/')
|
||||||
import salt.config
|
import salt.config
|
||||||
@@ -18,21 +17,6 @@ import salt.loader
|
|||||||
__opts__ = salt.config.minion_config('/etc/salt/minion')
|
__opts__ = salt.config.minion_config('/etc/salt/minion')
|
||||||
__grains__ = salt.loader.grains(__opts__)
|
__grains__ = salt.loader.grains(__opts__)
|
||||||
|
|
||||||
def write_atomic(path, value):
|
|
||||||
# telegraf reads these files on its own schedule; replacing them by rename means it never
|
|
||||||
# reads a truncated file and reports an empty value as if it were real
|
|
||||||
directory = os.path.dirname(path)
|
|
||||||
handle, temp = tempfile.mkstemp(dir=directory)
|
|
||||||
try:
|
|
||||||
with os.fdopen(handle, 'w') as f:
|
|
||||||
f.write(str(value))
|
|
||||||
os.chmod(temp, 0o644)
|
|
||||||
os.replace(temp, path)
|
|
||||||
except Exception:
|
|
||||||
os.path.exists(temp) and os.unlink(temp)
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def check_needs_restarted():
|
def check_needs_restarted():
|
||||||
osfam = __grains__['os_family']
|
osfam = __grains__['os_family']
|
||||||
val = '0'
|
val = '0'
|
||||||
@@ -50,7 +34,8 @@ def check_needs_restarted():
|
|||||||
else:
|
else:
|
||||||
fail("Unsupported OS")
|
fail("Unsupported OS")
|
||||||
|
|
||||||
write_atomic(outfile, val)
|
with open(outfile, 'w') as f:
|
||||||
|
f.write(val)
|
||||||
|
|
||||||
def check_for_fps():
|
def check_for_fps():
|
||||||
feat = 'fps'
|
feat = 'fps'
|
||||||
@@ -71,7 +56,8 @@ def check_for_fps():
|
|||||||
# Unknown, so assume 0
|
# Unknown, so assume 0
|
||||||
fps = 0
|
fps = 0
|
||||||
|
|
||||||
write_atomic('/opt/so/log/sostatus/fps_enabled', fps)
|
with open('/opt/so/log/sostatus/fps_enabled', 'w') as f:
|
||||||
|
f.write(str(fps))
|
||||||
|
|
||||||
def check_for_lks():
|
def check_for_lks():
|
||||||
feat = 'Lks'
|
feat = 'Lks'
|
||||||
@@ -94,7 +80,8 @@ def check_for_lks():
|
|||||||
lks = 1
|
lks = 1
|
||||||
if lks:
|
if lks:
|
||||||
break
|
break
|
||||||
write_atomic('/opt/so/log/sostatus/lks_enabled', lks)
|
with open('/opt/so/log/sostatus/lks_enabled', 'w') as f:
|
||||||
|
f.write(str(lks))
|
||||||
|
|
||||||
def fail(msg):
|
def fail(msg):
|
||||||
print(msg, file=sys.stderr)
|
print(msg, file=sys.stderr)
|
||||||
|
|||||||
@@ -177,8 +177,6 @@ if [[ $EXCLUDE_FALSE_POSITIVE_ERRORS == 'Y' ]]; then
|
|||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Unexpected authorization header" # expected WARN log lines indicating invalid auth header
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Unexpected authorization header" # expected WARN log lines indicating invalid auth header
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Missing ory_kratos_session cookie" # expected WARN log lines indicating invalid auth header
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Missing ory_kratos_session cookie" # expected WARN log lines indicating invalid auth header
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Static assets preprocessor only supports GET and HEAD requests" # expected WARN log lines indicating invalid auth header
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Static assets preprocessor only supports GET and HEAD requests" # expected WARN log lines indicating invalid auth header
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|respondError" # respondError is a function name, output via http middleware as standard request logging
|
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|GET /kibana/" # Ignore Kibana queries with triggered words
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
|
if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
|
||||||
@@ -242,7 +240,7 @@ if [[ $EXCLUDE_KNOWN_ERRORS == 'Y' ]]; then
|
|||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|marked for removal" # docker container getting recycled
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|marked for removal" # docker container getting recycled
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|tcp 127.0.0.1:6791: bind: address already in use" # so-elastic-fleet agent restarting. Seen starting w/ 8.18.8 https://github.com/elastic/kibana/issues/201459
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|tcp 127.0.0.1:6791: bind: address already in use" # so-elastic-fleet agent restarting. Seen starting w/ 8.18.8 https://github.com/elastic/kibana/issues/201459
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-.*user so_kibana lacks the required permissions" # Known issue with integrations starting transform jobs that are explicitly not allowed to start as a system user
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|TransformTask\] \[logs-.*user so_kibana lacks the required permissions" # Known issue with integrations starting transform jobs that are explicitly not allowed to start as a system user
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # so-dockerregistry logs a tag lookup miss during image copy; not tied to one docker version
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|manifest unknown" # appears in so-dockerregistry log for so-tcpreplay following docker upgrade to 29.2.1-1
|
||||||
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Could not index event to Elasticsearch.*\"version\" => \"9.0.8\"" # Expected during Elastic upgrade temporarily, as policies referencing older pipelines are updated
|
EXCLUDED_ERRORS="$EXCLUDED_ERRORS|Could not index event to Elasticsearch.*\"version\" => \"9.0.8\"" # Expected during Elastic upgrade temporarily, as policies referencing older pipelines are updated
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -8,37 +8,21 @@
|
|||||||
# Elastic License 2.0.
|
# Elastic License 2.0.
|
||||||
|
|
||||||
|
|
||||||
SENSOR_DIR="${SENSOR_DIR:-/nsm}"
|
SENSOR_DIR='/nsm'
|
||||||
CRIT_DISK_USAGE=90
|
CRIT_DISK_USAGE=90
|
||||||
LOG="${LOG:-/opt/so/log/sensor_clean.log}"
|
CUR_USAGE=$(df -P $SENSOR_DIR | tail -1 | awk '{print $5}' | tr -d %)
|
||||||
LOCK="${LOCK:-/var/tmp/so-sensor-clean.lock}"
|
LOG="/opt/so/log/sensor_clean.log"
|
||||||
MAX_PASSES=100
|
TODAY=$(date -u "+%Y-%m-%d")
|
||||||
|
|
||||||
ZEEK_LOGS="$SENSOR_DIR/zeek/logs"
|
|
||||||
STRELKA_FILES="$SENSOR_DIR/strelka/processed"
|
|
||||||
SURICATA_LOGS="$SENSOR_DIR/suricata"
|
|
||||||
PCAPS="$SENSOR_DIR/pcapout"
|
|
||||||
|
|
||||||
log() {
|
|
||||||
echo "$(date) - $*" >>"$LOG"
|
|
||||||
}
|
|
||||||
|
|
||||||
disk_usage() {
|
|
||||||
df -P "$SENSOR_DIR" | tail -1 | awk '{print $5}' | tr -d %
|
|
||||||
}
|
|
||||||
|
|
||||||
disk_avail() {
|
|
||||||
df -P "$SENSOR_DIR" | tail -1 | awk '{print $4}'
|
|
||||||
}
|
|
||||||
|
|
||||||
# sets REMOVED=1 if anything was actually deleted
|
|
||||||
clean() {
|
clean() {
|
||||||
## find the oldest Zeek logs directory
|
## find the oldest Zeek logs directory
|
||||||
OLDEST_DIR=$(ls "$ZEEK_LOGS" 2>/dev/null | grep -v "current" | grep -v "stats" | grep -v "packetloss" | grep -v "zeek_clean" | sort | head -n 1)
|
OLDEST_DIR=$(ls /nsm/zeek/logs/ | grep -v "current" | grep -v "stats" | grep -v "packetloss" | grep -v "zeek_clean" | sort | head -n 1)
|
||||||
if [ -n "$OLDEST_DIR" ]; then
|
if [ -z "$OLDEST_DIR" -o "$OLDEST_DIR" == ".." -o "$OLDEST_DIR" == "." ]; then
|
||||||
log "Removing directory: $ZEEK_LOGS/$OLDEST_DIR"
|
echo "$(date) - No old Zeek logs available to clean up in /nsm/zeek/logs/" >>$LOG
|
||||||
rm -rf "$ZEEK_LOGS/$OLDEST_DIR"
|
#exit 0
|
||||||
REMOVED=1
|
else
|
||||||
|
echo "$(date) - Removing directory: /nsm/zeek/logs/$OLDEST_DIR" >>$LOG
|
||||||
|
rm -rf /nsm/zeek/logs/"$OLDEST_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## Remarking for now, as we are moving extracted files to /nsm/strelka/processed
|
## Remarking for now, as we are moving extracted files to /nsm/strelka/processed
|
||||||
@@ -59,73 +43,58 @@ clean() {
|
|||||||
#fi
|
#fi
|
||||||
|
|
||||||
## Clean up Zeek extracted files processed by Strelka
|
## Clean up Zeek extracted files processed by Strelka
|
||||||
OLDEST_STRELKA=$(find "$STRELKA_FILES" -type f -printf '%T+ %p\n' 2>/dev/null | sort -n | head -n 1)
|
STRELKA_FILES='/nsm/strelka/processed'
|
||||||
if [ -n "$OLDEST_STRELKA" ]; then
|
OLDEST_STRELKA=$(find $STRELKA_FILES -type f -printf '%T+ %p\n' | sort -n | head -n 1)
|
||||||
|
if [ -z "$OLDEST_STRELKA" -o "$OLDEST_STRELKA" == ".." -o "$OLDEST_STRELKA" == "." ]; then
|
||||||
|
echo "$(date) - No old files available to clean up in $STRELKA_FILES" >>$LOG
|
||||||
|
else
|
||||||
OLDEST_STRELKA_DATE=$(echo $OLDEST_STRELKA | awk '{print $1}' | cut -d+ -f1)
|
OLDEST_STRELKA_DATE=$(echo $OLDEST_STRELKA | awk '{print $1}' | cut -d+ -f1)
|
||||||
log "Removing extracted files for $OLDEST_STRELKA_DATE"
|
OLDEST_STRELKA_FILE=$(echo $OLDEST_STRELKA | awk '{print $2}')
|
||||||
REMOVED=1
|
echo "$(date) - Removing extracted files for $OLDEST_STRELKA_DATE" >>$LOG
|
||||||
find "$STRELKA_FILES" -type f -printf '%T+ %p\n' 2>/dev/null | grep $OLDEST_STRELKA_DATE | awk '{print $2}' | while read FILE; do
|
find $STRELKA_FILES -type f -printf '%T+ %p\n' | grep $OLDEST_STRELKA_DATE | awk '{print $2}' | while read FILE; do
|
||||||
log "Removing file: $FILE"
|
echo "$(date) - Removing file: $FILE" >>$LOG
|
||||||
rm -f "$FILE"
|
rm -f "$FILE"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## Clean up Suricata log files
|
## Clean up Suricata log files
|
||||||
OLDEST_SURICATA=$(find "$SURICATA_LOGS" -type f -printf '%T+ %p\n' 2>/dev/null | sort -n | head -n 1)
|
SURICATA_LOGS='/nsm/suricata'
|
||||||
if [ -n "$OLDEST_SURICATA" ]; then
|
OLDEST_SURICATA=$(find $SURICATA_LOGS -type f -printf '%T+ %p\n' | sort -n | head -n 1)
|
||||||
|
if [[ -z "$OLDEST_SURICATA" ]] || [[ "$OLDEST_SURICATA" == ".." ]] || [[ "$OLDEST_SURICATA" == "." ]]; then
|
||||||
|
echo "$(date) - No old files available to clean up in $SURICATA_LOGS" >>$LOG
|
||||||
|
else
|
||||||
OLDEST_SURICATA_DATE=$(echo $OLDEST_SURICATA | awk '{print $1}' | cut -d+ -f1)
|
OLDEST_SURICATA_DATE=$(echo $OLDEST_SURICATA | awk '{print $1}' | cut -d+ -f1)
|
||||||
log "Removing logs for $OLDEST_SURICATA_DATE"
|
OLDEST_SURICATA_FILE=$(echo $OLDEST_SURICATA | awk '{print $2}')
|
||||||
REMOVED=1
|
echo "$(date) - Removing logs for $OLDEST_SURICATA_DATE" >>$LOG
|
||||||
find "$SURICATA_LOGS" -type f -printf '%T+ %p\n' 2>/dev/null | grep $OLDEST_SURICATA_DATE | awk '{print $2}' | while read FILE; do
|
find $SURICATA_LOGS -type f -printf '%T+ %p\n' | grep $OLDEST_SURICATA_DATE | awk '{print $2}' | while read FILE; do
|
||||||
log "Removing file: $FILE"
|
echo "$(date) - Removing file: $FILE" >>$LOG
|
||||||
rm -f "$FILE"
|
rm -f "$FILE"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
## Clean up extracted pcaps
|
## Clean up extracted pcaps
|
||||||
OLDEST_PCAP=$(find "$PCAPS" -type f -printf '%T+ %p\n' 2>/dev/null | sort -n | head -n 1)
|
PCAPS='/nsm/pcapout'
|
||||||
if [ -n "$OLDEST_PCAP" ]; then
|
OLDEST_PCAP=$(find $PCAPS -type f -printf '%T+ %p\n' | sort -n | head -n 1)
|
||||||
|
if [ -z "$OLDEST_PCAP" -o "$OLDEST_PCAP" == ".." -o "$OLDEST_PCAP" == "." ]; then
|
||||||
|
echo "$(date) - No old files available to clean up in $PCAPS" >>$LOG
|
||||||
|
else
|
||||||
OLDEST_PCAP_DATE=$(echo $OLDEST_PCAP | awk '{print $1}' | cut -d+ -f1)
|
OLDEST_PCAP_DATE=$(echo $OLDEST_PCAP | awk '{print $1}' | cut -d+ -f1)
|
||||||
log "Removing extracted files for $OLDEST_PCAP_DATE"
|
OLDEST_PCAP_FILE=$(echo $OLDEST_PCAP | awk '{print $2}')
|
||||||
REMOVED=1
|
echo "$(date) - Removing extracted files for $OLDEST_PCAP_DATE" >>$LOG
|
||||||
find "$PCAPS" -type f -printf '%T+ %p\n' 2>/dev/null | grep $OLDEST_PCAP_DATE | awk '{print $2}' | while read FILE; do
|
find $PCAPS -type f -printf '%T+ %p\n' | grep $OLDEST_PCAP_DATE | awk '{print $2}' | while read FILE; do
|
||||||
log "Removing file: $FILE"
|
echo "$(date) - Removing file: $FILE" >>$LOG
|
||||||
rm -f "$FILE"
|
rm -f "$FILE"
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only one instance at a time; the lock is the fd, so it releases on any exit
|
# Check to see if we are already running
|
||||||
exec 9>"$LOCK" || exit 1
|
NUM_RUNNING=$(pgrep -cf "/bin/bash /usr/sbin/so-sensor-clean")
|
||||||
if ! flock -n 9; then
|
[ "$NUM_RUNNING" -gt 1 ] && echo "$(date) - $NUM_RUNNING sensor clean script processes running...exiting." >>$LOG && exit 0
|
||||||
log "another so-sensor-clean is already running (lock $LOCK held); exiting"
|
|
||||||
exit 0
|
if [ "$CUR_USAGE" -gt "$CRIT_DISK_USAGE" ]; then
|
||||||
|
while [ "$CUR_USAGE" -gt "$CRIT_DISK_USAGE" ]; do
|
||||||
|
clean
|
||||||
|
CUR_USAGE=$(df -P $SENSOR_DIR | tail -1 | awk '{print $5}' | tr -d %)
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
CUR_USAGE=$(disk_usage)
|
|
||||||
[ "$CUR_USAGE" -gt "$CRIT_DISK_USAGE" ] || exit 0
|
|
||||||
|
|
||||||
log "$SENSOR_DIR at ${CUR_USAGE}% (threshold ${CRIT_DISK_USAGE}%); starting cleanup"
|
|
||||||
|
|
||||||
PASS=0
|
|
||||||
while [ "$CUR_USAGE" -gt "$CRIT_DISK_USAGE" ]; do
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
if [ "$PASS" -gt "$MAX_PASSES" ]; then
|
|
||||||
log "stopping after $MAX_PASSES passes; $SENSOR_DIR still at ${CUR_USAGE}%"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
REMOVED=0
|
|
||||||
BEFORE=$(disk_avail)
|
|
||||||
clean
|
|
||||||
CUR_USAGE=$(disk_usage)
|
|
||||||
|
|
||||||
if [ "$REMOVED" -eq 0 ]; then
|
|
||||||
log "nothing left to remove in $ZEEK_LOGS, $STRELKA_FILES, $SURICATA_LOGS, $PCAPS; $SENSOR_DIR still at ${CUR_USAGE}% - space is consumed outside of NSM cleanup scope"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if [ "$(disk_avail)" -le "$BEFORE" ]; then
|
|
||||||
log "pass $PASS freed no space; $SENSOR_DIR still at ${CUR_USAGE}% - stopping until next run"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
@@ -125,6 +125,4 @@ else
|
|||||||
RAIDSTATUS=1
|
RAIDSTATUS=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# telegraf reads this file; write aside and rename so it never sees a half-written file
|
echo "nsmraid=$RAIDSTATUS" > /opt/so/log/raid/status.log
|
||||||
echo "nsmraid=$RAIDSTATUS" > /opt/so/log/raid/status.log.tmp
|
|
||||||
mv -f /opt/so/log/raid/status.log.tmp /opt/so/log/raid/status.log
|
|
||||||
@@ -438,10 +438,6 @@ install_networkminer:
|
|||||||
pkg.latest:
|
pkg.latest:
|
||||||
- name: securityonion-networkminer
|
- name: securityonion-networkminer
|
||||||
|
|
||||||
install_so_crates:
|
|
||||||
pkg.latest:
|
|
||||||
- name: securityonion-so-crates
|
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
desktop_packages_os_fail:
|
desktop_packages_os_fail:
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
|
|
||||||
{# we only want this state to run it is CentOS #}
|
{# we only want this state to run it is CentOS #}
|
||||||
{% if GLOBALS.os == 'OEL' %}
|
{% if GLOBALS.os == 'OEL' %}
|
||||||
|
|
||||||
include:
|
trusted_ca:
|
||||||
- ca.trust
|
file.managed:
|
||||||
|
- name: /etc/pki/ca-trust/source/anchors/ca.crt
|
||||||
|
- source: salt://ca/files/ca.crt
|
||||||
|
|
||||||
|
update_ca_certs:
|
||||||
|
cmd.run:
|
||||||
|
- name: update-ca-trust
|
||||||
|
- onchanges:
|
||||||
|
- file: trusted_ca
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
docker:
|
docker:
|
||||||
range: '172.17.1.0/24'
|
range: '172.17.1.0/24'
|
||||||
gateway: '172.17.1.1'
|
gateway: '172.17.1.1'
|
||||||
networks:
|
|
||||||
sobridge: {}
|
|
||||||
soauth:
|
|
||||||
range: '172.17.2.0/24'
|
|
||||||
gateway: '172.17.2.1'
|
|
||||||
manager_only: True
|
|
||||||
ulimits:
|
ulimits:
|
||||||
- name: nofile
|
- name: nofile
|
||||||
soft: 1048576
|
soft: 1048576
|
||||||
@@ -64,18 +58,18 @@ docker:
|
|||||||
ulimits: []
|
ulimits: []
|
||||||
'so-kratos':
|
'so-kratos':
|
||||||
final_octet: 28
|
final_octet: 28
|
||||||
networks: ['soauth']
|
|
||||||
port_bindings:
|
port_bindings:
|
||||||
- 0.0.0.0:4433:4433
|
- 0.0.0.0:4433:4433
|
||||||
|
- 0.0.0.0:4434:4434
|
||||||
custom_bind_mounts: []
|
custom_bind_mounts: []
|
||||||
extra_hosts: []
|
extra_hosts: []
|
||||||
extra_env: []
|
extra_env: []
|
||||||
ulimits: []
|
ulimits: []
|
||||||
'so-hydra':
|
'so-hydra':
|
||||||
final_octet: 30
|
final_octet: 30
|
||||||
networks: ['soauth']
|
|
||||||
port_bindings:
|
port_bindings:
|
||||||
- 0.0.0.0:4444:4444
|
- 0.0.0.0:4444:4444
|
||||||
|
- 0.0.0.0:4445:4445
|
||||||
custom_bind_mounts: []
|
custom_bind_mounts: []
|
||||||
extra_hosts: []
|
extra_hosts: []
|
||||||
extra_env: []
|
extra_env: []
|
||||||
@@ -134,7 +128,6 @@ docker:
|
|||||||
ulimits: []
|
ulimits: []
|
||||||
'so-soc':
|
'so-soc':
|
||||||
final_octet: 34
|
final_octet: 34
|
||||||
networks: ['sobridge', 'soauth']
|
|
||||||
port_bindings:
|
port_bindings:
|
||||||
- 0.0.0.0:9822:9822
|
- 0.0.0.0:9822:9822
|
||||||
custom_bind_mounts: []
|
custom_bind_mounts: []
|
||||||
|
|||||||
@@ -1,26 +1,8 @@
|
|||||||
{% import_yaml 'docker/defaults.yaml' as DOCKERDEFAULTS %}
|
{% import_yaml 'docker/defaults.yaml' as DOCKERDEFAULTS %}
|
||||||
{% set DOCKERMERGED = salt['pillar.get']('docker', DOCKERDEFAULTS.docker, merge=True) %}
|
{% set DOCKERMERGED = salt['pillar.get']('docker', DOCKERDEFAULTS.docker, merge=True) %}
|
||||||
|
{% set RANGESPLIT = DOCKERMERGED.range.split('.') %}
|
||||||
{% if DOCKERMERGED.networks.sobridge is not mapping %}
|
{% set FIRSTTHREE = RANGESPLIT[0] ~ '.' ~ RANGESPLIT[1] ~ '.' ~ RANGESPLIT[2] ~ '.' %}
|
||||||
{% do DOCKERMERGED.networks.update({'sobridge': {}}) %}
|
|
||||||
{% endif %}
|
|
||||||
{% do DOCKERMERGED.networks['sobridge'].update({'range': DOCKERMERGED.range, 'gateway': DOCKERMERGED.gateway}) %}
|
|
||||||
|
|
||||||
{% for netname, net in DOCKERMERGED.networks.items() %}
|
|
||||||
{% set RANGESPLIT = net.range.split('.') %}
|
|
||||||
{% do net.update({'prefix': RANGESPLIT[0] ~ '.' ~ RANGESPLIT[1] ~ '.' ~ RANGESPLIT[2] ~ '.'}) %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% for container, vals in DOCKERMERGED.containers.items() %}
|
{% for container, vals in DOCKERMERGED.containers.items() %}
|
||||||
{% set CONTAINER_NETS = vals.get('networks', ['sobridge']) %}
|
{% do DOCKERMERGED.containers[container].update({'ip': FIRSTTHREE ~ DOCKERMERGED.containers[container].final_octet}) %}
|
||||||
{% set IPS = {} %}
|
|
||||||
{% for netname in CONTAINER_NETS %}
|
|
||||||
{% do IPS.update({netname: DOCKERMERGED.networks[netname].prefix ~ vals.final_octet}) %}
|
|
||||||
{% endfor %}
|
|
||||||
{% do DOCKERMERGED.containers[container].update({
|
|
||||||
'networks': CONTAINER_NETS,
|
|
||||||
'ips': IPS,
|
|
||||||
'network': CONTAINER_NETS[0],
|
|
||||||
'ip': IPS[CONTAINER_NETS[0]]
|
|
||||||
}) %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
+10
-14
@@ -18,10 +18,10 @@ dockergroup:
|
|||||||
dockerheldpackages:
|
dockerheldpackages:
|
||||||
pkg.installed:
|
pkg.installed:
|
||||||
- pkgs:
|
- pkgs:
|
||||||
- containerd.io: 2.3.6-1.el9
|
- containerd.io: 2.2.1-1.el9
|
||||||
- docker-ce: 3:29.8.1-1.el9
|
- docker-ce: 3:29.2.1-1.el9
|
||||||
- docker-ce-cli: 1:29.8.1-1.el9
|
- docker-ce-cli: 1:29.2.1-1.el9
|
||||||
- docker-ce-rootless-extras: 29.8.1-1.el9
|
- docker-ce-rootless-extras: 29.2.1-1.el9
|
||||||
- hold: True
|
- hold: True
|
||||||
- update_holds: True
|
- update_holds: True
|
||||||
|
|
||||||
@@ -71,19 +71,15 @@ dockerreserveports:
|
|||||||
- source: salt://common/files/99-reserved-ports.conf
|
- source: salt://common/files/99-reserved-ports.conf
|
||||||
- name: /etc/sysctl.d/99-reserved-ports.conf
|
- name: /etc/sysctl.d/99-reserved-ports.conf
|
||||||
|
|
||||||
{% for NETNAME, NETWORK in DOCKERMERGED.networks.items() %}
|
sos_docker_net:
|
||||||
{% if not NETWORK.get('manager_only') or GLOBALS.get('is_manager', False) %}
|
|
||||||
sos_docker_net_{{ NETNAME }}:
|
|
||||||
docker_network.present:
|
docker_network.present:
|
||||||
- name: {{ NETNAME }}
|
- name: sobridge
|
||||||
- subnet: {{ NETWORK.range }}
|
- subnet: {{ DOCKERMERGED.range }}
|
||||||
- gateway: {{ NETWORK.gateway }}
|
- gateway: {{ DOCKERMERGED.gateway }}
|
||||||
- options:
|
- options:
|
||||||
com.docker.network.bridge.name: '{{ NETNAME }}'
|
com.docker.network.bridge.name: 'sobridge'
|
||||||
com.docker.network.driver.mtu: '1500'
|
com.docker.network.driver.mtu: '1500'
|
||||||
com.docker.network.bridge.enable_ip_masquerade: 'true'
|
com.docker.network.bridge.enable_ip_masquerade: 'true'
|
||||||
com.docker.network.bridge.enable_icc: 'true'
|
com.docker.network.bridge.enable_icc: 'true'
|
||||||
com.docker.network.bridge.host_binding_ipv4: '0.0.0.0'
|
com.docker.network.bridge.host_binding_ipv4: '0.0.0.0'
|
||||||
- unless: ip l | grep {{ NETNAME }}
|
- unless: ip l | grep sobridge
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
@@ -7,40 +7,6 @@ docker:
|
|||||||
description: Default docker IP range for containers.
|
description: Default docker IP range for containers.
|
||||||
helpLink: docker
|
helpLink: docker
|
||||||
advanced: True
|
advanced: True
|
||||||
networks:
|
|
||||||
sobridge:
|
|
||||||
description: |
|
|
||||||
The default docker network, carrying most containers. Its range and gateway are taken
|
|
||||||
from the docker.range and docker.gateway settings above rather than set here.
|
|
||||||
helpLink: docker
|
|
||||||
readonly: True
|
|
||||||
advanced: True
|
|
||||||
global: True
|
|
||||||
soauth:
|
|
||||||
range:
|
|
||||||
description: |
|
|
||||||
IP range for the soauth docker network, an isolated network for the authentication
|
|
||||||
services, so that the Kratos and Hydra admin APIs are only reachable from the
|
|
||||||
containers placed on it.
|
|
||||||
helpLink: docker
|
|
||||||
readonly: True
|
|
||||||
advanced: True
|
|
||||||
global: True
|
|
||||||
gateway:
|
|
||||||
description: Gateway for the soauth docker network.
|
|
||||||
helpLink: docker
|
|
||||||
readonly: True
|
|
||||||
advanced: True
|
|
||||||
global: True
|
|
||||||
manager_only:
|
|
||||||
description: |
|
|
||||||
Limits the soauth network to grid members running the authentication containers,
|
|
||||||
instead of creating it on every node.
|
|
||||||
helpLink: docker
|
|
||||||
readonly: True
|
|
||||||
advanced: True
|
|
||||||
global: True
|
|
||||||
forcedType: bool
|
|
||||||
ulimits:
|
ulimits:
|
||||||
description: |
|
description: |
|
||||||
Default ulimit settings applied to all containers via the Docker daemon. Each entry specifies a resource name (e.g. nofile, memlock, core, nproc) with soft and hard limits. Individual container ulimits override these defaults. Valid resource names include: cpu, fsize, data, stack, core, rss, nproc, nofile, memlock, as, locks, sigpending, msgqueue, nice, rtprio, rttime.
|
Default ulimit settings applied to all containers via the Docker daemon. Each entry specifies a resource name (e.g. nofile, memlock, core, nproc) with soft and hard limits. Individual container ulimits override these defaults. Valid resource names include: cpu, fsize, data, stack, core, rss, nproc, nofile, memlock, as, locks, sigpending, msgqueue, nice, rtprio, rttime.
|
||||||
@@ -68,16 +34,6 @@ docker:
|
|||||||
readonly: True
|
readonly: True
|
||||||
advanced: True
|
advanced: True
|
||||||
global: True
|
global: True
|
||||||
networks:
|
|
||||||
description: |
|
|
||||||
Docker networks this container is attached to. The first entry is the container's
|
|
||||||
primary network and determines the address its published ports are forwarded to.
|
|
||||||
Defaults to sobridge when unset.
|
|
||||||
helpLink: docker
|
|
||||||
readonly: True
|
|
||||||
advanced: True
|
|
||||||
global: True
|
|
||||||
forcedType: "[]string"
|
|
||||||
port_bindings:
|
port_bindings:
|
||||||
description: List of port bindings for the container.
|
description: List of port bindings for the container.
|
||||||
helpLink: docker
|
helpLink: docker
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ elastalert:
|
|||||||
- gid: 933
|
- gid: 933
|
||||||
- home: /opt/so/conf/elastalert
|
- home: /opt/so/conf/elastalert
|
||||||
- createhome: False
|
- createhome: False
|
||||||
- shell: /sbin/nologin
|
|
||||||
|
|
||||||
elastalogdir:
|
elastalogdir:
|
||||||
file.directory:
|
file.directory:
|
||||||
@@ -34,8 +33,8 @@ elastalert_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://elastalert/tools/sbin
|
- source: salt://elastalert/tools/sbin
|
||||||
- user: root
|
- user: 933
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#elastalert_sbin_jinja:
|
#elastalert_sbin_jinja:
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ elastalert:
|
|||||||
buffer_time:
|
buffer_time:
|
||||||
minutes: 10
|
minutes: 10
|
||||||
old_query_limit:
|
old_query_limit:
|
||||||
minutes: 1440
|
minutes: 5
|
||||||
es_port: 9200
|
es_port: 9200
|
||||||
es_conn_timeout: 55
|
es_conn_timeout: 55
|
||||||
max_query_size: 5000
|
max_query_size: 5000
|
||||||
|
|||||||
@@ -6,11 +6,9 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- elastalert.config
|
- elastalert.config
|
||||||
- elastalert.sostatus
|
- elastalert.sostatus
|
||||||
|
|
||||||
@@ -30,7 +28,6 @@ so-elastalert:
|
|||||||
- ipv4_address: {{ DOCKERMERGED.containers['so-elastalert'].ip }}
|
- ipv4_address: {{ DOCKERMERGED.containers['so-elastalert'].ip }}
|
||||||
- detach: True
|
- detach: True
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- /opt/so/rules/elastalert:/opt/elastalert/rules/:ro
|
- /opt/so/rules/elastalert:/opt/elastalert/rules/:ro
|
||||||
- /opt/so/log/elastalert:/var/log/elastalert:rw
|
- /opt/so/log/elastalert:/var/log/elastalert:rw
|
||||||
- /opt/so/conf/elastalert/modules/:/opt/elastalert/modules/:ro
|
- /opt/so/conf/elastalert/modules/:/opt/elastalert/modules/:ro
|
||||||
@@ -49,9 +46,8 @@ so-elastalert:
|
|||||||
- {{ XTRAHOST }}
|
- {{ XTRAHOST }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- environment:
|
|
||||||
- REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt
|
|
||||||
{% if DOCKERMERGED.containers['so-elastalert'].extra_env %}
|
{% if DOCKERMERGED.containers['so-elastalert'].extra_env %}
|
||||||
|
- environment:
|
||||||
{% for XTRAENV in DOCKERMERGED.containers['so-elastalert'].extra_env %}
|
{% for XTRAENV in DOCKERMERGED.containers['so-elastalert'].extra_env %}
|
||||||
- {{ XTRAENV }}
|
- {{ XTRAENV }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -69,7 +65,6 @@ so-elastalert:
|
|||||||
- file: elastacustmodulesdir
|
- file: elastacustmodulesdir
|
||||||
- file: elastaconf
|
- file: elastaconf
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: elastaconf
|
- file: elastaconf
|
||||||
- onlyif:
|
- onlyif:
|
||||||
- "so-elasticsearch-query / | jq -r '.version.number[0:1]' | grep -q 9" {# only run this state if elasticsearch is version 9 #}
|
- "so-elasticsearch-query / | jq -r '.version.number[0:1]' | grep -q 9" {# only run this state if elasticsearch is version 9 #}
|
||||||
|
|||||||
@@ -1,80 +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.
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import types
|
|
||||||
|
|
||||||
# stand-ins when ElastAlert isn't installed (CI)
|
|
||||||
try:
|
|
||||||
import elastalert.alerts # noqa: F401
|
|
||||||
except ImportError:
|
|
||||||
|
|
||||||
class Alerter:
|
|
||||||
def __init__(self, rule):
|
|
||||||
self.rule = rule
|
|
||||||
|
|
||||||
class DateTimeEncoder(json.JSONEncoder):
|
|
||||||
def default(self, obj):
|
|
||||||
return obj.isoformat() if hasattr(obj, 'isoformat') else json.JSONEncoder.default(self, obj)
|
|
||||||
|
|
||||||
class EAException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def lookup_es_key(doc, term):
|
|
||||||
for part in term.split('.'):
|
|
||||||
if not isinstance(doc, dict) or part not in doc:
|
|
||||||
return None
|
|
||||||
doc = doc[part]
|
|
||||||
return doc
|
|
||||||
|
|
||||||
def ts_to_dt(value):
|
|
||||||
return value if isinstance(value, datetime) else datetime.fromisoformat(value)
|
|
||||||
|
|
||||||
def elasticsearch_client(conf):
|
|
||||||
return None # tests set the alerter's client
|
|
||||||
|
|
||||||
alerts = types.ModuleType('elastalert.alerts')
|
|
||||||
alerts.Alerter = Alerter
|
|
||||||
alerts.DateTimeEncoder = DateTimeEncoder
|
|
||||||
util = types.ModuleType('elastalert.util')
|
|
||||||
util.EAException = EAException
|
|
||||||
util.elastalert_logger = logging.getLogger('elastalert')
|
|
||||||
util.lookup_es_key = lookup_es_key
|
|
||||||
util.ts_to_dt = ts_to_dt
|
|
||||||
util.elasticsearch_client = elasticsearch_client
|
|
||||||
package = types.ModuleType('elastalert')
|
|
||||||
package.alerts = alerts
|
|
||||||
package.util = util
|
|
||||||
sys.modules.update({'elastalert': package, 'elastalert.alerts': alerts, 'elastalert.util': util})
|
|
||||||
|
|
||||||
# stand-ins when elasticsearch-py isn't installed (CI)
|
|
||||||
try:
|
|
||||||
import elasticsearch.exceptions # noqa: F401
|
|
||||||
except ImportError:
|
|
||||||
|
|
||||||
class ElasticsearchException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class TransportError(ElasticsearchException):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ConnectionError(TransportError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ConflictError(TransportError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class RequestError(TransportError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
exceptions = types.ModuleType('elasticsearch.exceptions')
|
|
||||||
for cls in (ElasticsearchException, TransportError, ConnectionError, ConflictError, RequestError):
|
|
||||||
setattr(exceptions, cls.__name__, cls)
|
|
||||||
es_package = types.ModuleType('elasticsearch')
|
|
||||||
es_package.exceptions = exceptions
|
|
||||||
sys.modules.update({'elasticsearch': es_package, 'elasticsearch.exceptions': exceptions})
|
|
||||||
@@ -5,265 +5,59 @@
|
|||||||
# https://securityonion.net/license; you may not use this file except in compliance with the
|
# https://securityonion.net/license; you may not use this file except in compliance with the
|
||||||
# Elastic License 2.0.
|
# Elastic License 2.0.
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
import hashlib
|
from time import gmtime, strftime
|
||||||
import ipaddress
|
import requests,json
|
||||||
import json
|
from elastalert.alerts import Alerter
|
||||||
import re
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import urllib3
|
import urllib3
|
||||||
from elasticsearch.exceptions import ConflictError, ElasticsearchException, RequestError
|
|
||||||
from elastalert.alerts import Alerter, DateTimeEncoder
|
|
||||||
from elastalert.util import EAException, elastalert_logger, elasticsearch_client, lookup_es_key, ts_to_dt
|
|
||||||
|
|
||||||
# grid runs verify_certs: false; also quiets ElastAlert's own queries
|
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
ALERT_INDEX = 'logs-detections.alerts-so'
|
|
||||||
# ES error text kept in logs and alerts
|
|
||||||
ERROR_TEXT_LIMIT = 500
|
|
||||||
# a match missing backend columns (window_start, count, @timestamp); the alert is still written
|
|
||||||
MATCH_ERRORS = (KeyError, TypeError, ValueError)
|
|
||||||
|
|
||||||
|
|
||||||
class SecurityOnionESAlerter(Alerter):
|
class SecurityOnionESAlerter(Alerter):
|
||||||
"""
|
"""
|
||||||
Use matched data to create alerts in Elasticsearch.
|
Use matched data to create alerts in Elasticsearch.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
required_options = {'detection_title', 'sigma_level'}
|
required_options = set(['detection_title', 'sigma_level'])
|
||||||
optional_fields = ['sigma_category', 'sigma_product', 'sigma_service', 'sigma_correlation']
|
optional_fields = ['sigma_category', 'sigma_product', 'sigma_service']
|
||||||
|
|
||||||
# count column and default summary per type; stored alert data, so not localized
|
|
||||||
CORRELATION_COUNTS = {
|
|
||||||
'event_count': ('event_count', '%count% events'),
|
|
||||||
'value_count': ('value_count', '%count% distinct values'),
|
|
||||||
'temporal': ('event_type_count', '%count% correlated rules matched'),
|
|
||||||
'value_sum': ('value_sum', 'total %count%'),
|
|
||||||
'value_avg': ('value_avg', 'average %count%'),
|
|
||||||
'value_percentile': ('value_percentile', 'percentile %count%'),
|
|
||||||
'value_median': ('value_median', 'median %count%'),
|
|
||||||
}
|
|
||||||
PLACEHOLDER = re.compile(r'%([^%\s]+)%')
|
|
||||||
# group-by fields copied into ECS related.*
|
|
||||||
RELATED_USERS = {'user.name', 'winlog.event_data.TargetUserName', 'winlog.event_data.SubjectUserName'}
|
|
||||||
RELATED_HOSTS = {'host.name', 'host.hostname', 'winlog.computer_name'}
|
|
||||||
|
|
||||||
def __init__(self, rule):
|
|
||||||
super().__init__(rule)
|
|
||||||
# uses the grid's TLS, auth and timeout settings
|
|
||||||
self.es = elasticsearch_client(rule)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_correlation(self):
|
|
||||||
return bool(self.rule.get('sigma_correlation'))
|
|
||||||
|
|
||||||
def query_keys(self):
|
|
||||||
"""compound_query_key holds the list; query_key is flattened to a string."""
|
|
||||||
if self.rule.get('compound_query_key'):
|
|
||||||
return self.rule['compound_query_key']
|
|
||||||
if self.rule.get('query_key'):
|
|
||||||
return [self.rule['query_key']]
|
|
||||||
return []
|
|
||||||
|
|
||||||
def alert_id(self, match):
|
|
||||||
"""Stable id: window end + group values for correlations, source _id otherwise; random without one."""
|
|
||||||
if self.is_correlation:
|
|
||||||
# ungrouped rows have a hashed _id that changes with the count
|
|
||||||
values = ''.join(f"|{lookup_es_key(match, k)}" for k in self.query_keys())
|
|
||||||
key = f"{self.rule['detection_public_id']}|{ts_to_dt(match['@timestamp']).isoformat()}{values}"
|
|
||||||
elif match.get('_id'):
|
|
||||||
key = f"{self.rule['detection_public_id']}|{match['_id']}"
|
|
||||||
else:
|
|
||||||
return uuid.uuid4().hex
|
|
||||||
|
|
||||||
return hashlib.sha256(key.encode('utf-8')).hexdigest()
|
|
||||||
|
|
||||||
def group(self, match):
|
|
||||||
"""Group-by values, joined like ElastAlert's realert key."""
|
|
||||||
return ', '.join(str(lookup_es_key(match, k)) for k in self.query_keys())
|
|
||||||
|
|
||||||
def related_bucket(self, key):
|
|
||||||
if key == 'ip' or key.endswith('.ip'):
|
|
||||||
return 'ip'
|
|
||||||
if key in self.RELATED_USERS or key.endswith('.user.name'):
|
|
||||||
return 'user'
|
|
||||||
if key in self.RELATED_HOSTS:
|
|
||||||
return 'hosts'
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def valid_ip(value):
|
|
||||||
try:
|
|
||||||
ipaddress.ip_address(value)
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def related(self, match):
|
|
||||||
"""ECS related.* from group-by values; skips invalid IPs."""
|
|
||||||
related = {}
|
|
||||||
for key in self.query_keys():
|
|
||||||
bucket = self.related_bucket(key)
|
|
||||||
if not bucket:
|
|
||||||
continue
|
|
||||||
# original spellings of a lowercased group
|
|
||||||
value = lookup_es_key(match, f"{key}_spellings")
|
|
||||||
if value is None:
|
|
||||||
value = lookup_es_key(match, key)
|
|
||||||
for v in value if isinstance(value, list) else [value]:
|
|
||||||
if v is None or (bucket == 'ip' and not self.valid_ip(str(v))):
|
|
||||||
continue
|
|
||||||
# dict: ordered and deduped
|
|
||||||
related.setdefault(bucket, {})[str(v)] = None
|
|
||||||
return {bucket: list(values) for bucket, values in related.items()}
|
|
||||||
|
|
||||||
def event_data(self, match):
|
|
||||||
"""The match minus the compound query_key field, which ES would map by its last part."""
|
|
||||||
if not self.rule.get('compound_query_key'):
|
|
||||||
return match
|
|
||||||
return {k: v for k, v in match.items() if k != self.rule['query_key']}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_value(value):
|
|
||||||
if isinstance(value, list):
|
|
||||||
shown = ', '.join(str(v) for v in value[:3])
|
|
||||||
return shown if len(value) <= 3 else f"{shown} and {len(value) - 3} more"
|
|
||||||
return str(value)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_count(value):
|
|
||||||
if isinstance(value, float) and not value.is_integer():
|
|
||||||
return f"{value:,.2f}"
|
|
||||||
if isinstance(value, (int, float)):
|
|
||||||
return f"{int(value):,}"
|
|
||||||
return str(value)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def format_duration(seconds):
|
|
||||||
for unit, size in (('hour', 3600), ('minute', 60)):
|
|
||||||
if seconds >= 2 * size:
|
|
||||||
return f"{seconds // size} {unit}s"
|
|
||||||
return f"{seconds} second{'' if seconds == 1 else 's'}"
|
|
||||||
|
|
||||||
def summary(self, match):
|
|
||||||
"""One-line correlation summary."""
|
|
||||||
column, label = self.CORRELATION_COUNTS.get(self.rule['sigma_correlation'], (None, '%count%'))
|
|
||||||
start = ts_to_dt(match['window_start'])
|
|
||||||
end = ts_to_dt(match['@timestamp'])
|
|
||||||
values = {
|
|
||||||
'count': self.format_count(match.get(column)),
|
|
||||||
'start': start.strftime('%Y-%m-%d %H:%M:%S UTC'),
|
|
||||||
'end': end.strftime('%Y-%m-%d %H:%M:%S UTC'),
|
|
||||||
'duration': self.format_duration(int((end - start).total_seconds())),
|
|
||||||
}
|
|
||||||
|
|
||||||
template = self.rule.get('summary_template')
|
|
||||||
if not template:
|
|
||||||
groups = ', '.join(f"{k} %{k}%" for k in self.query_keys())
|
|
||||||
template = f"{label} for {groups} in %duration%" if groups else f"{label} in %duration%"
|
|
||||||
|
|
||||||
def fill(m):
|
|
||||||
if m[1] in values:
|
|
||||||
return values[m[1]]
|
|
||||||
value = lookup_es_key(match, m[1])
|
|
||||||
# unknown placeholders stay visible so typos show
|
|
||||||
return m[0] if value is None else self.format_value(value)
|
|
||||||
|
|
||||||
return self.PLACEHOLDER.sub(fill, template)
|
|
||||||
|
|
||||||
def alert(self, matches):
|
def alert(self, matches):
|
||||||
for match in matches:
|
for match in matches:
|
||||||
try:
|
timestamp = strftime("%Y-%m-%d"'T'"%H:%M:%S"'.000Z', gmtime())
|
||||||
alert_id = self.alert_id(match)
|
headers = {"Content-Type": "application/json"}
|
||||||
except MATCH_ERRORS as e:
|
|
||||||
elastalert_logger.warning("Writing alert for rule %s without a stable id, so a retry may duplicate it: %r",
|
|
||||||
self.rule['detection_public_id'], e)
|
|
||||||
alert_id = uuid.uuid4().hex
|
|
||||||
try:
|
|
||||||
self.write(alert_id, self.payload(match))
|
|
||||||
except ElasticsearchException as e:
|
|
||||||
# EAException makes ElastAlert retry
|
|
||||||
raise EAException(f"Unable to write the alert to Elasticsearch: {str(e)[:ERROR_TEXT_LIMIT]}") from e
|
|
||||||
|
|
||||||
def payload(self, match):
|
creds = None
|
||||||
rule_info = {
|
if 'es_username' in self.rule and 'es_password' in self.rule:
|
||||||
"name": self.rule['detection_title'],
|
creds = (self.rule['es_username'], self.rule['es_password'])
|
||||||
"uuid": self.rule['detection_public_id']
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add optional fields if they are present in the rule
|
# Start building the rule dict
|
||||||
for field in self.optional_fields:
|
rule_info = {
|
||||||
rule_key = field.split('_')[-1] # Assumes field format "sigma_<key>"
|
"name": self.rule['detection_title'],
|
||||||
if field in self.rule:
|
"uuid": self.rule['detection_public_id']
|
||||||
rule_info[rule_key] = self.rule[field]
|
}
|
||||||
|
|
||||||
event_info = {
|
# Add optional fields if they are present in the rule
|
||||||
"kind": "alert",
|
for field in self.optional_fields:
|
||||||
"severity": self.rule['event.severity'],
|
rule_key = field.split('_')[-1] # Assumes field format "sigma_<key>"
|
||||||
"module": self.rule['event.module'],
|
if field in self.rule:
|
||||||
"dataset": self.rule['event.dataset'],
|
rule_info[rule_key] = self.rule[field]
|
||||||
"severity_label": self.rule['sigma_level']
|
|
||||||
}
|
|
||||||
|
|
||||||
payload = {
|
# Construct the payload with the conditional rule_info
|
||||||
"tags": ["alert"],
|
payload = {
|
||||||
"rule": rule_info,
|
"tags": "alert",
|
||||||
"event": event_info,
|
"rule": rule_info,
|
||||||
"sigma_level": self.rule['sigma_level'],
|
"event": {
|
||||||
"event_data": self.event_data(match),
|
"severity": self.rule['event.severity'],
|
||||||
"@timestamp": datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.000Z')
|
"module": self.rule['event.module'],
|
||||||
}
|
"dataset": self.rule['event.dataset'],
|
||||||
|
"severity_label": self.rule['sigma_level']
|
||||||
if self.is_correlation:
|
},
|
||||||
keys = self.query_keys()
|
"sigma_level": self.rule['sigma_level'],
|
||||||
try:
|
"event_data": match,
|
||||||
# built before any is added, so a failure adds none
|
"@timestamp": timestamp
|
||||||
reason = self.summary(match)
|
}
|
||||||
labels = {"correlation_group_by": ', '.join(keys), "correlation_group": self.group(match)} if keys else None
|
url = f"https://{self.rule['es_host']}:{self.rule['es_port']}/logs-detections.alerts-so/_doc/"
|
||||||
related = self.related(match)
|
requests.post(url, data=json.dumps(payload), headers=headers, verify=False, auth=creds)
|
||||||
except MATCH_ERRORS as e:
|
|
||||||
elastalert_logger.warning("Writing alert for rule %s without its correlation summary: %r",
|
|
||||||
self.rule['detection_public_id'], e)
|
|
||||||
else:
|
|
||||||
payload["event"]["reason"] = reason
|
|
||||||
if labels:
|
|
||||||
payload["labels"] = labels
|
|
||||||
if related:
|
|
||||||
payload["related"] = related
|
|
||||||
|
|
||||||
return payload
|
|
||||||
|
|
||||||
def write(self, alert_id, payload):
|
|
||||||
try:
|
|
||||||
self.create(alert_id, payload)
|
|
||||||
except RequestError as e:
|
|
||||||
# mapping rejections come from event_data; retry it as text
|
|
||||||
rejection = str(e)[:ERROR_TEXT_LIMIT]
|
|
||||||
try:
|
|
||||||
self.create(alert_id, self.without_event_data(payload, rejection))
|
|
||||||
except RequestError as again:
|
|
||||||
elastalert_logger.error("Dropping alert %s for rule %s, rejected by Elasticsearch even without its event data: %s; first rejection: %s",
|
|
||||||
alert_id, self.rule['detection_public_id'], str(again)[:ERROR_TEXT_LIMIT], rejection)
|
|
||||||
return
|
|
||||||
elastalert_logger.warning("Stored alert %s for rule %s with its event data as text, rejected by Elasticsearch: %s",
|
|
||||||
alert_id, self.rule['detection_public_id'], rejection)
|
|
||||||
|
|
||||||
def create(self, alert_id, payload):
|
|
||||||
try:
|
|
||||||
self.es.create(index=ALERT_INDEX, id=alert_id, body=payload)
|
|
||||||
except ConflictError:
|
|
||||||
pass # a repeat id is already stored
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def without_event_data(payload, rejection):
|
|
||||||
"""Moves event_data to event.original; the tag keeps Fleet's final pipeline from dropping it."""
|
|
||||||
fallback = {k: v for k, v in payload.items() if k != 'event_data'}
|
|
||||||
fallback['event'] = dict(payload['event'], original=json.dumps(payload['event_data'], cls=DateTimeEncoder))
|
|
||||||
fallback['error'] = {'message': f"event_data rejected by Elasticsearch: {rejection}"}
|
|
||||||
fallback['tags'] = payload['tags'] + ['preserve_original_event']
|
|
||||||
return fallback
|
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self):
|
||||||
return {'type': 'SecurityOnionESAlerter'}
|
return {'type': 'SecurityOnionESAlerter'}
|
||||||
@@ -1,260 +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.
|
|
||||||
|
|
||||||
import copy
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
import importlib.util
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import unittest
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
from elasticsearch.exceptions import ConflictError, ConnectionError, RequestError
|
|
||||||
|
|
||||||
spec = importlib.util.spec_from_file_location('securityonion_es', os.path.join(os.path.dirname(__file__), 'securityonion-es.py'))
|
|
||||||
es = importlib.util.module_from_spec(spec)
|
|
||||||
spec.loader.exec_module(es)
|
|
||||||
|
|
||||||
BASE_RULE = {
|
|
||||||
'detection_title': 'Many Failed Network Logons To One Host From One Source',
|
|
||||||
'detection_public_id': '35a42db6-6629-45af-b8aa-e1fa33c28ef5',
|
|
||||||
'sigma_level': 'medium',
|
|
||||||
'sigma_correlation': 'event_count',
|
|
||||||
'event.severity': 3,
|
|
||||||
'event.module': 'sigma',
|
|
||||||
'event.dataset': 'sigma.alert',
|
|
||||||
'summary_template': '%count% failed network logons to %host.name% from %source.ip% in %duration%',
|
|
||||||
}
|
|
||||||
|
|
||||||
PLAIN_RULE = {k: v for k, v in BASE_RULE.items() if k not in ('sigma_correlation', 'summary_template')}
|
|
||||||
|
|
||||||
|
|
||||||
def correlation_match():
|
|
||||||
return {
|
|
||||||
'event_count': 3561,
|
|
||||||
'window_start': '2026-09-30T18:05:10+00:00',
|
|
||||||
'@timestamp': '2026-09-30T18:07:53+00:00',
|
|
||||||
'host': {'name': 'host-01'},
|
|
||||||
'source': {'ip': '192.0.2.10'},
|
|
||||||
'_id': '6d1c',
|
|
||||||
'num_hits': 1,
|
|
||||||
'num_matches': 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TestSecurityOnionESAlerter(unittest.TestCase):
|
|
||||||
|
|
||||||
def creates(self, rule, match, effects=None):
|
|
||||||
"""Run alert(); return (body, id) of each create."""
|
|
||||||
alerter = es.SecurityOnionESAlerter(rule)
|
|
||||||
alerter.es = MagicMock()
|
|
||||||
alerter.es.create.side_effect = effects
|
|
||||||
alerter.alert([match])
|
|
||||||
calls = alerter.es.create.call_args_list
|
|
||||||
self.assertTrue(all(c.kwargs['index'] == 'logs-detections.alerts-so' for c in calls))
|
|
||||||
return [(json.loads(json.dumps(c.kwargs['body'], cls=es.DateTimeEncoder)), c.kwargs['id']) for c in calls]
|
|
||||||
|
|
||||||
def send(self, rule, match):
|
|
||||||
"""Run alert(); return the payload it wrote and its id."""
|
|
||||||
(payload, alert_id), = self.creates(rule, match)
|
|
||||||
return payload, alert_id
|
|
||||||
|
|
||||||
def test_compound_query_key_left_out_of_event_data(self):
|
|
||||||
rule = dict(BASE_RULE, compound_query_key=['host.name', 'source.ip'], query_key='host.name,source.ip')
|
|
||||||
match = correlation_match()
|
|
||||||
match['host.name,source.ip'] = 'host-01, 192.0.2.10'
|
|
||||||
original = copy.deepcopy(match)
|
|
||||||
|
|
||||||
payload, _ = self.send(rule, match)
|
|
||||||
|
|
||||||
self.assertNotIn('host.name,source.ip', payload['event_data'])
|
|
||||||
self.assertEqual(payload['event_data']['host'], {'name': 'host-01'})
|
|
||||||
self.assertEqual(payload['event_data']['source'], {'ip': '192.0.2.10'})
|
|
||||||
self.assertEqual(payload['rule'], {'name': BASE_RULE['detection_title'], 'uuid': BASE_RULE['detection_public_id'], 'correlation': 'event_count'})
|
|
||||||
self.assertEqual(payload['labels'], {'correlation_group_by': 'host.name, source.ip', 'correlation_group': 'host-01, 192.0.2.10'})
|
|
||||||
self.assertEqual(payload['related'], {'hosts': ['host-01'], 'ip': ['192.0.2.10']})
|
|
||||||
self.assertEqual(payload['event']['kind'], 'alert')
|
|
||||||
self.assertEqual(payload['event']['reason'], '3,561 failed network logons to host-01 from 192.0.2.10 in 2 minutes')
|
|
||||||
# ElastAlert reuses the match
|
|
||||||
self.assertEqual(match, original)
|
|
||||||
|
|
||||||
def test_single_query_key(self):
|
|
||||||
rule = dict(BASE_RULE, query_key='user.name', summary_template='%count% failed SOC logins for %user.name%')
|
|
||||||
match = {'event_count': 3, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00',
|
|
||||||
'user': {'name': 'user@example.invalid'}}
|
|
||||||
|
|
||||||
payload, _ = self.send(rule, match)
|
|
||||||
|
|
||||||
self.assertEqual(payload['labels'], {'correlation_group_by': 'user.name', 'correlation_group': 'user@example.invalid'})
|
|
||||||
self.assertEqual(payload['related'], {'user': ['user@example.invalid']})
|
|
||||||
self.assertEqual(payload['event']['reason'], '3 failed SOC logins for user@example.invalid')
|
|
||||||
self.assertEqual(payload['event_data'], match)
|
|
||||||
|
|
||||||
def test_related_buckets(self):
|
|
||||||
rule = dict(BASE_RULE, compound_query_key=['winlog.event_data.TargetUserName', 'source.ip', 'dns.highest_registered_domain'],
|
|
||||||
query_key='winlog.event_data.TargetUserName,source.ip,dns.highest_registered_domain')
|
|
||||||
match = correlation_match()
|
|
||||||
match.update({'winlog': {'event_data': {'TargetUserName': ['admin1', 'svc', 'admin1']}},
|
|
||||||
'source': {'ip': 'not-an-ip'}, 'dns': {'highest_registered_domain': 'example.com'}})
|
|
||||||
|
|
||||||
payload, _ = self.send(rule, match)
|
|
||||||
|
|
||||||
# deduped; invalid IP skipped; domain stays in the group only
|
|
||||||
self.assertEqual(payload['related'], {'user': ['admin1', 'svc']})
|
|
||||||
self.assertEqual(payload['labels']['correlation_group'], "['admin1', 'svc', 'admin1'], not-an-ip, example.com")
|
|
||||||
|
|
||||||
payload, _ = self.send(dict(BASE_RULE, query_key='dns.highest_registered_domain'), match)
|
|
||||||
|
|
||||||
self.assertNotIn('related', payload)
|
|
||||||
|
|
||||||
def test_related_uses_original_spellings(self):
|
|
||||||
rule = dict(BASE_RULE, query_key='user.name', summary_template=None)
|
|
||||||
match = {'event_count': 3, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00',
|
|
||||||
'user': {'name': 'admin', 'name_spellings': ['Admin', 'admin', 'ADMIN']}}
|
|
||||||
|
|
||||||
payload, _ = self.send(rule, match)
|
|
||||||
|
|
||||||
# the group shows the lowercased value; related.user finds every spelling
|
|
||||||
self.assertEqual(payload['labels']['correlation_group'], 'admin')
|
|
||||||
self.assertEqual(payload['related'], {'user': ['Admin', 'admin', 'ADMIN']})
|
|
||||||
self.assertEqual(payload['event']['reason'], '3 events for user.name admin in 8 seconds')
|
|
||||||
|
|
||||||
def test_plain_rule_has_no_correlation_fields(self):
|
|
||||||
# a query_key alone (e.g. from an override) isn't a correlation
|
|
||||||
rule = dict(PLAIN_RULE, query_key='user.name')
|
|
||||||
# ElastAlert parses @timestamp for EQL hits
|
|
||||||
match = {'@timestamp': datetime(2026, 9, 30, 16, 50, tzinfo=timezone.utc), '_id': 'abc',
|
|
||||||
'process': {'name': 'whoami.exe'}, 'user': {'name': 'user'}}
|
|
||||||
|
|
||||||
payload, alert_id = self.send(rule, match)
|
|
||||||
|
|
||||||
alerter = es.SecurityOnionESAlerter(rule)
|
|
||||||
self.assertEqual(payload['rule'], {'name': PLAIN_RULE['detection_title'], 'uuid': PLAIN_RULE['detection_public_id']})
|
|
||||||
self.assertNotIn('labels', payload)
|
|
||||||
self.assertNotIn('related', payload)
|
|
||||||
self.assertNotIn('reason', payload['event'])
|
|
||||||
self.assertEqual(payload['event']['kind'], 'alert')
|
|
||||||
self.assertEqual(payload['event_data'], dict(match, **{'@timestamp': '2026-09-30T16:50:00+00:00'}))
|
|
||||||
self.assertEqual(alert_id, alerter.alert_id(match))
|
|
||||||
self.assertNotEqual(alerter.alert_id(match), alerter.alert_id(dict(match, _id='abd')))
|
|
||||||
self.assertNotEqual(alerter.alert_id(match), es.SecurityOnionESAlerter(dict(rule, detection_public_id='other')).alert_id(match))
|
|
||||||
# without an _id, never deduplicated
|
|
||||||
self.assertNotEqual(alerter.alert_id({'_id': None}), alerter.alert_id({'_id': None}))
|
|
||||||
|
|
||||||
def test_ungrouped_correlation_id_ignores_row_hash(self):
|
|
||||||
rule = dict(BASE_RULE, summary_template=None)
|
|
||||||
first = {k: v for k, v in correlation_match().items() if k not in ('host', 'source')}
|
|
||||||
# ES|QL hashes the row into _id, so a later count changes it
|
|
||||||
later = dict(first, event_count=3600, _id='9f2a')
|
|
||||||
|
|
||||||
payload, alert_id = self.send(rule, first)
|
|
||||||
|
|
||||||
alerter = es.SecurityOnionESAlerter(rule)
|
|
||||||
self.assertEqual(alerter.alert_id(first), alerter.alert_id(later))
|
|
||||||
self.assertNotEqual(alerter.alert_id(first), alerter.alert_id(dict(first, **{'@timestamp': '2026-09-30T18:09:00+00:00'})))
|
|
||||||
self.assertEqual(alert_id, alerter.alert_id(first))
|
|
||||||
self.assertEqual(payload['event']['reason'], '3,561 events in 2 minutes')
|
|
||||||
self.assertNotIn('labels', payload)
|
|
||||||
|
|
||||||
def test_temporal_count_column(self):
|
|
||||||
rule = dict(BASE_RULE, sigma_correlation='temporal', summary_template=None)
|
|
||||||
match = {'event_type_count': 2, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00'}
|
|
||||||
|
|
||||||
payload, _ = self.send(rule, match)
|
|
||||||
|
|
||||||
self.assertEqual(payload['event']['reason'], '2 correlated rules matched in 8 seconds')
|
|
||||||
|
|
||||||
def test_grouped_correlation_id_unchanged(self):
|
|
||||||
"""Ids of alerts already written must not change."""
|
|
||||||
rule = dict(BASE_RULE, compound_query_key=['host.name', 'source.ip'], query_key='host.name,source.ip')
|
|
||||||
key = f"{BASE_RULE['detection_public_id']}|2026-09-30T18:07:53+00:00|host-01|192.0.2.10"
|
|
||||||
|
|
||||||
self.assertEqual(es.SecurityOnionESAlerter(rule).alert_id(correlation_match()), es.hashlib.sha256(key.encode()).hexdigest())
|
|
||||||
|
|
||||||
def test_rejected_event_data_is_kept_as_text(self):
|
|
||||||
rule = dict(BASE_RULE, query_key='user.name', summary_template='%count% failed SOC logins for %user.name%')
|
|
||||||
match = {'event_count': 3, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00',
|
|
||||||
'user': {'name': 'user@example.invalid'}}
|
|
||||||
rejected = RequestError(400, 'document_parsing_exception', {})
|
|
||||||
|
|
||||||
(first, _), (second, _) = self.creates(rule, match, [rejected, None])
|
|
||||||
|
|
||||||
self.assertIn('event_data', first)
|
|
||||||
self.assertNotIn('event_data', second)
|
|
||||||
self.assertEqual(json.loads(second['event']['original']), match)
|
|
||||||
self.assertEqual(second['tags'], ['alert', 'preserve_original_event'])
|
|
||||||
self.assertEqual(first['tags'], ['alert'])
|
|
||||||
self.assertTrue(second['error']['message'].startswith('event_data rejected by Elasticsearch: '))
|
|
||||||
self.assertIn('document_parsing_exception', second['error']['message'])
|
|
||||||
# everything else carries over
|
|
||||||
self.assertEqual({k: v for k, v in second['event'].items() if k != 'original'}, first['event'])
|
|
||||||
changed = ('event_data', 'event', 'error', 'tags')
|
|
||||||
self.assertEqual({k: v for k, v in second.items() if k not in changed}, {k: v for k, v in first.items() if k not in changed})
|
|
||||||
|
|
||||||
def test_rejected_dates_are_kept_as_text(self):
|
|
||||||
match = {'@timestamp': datetime(2026, 9, 30, 16, 50, tzinfo=timezone.utc), '_id': 'abc'}
|
|
||||||
|
|
||||||
_, (second, _) = self.creates(PLAIN_RULE, match, [RequestError(400, 'document_parsing_exception', {}), None])
|
|
||||||
|
|
||||||
self.assertEqual(json.loads(second['event']['original']), {'@timestamp': '2026-09-30T16:50:00+00:00', '_id': 'abc'})
|
|
||||||
|
|
||||||
def test_rejected_twice_is_dropped_without_retry(self):
|
|
||||||
rejected = RequestError(400, 'document_parsing_exception', {})
|
|
||||||
match = {'@timestamp': '2026-09-30T16:50:00+00:00', '_id': 'abc'}
|
|
||||||
|
|
||||||
with self.assertLogs('elastalert', 'ERROR') as logs:
|
|
||||||
self.assertEqual(len(self.creates(PLAIN_RULE, match, [rejected, rejected])), 2)
|
|
||||||
|
|
||||||
self.assertIn('Dropping alert', logs.output[0])
|
|
||||||
|
|
||||||
def test_write_failure_is_retried(self):
|
|
||||||
match = {'@timestamp': '2026-09-30T16:50:00+00:00', '_id': 'abc'}
|
|
||||||
|
|
||||||
with self.assertRaisesRegex(es.EAException, 'Unable to write the alert to Elasticsearch'):
|
|
||||||
self.creates(PLAIN_RULE, match, [ConnectionError('N/A', 'refused', None)])
|
|
||||||
|
|
||||||
def test_repeat_id_is_ignored(self):
|
|
||||||
match = {'@timestamp': '2026-09-30T16:50:00+00:00', '_id': 'abc'}
|
|
||||||
|
|
||||||
self.assertEqual(len(self.creates(PLAIN_RULE, match, [ConflictError(409, 'version_conflict_engine_exception', {})])), 1)
|
|
||||||
|
|
||||||
def test_correlation_fields_are_optional(self):
|
|
||||||
rule = dict(BASE_RULE, query_key='source.ip')
|
|
||||||
# no window_start: the summary cannot be built
|
|
||||||
match = {k: v for k, v in correlation_match().items() if k != 'window_start'}
|
|
||||||
|
|
||||||
with self.assertLogs('elastalert', 'WARNING') as logs:
|
|
||||||
payload, _ = self.send(rule, match)
|
|
||||||
|
|
||||||
self.assertIn('without its correlation summary', logs.output[0])
|
|
||||||
self.assertEqual(payload['event']['kind'], 'alert')
|
|
||||||
self.assertNotIn('reason', payload['event'])
|
|
||||||
self.assertNotIn('labels', payload)
|
|
||||||
self.assertNotIn('related', payload)
|
|
||||||
|
|
||||||
def test_unstable_id_still_writes(self):
|
|
||||||
# no @timestamp: the window end is unknown
|
|
||||||
match = {k: v for k, v in correlation_match().items() if k not in ('@timestamp', 'window_start')}
|
|
||||||
|
|
||||||
with self.assertLogs('elastalert', 'WARNING') as logs:
|
|
||||||
payload, alert_id = self.send(BASE_RULE, match)
|
|
||||||
|
|
||||||
self.assertIn('without a stable id', logs.output[0])
|
|
||||||
self.assertEqual(len(alert_id), 32)
|
|
||||||
self.assertEqual(payload['event_data'], match)
|
|
||||||
|
|
||||||
def test_summary_formats_values(self):
|
|
||||||
rule = dict(BASE_RULE, sigma_correlation='value_avg', query_key='source.ip',
|
|
||||||
summary_template='%count% for %source.ip% to %destination.port% %no.such.field%')
|
|
||||||
match = {'value_avg': 2.5, 'window_start': '2026-09-30T16:49:52+00:00', '@timestamp': '2026-09-30T16:50:00+00:00',
|
|
||||||
'source': {'ip': '192.0.2.10'}, 'destination': {'port': [22, 80, 443, 8080, 8443]}}
|
|
||||||
|
|
||||||
payload, _ = self.send(rule, match)
|
|
||||||
|
|
||||||
self.assertEqual(payload['event']['reason'], '2.50 for 192.0.2.10 to 22, 80, 443 and 2 more %no.such.field%')
|
|
||||||
self.assertEqual(es.SecurityOnionESAlerter.format_count('n/a'), 'n/a')
|
|
||||||
|
|
||||||
def test_get_info(self):
|
|
||||||
self.assertEqual(es.SecurityOnionESAlerter(PLAIN_RULE).get_info(), {'type': 'SecurityOnionESAlerter'})
|
|
||||||
@@ -120,7 +120,7 @@ elastalert:
|
|||||||
helpLink: elastalert
|
helpLink: elastalert
|
||||||
old_query_limit:
|
old_query_limit:
|
||||||
minutes:
|
minutes:
|
||||||
description: How long ElastAlert can be down, in minutes, and still resume each rule where it stopped. After a longer outage, rules restart from now and skip the gap.
|
description: Amount of time in minutes between queries to start at the most recently run query.
|
||||||
global: True
|
global: True
|
||||||
helpLink: elastalert
|
helpLink: elastalert
|
||||||
es_conn_timeout:
|
es_conn_timeout:
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ elastic-agent-pr:
|
|||||||
- gid: 948
|
- gid: 948
|
||||||
- home: /opt/so/conf/elastic-fleet-pr
|
- home: /opt/so/conf/elastic-fleet-pr
|
||||||
- createhome: False
|
- createhome: False
|
||||||
- shell: /sbin/nologin
|
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,9 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- elastic-fleet-package-registry.config
|
- elastic-fleet-package-registry.config
|
||||||
- elastic-fleet-package-registry.sostatus
|
- elastic-fleet-package-registry.sostatus
|
||||||
|
|
||||||
@@ -36,9 +34,8 @@ so-elastic-fleet-package-registry:
|
|||||||
{% for BINDING in DOCKERMERGED.containers['so-elastic-fleet-package-registry'].port_bindings %}
|
{% for BINDING in DOCKERMERGED.containers['so-elastic-fleet-package-registry'].port_bindings %}
|
||||||
- {{ BINDING }}
|
- {{ BINDING }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
- binds:
|
|
||||||
- {{ CATRUST.pem }}:/etc/ssl/certs/ca-certificates.crt:ro
|
|
||||||
{% if DOCKERMERGED.containers['so-elastic-fleet-package-registry'].custom_bind_mounts %}
|
{% if DOCKERMERGED.containers['so-elastic-fleet-package-registry'].custom_bind_mounts %}
|
||||||
|
- binds:
|
||||||
{% for BIND in DOCKERMERGED.containers['so-elastic-fleet-package-registry'].custom_bind_mounts %}
|
{% for BIND in DOCKERMERGED.containers['so-elastic-fleet-package-registry'].custom_bind_mounts %}
|
||||||
- {{ BIND }}
|
- {{ BIND }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -55,8 +52,6 @@ so-elastic-fleet-package-registry:
|
|||||||
- {{ ULIMIT.name }}={{ ULIMIT.soft }}:{{ ULIMIT.hard }}
|
- {{ ULIMIT.name }}={{ ULIMIT.soft }}:{{ ULIMIT.hard }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
|
||||||
- cmd: ca_trust_extract
|
|
||||||
|
|
||||||
wait_for_so-elastic-fleet-package-registry:
|
wait_for_so-elastic-fleet-package-registry:
|
||||||
http.wait_for_successful_query:
|
http.wait_for_successful_query:
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ elastic-agent:
|
|||||||
- gid: 949
|
- gid: 949
|
||||||
- home: /opt/so/conf/elastic-agent
|
- home: /opt/so/conf/elastic-agent
|
||||||
- createhome: False
|
- createhome: False
|
||||||
- shell: /sbin/nologin
|
|
||||||
|
|
||||||
elasticagentconfdir:
|
elasticagentconfdir:
|
||||||
file.directory:
|
file.directory:
|
||||||
@@ -40,8 +39,8 @@ elasticagent_sbin_jinja:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://elasticagent/tools/sbin_jinja
|
- source: salt://elasticagent/tools/sbin_jinja
|
||||||
- user: root
|
- user: 949
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
@@ -38,7 +37,6 @@ so-elastic-agent:
|
|||||||
- {{ BINDING }}
|
- {{ BINDING }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- /opt/so/conf/elastic-agent/elastic-agent.yml:/usr/share/elastic-agent/elastic-agent.yml:ro
|
- /opt/so/conf/elastic-agent/elastic-agent.yml:/usr/share/elastic-agent/elastic-agent.yml:ro
|
||||||
- /opt/so/log/elasticagent:/usr/share/elastic-agent/logs
|
- /opt/so/log/elasticagent:/usr/share/elastic-agent/logs
|
||||||
- /etc/pki/tls/certs/intca.crt:/etc/pki/tls/certs/intca.crt:ro
|
- /etc/pki/tls/certs/intca.crt:/etc/pki/tls/certs/intca.crt:ro
|
||||||
@@ -67,7 +65,6 @@ so-elastic-agent:
|
|||||||
- file: create-elastic-agent-config
|
- file: create-elastic-agent-config
|
||||||
- file: trusttheca
|
- file: trusttheca
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: create-elastic-agent-config
|
- file: create-elastic-agent-config
|
||||||
- file: trusttheca
|
- file: trusttheca
|
||||||
|
|
||||||
|
|||||||
@@ -26,14 +26,13 @@ elastic-fleet:
|
|||||||
- gid: 947
|
- gid: 947
|
||||||
- home: /opt/so/conf/elastic-fleet
|
- home: /opt/so/conf/elastic-fleet
|
||||||
- createhome: False
|
- createhome: False
|
||||||
- shell: /sbin/nologin
|
|
||||||
|
|
||||||
elasticfleet_sbin:
|
elasticfleet_sbin:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://elasticfleet/tools/sbin
|
- source: salt://elasticfleet/tools/sbin
|
||||||
- user: root
|
- user: 947
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
|
|
||||||
@@ -41,8 +40,8 @@ elasticfleet_sbin_jinja:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://elasticfleet/tools/sbin_jinja
|
- source: salt://elasticfleet/tools/sbin_jinja
|
||||||
- user: root
|
- user: 947
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- exclude_pat:
|
- exclude_pat:
|
||||||
@@ -82,8 +81,8 @@ eapackageupgrade:
|
|||||||
file.managed:
|
file.managed:
|
||||||
- name: /usr/sbin/so-elastic-fleet-package-upgrade
|
- name: /usr/sbin/so-elastic-fleet-package-upgrade
|
||||||
- source: salt://elasticfleet/tools/sbin_jinja/so-elastic-fleet-package-upgrade
|
- source: salt://elasticfleet/tools/sbin_jinja/so-elastic-fleet-package-upgrade
|
||||||
- user: root
|
- user: 947
|
||||||
- group: root
|
- group: 939
|
||||||
- mode: 755
|
- mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% from 'elasticfleet/map.jinja' import ELASTICFLEETMERGED %}
|
{% from 'elasticfleet/map.jinja' import ELASTICFLEETMERGED %}
|
||||||
|
|
||||||
@@ -64,7 +63,6 @@ so-elastic-fleet:
|
|||||||
- {{ BINDING }}
|
- {{ BINDING }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- /etc/pki/elasticfleet-server.crt:/etc/pki/elasticfleet-server.crt:ro
|
- /etc/pki/elasticfleet-server.crt:/etc/pki/elasticfleet-server.crt:ro
|
||||||
- /etc/pki/elasticfleet-server.key:/etc/pki/elasticfleet-server.key:ro
|
- /etc/pki/elasticfleet-server.key:/etc/pki/elasticfleet-server.key:ro
|
||||||
- /etc/pki/tls/certs/intca.crt:/etc/pki/tls/certs/intca.crt:ro
|
- /etc/pki/tls/certs/intca.crt:/etc/pki/tls/certs/intca.crt:ro
|
||||||
@@ -97,7 +95,6 @@ so-elastic-fleet:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: trusttheca
|
- file: trusttheca
|
||||||
- x509: etc_elasticfleet_key
|
- x509: etc_elasticfleet_key
|
||||||
- x509: etc_elasticfleet_crt
|
- x509: etc_elasticfleet_crt
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
"\\.gz$"
|
"\\.gz$"
|
||||||
],
|
],
|
||||||
"include_files": [],
|
"include_files": [],
|
||||||
"processors": "- dissect:\n tokenizer: \"/nsm/import/%{import.id}/evtx/%{import.file}\"\n field: \"log.file.path\"\n target_prefix: \"\"\n- decode_json_fields:\n fields: [\"message\"]\n target: \"\"\n- add_fields:\n target: event\n fields:\n dataset: windows.forwarded\n module: windows\n imported: true\n- add_fields:\n target: \"@metadata\"\n fields:\n pipeline: import.evtx\n- if:\n equals:\n winlog.channel: 'Security'\n then: \n - add_fields:\n target: event\n fields:\n dataset: system.security\n module: system\n- if:\n equals:\n winlog.channel: 'Windows PowerShell'\n then: \n - add_fields:\n target: event\n fields:\n dataset: windows.powershell\n module: windows\n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-Sysmon/Operational'\n then: \n - add_fields:\n target: event\n fields:\n dataset: windows.sysmon_operational\n module: windows\n imported: true\n- if:\n equals:\n winlog.channel: 'Application'\n then: \n - add_fields:\n target: event\n fields:\n dataset: system.application\n module: system\n- if:\n equals:\n winlog.channel: 'System'\n then: \n - add_fields:\n target: event\n fields:\n dataset: system.system\n module: system\n \n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-PowerShell/Operational'\n then: \n - add_fields:\n target: event\n fields:\n dataset: windows.powershell_operational\n module: windows\n- add_fields:\n target: data_stream\n fields:\n type: logs\n dataset: import",
|
"processors": "- dissect:\n tokenizer: \"/nsm/import/%{import.id}/evtx/%{import.file}\"\n field: \"log.file.path\"\n target_prefix: \"\"\n- decode_json_fields:\n fields: [\"message\"]\n target: \"\"\n- drop_fields:\n fields: [\"host\"]\n ignore_missing: true\n- add_fields:\n target: data_stream\n fields:\n type: logs\n dataset: system.security\n- add_fields:\n target: event\n fields:\n dataset: system.security\n module: system\n imported: true\n- add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.security-2.22.3\n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-Sysmon/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.sysmon_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.sysmon_operational\n module: windows\n imported: true\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.sysmon_operational-3.9.0\n- if:\n equals:\n winlog.channel: 'Application'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.application\n - add_fields:\n target: event\n fields:\n dataset: system.application\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.application-2.22.3\n- if:\n equals:\n winlog.channel: 'System'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: system.system\n - add_fields:\n target: event\n fields:\n dataset: system.system\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-system.system-2.22.3\n \n- if:\n equals:\n winlog.channel: 'Microsoft-Windows-PowerShell/Operational'\n then: \n - add_fields:\n target: data_stream\n fields:\n dataset: windows.powershell_operational\n - add_fields:\n target: event\n fields:\n dataset: windows.powershell_operational\n module: windows\n - add_fields:\n target: \"@metadata\"\n fields:\n pipeline: logs-windows.powershell_operational-3.9.0\n- add_fields:\n target: data_stream\n fields:\n dataset: import",
|
||||||
"tags": [
|
"tags": [
|
||||||
"import"
|
"import"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ so-elastic-agent-install:
|
|||||||
file.managed:
|
file.managed:
|
||||||
- name: /usr/sbin/so-elastic-agent-install
|
- name: /usr/sbin/so-elastic-agent-install
|
||||||
- source: salt://elasticfleet/tools/sbin/so-elastic-agent-install
|
- source: salt://elasticfleet/tools/sbin/so-elastic-agent-install
|
||||||
- user: root
|
- user: 947
|
||||||
- group: root
|
- group: 939
|
||||||
- mode: 755
|
- mode: 755
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
'azure_metrics.monitor': 'azure.monitor',
|
'azure_metrics.monitor': 'azure.monitor',
|
||||||
'azure_metrics.storage_account': 'azure.storage_account',
|
'azure_metrics.storage_account': 'azure.storage_account',
|
||||||
'azure_openai.metrics': 'azure.open_ai',
|
'azure_openai.metrics': 'azure.open_ai',
|
||||||
|
'beat.state': 'beats.stack_monitoring.state',
|
||||||
|
'beat.stats': 'beats.stack_monitoring.stats',
|
||||||
'enterprisesearch.health': 'enterprisesearch.stack_monitoring.health',
|
'enterprisesearch.health': 'enterprisesearch.stack_monitoring.health',
|
||||||
'enterprisesearch.stats': 'enterprisesearch.stack_monitoring.stats',
|
'enterprisesearch.stats': 'enterprisesearch.stack_monitoring.stats',
|
||||||
'kibana.cluster_actions': 'kibana.stack_monitoring.cluster_actions',
|
'kibana.cluster_actions': 'kibana.stack_monitoring.cluster_actions',
|
||||||
@@ -38,8 +40,7 @@
|
|||||||
'kibana.node_rules': 'kibana.stack_monitoring.node_rules',
|
'kibana.node_rules': 'kibana.stack_monitoring.node_rules',
|
||||||
'kibana.stats': 'kibana.stack_monitoring.stats',
|
'kibana.stats': 'kibana.stack_monitoring.stats',
|
||||||
'kibana.status': 'kibana.stack_monitoring.status',
|
'kibana.status': 'kibana.stack_monitoring.status',
|
||||||
'logstash.node': 'logstash.stack_monitoring.node',
|
'logstash.node_cel': 'logstash.stack_monitoring.node',
|
||||||
'logstash.node_cel': 'logstash.node',
|
|
||||||
'logstash.node_stats': 'logstash.stack_monitoring.node_stats',
|
'logstash.node_stats': 'logstash.stack_monitoring.node_stats',
|
||||||
'synthetics.browser': 'synthetics-browser',
|
'synthetics.browser': 'synthetics-browser',
|
||||||
'synthetics.browser_network': 'synthetics-browser.network',
|
'synthetics.browser_network': 'synthetics-browser.network',
|
||||||
|
|||||||
@@ -30,56 +30,6 @@ fleet_api() {
|
|||||||
curl -sK /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/${QUERYPATH}" "$@" --retry 3 --retry-delay 10 --fail 2>/dev/null
|
curl -sK /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/${QUERYPATH}" "$@" --retry 3 --retry-delay 10 --fail 2>/dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
elastic_fleet_require_agent_policy() {
|
|
||||||
local AGENT_POLICY=$1
|
|
||||||
local POLICY_JSON
|
|
||||||
|
|
||||||
if ! POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY") || [ -z "$POLICY_JSON" ]; then
|
|
||||||
echo "Error: Agent policy '$AGENT_POLICY' was not found or is not visible in the current Kibana space." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! jq -e '.item.package_policies | type == "array"' <<<"$POLICY_JSON" >/dev/null 2>&1; then
|
|
||||||
echo "Error: Agent policy '$AGENT_POLICY' was not found or is not visible in the current Kibana space." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$POLICY_JSON"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Print the single active enrollment token for POLICY_ID.
|
|
||||||
# Exit 1: retryable (API failure, invalid response, no active token)
|
|
||||||
# Exit 2: multiple active tokens - Shouldn't get into this state without manual intervention
|
|
||||||
elastic_fleet_active_enrollment_token() {
|
|
||||||
local POLICY_ID=$1
|
|
||||||
local RESP TOKEN_COUNT API_KEY
|
|
||||||
|
|
||||||
if ! RESP=$(fleet_api "enrollment_api_keys?perPage=100" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
|
|
||||||
echo "Error: Failed to retrieve enrollment tokens for agent policy '$POLICY_ID'." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! jq -e '.list' <<<"$RESP" >/dev/null 2>&1; then
|
|
||||||
echo "Error: Invalid enrollment token response for agent policy '$POLICY_ID'." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
TOKEN_COUNT=$(jq --arg pid "$POLICY_ID" '[.list[] | select(.policy_id == $pid and .active == true)] | length' <<<"$RESP")
|
|
||||||
|
|
||||||
if [ "${TOKEN_COUNT:-0}" -eq 0 ]; then
|
|
||||||
echo "Error: No active enrollment token found for agent policy '$POLICY_ID'." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$TOKEN_COUNT" -gt 1 ]; then
|
|
||||||
echo "Error: Found $TOKEN_COUNT active enrollment tokens for agent policy '$POLICY_ID'; expected exactly one." >&2
|
|
||||||
return 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
API_KEY=$(jq -r --arg pid "$POLICY_ID" '.list[] | select(.policy_id == $pid and .active == true) | .api_key' <<<"$RESP")
|
|
||||||
echo "$API_KEY"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Max number of concurrent Fleet write jobs (create/update). Override via env if needed.
|
# Max number of concurrent Fleet write jobs (create/update). Override via env if needed.
|
||||||
MAX_FLEET_JOBS=${MAX_FLEET_JOBS:-10}
|
MAX_FLEET_JOBS=${MAX_FLEET_JOBS:-10}
|
||||||
|
|
||||||
@@ -112,7 +62,15 @@ elastic_fleet_load_integrations_dir() {
|
|||||||
i=0
|
i=0
|
||||||
|
|
||||||
# Fetch the agent policy a single time; we look up integration ids locally below.
|
# Fetch the agent policy a single time; we look up integration ids locally below.
|
||||||
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$AGENT_POLICY"); then
|
if ! POLICY_JSON=$(fleet_api "agent_policies/$AGENT_POLICY"); then
|
||||||
|
echo "Error: Failed to retrieve agent policy '$AGENT_POLICY'."
|
||||||
|
rm -f "$FAIL_FILE"
|
||||||
|
rm -rf "$OUT_DIR"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! jq -e '.item.package_policies' <<<"$POLICY_JSON" >/dev/null 2>&1; then
|
||||||
|
echo "Error: Invalid agent policy response for '$AGENT_POLICY'."
|
||||||
rm -f "$FAIL_FILE"
|
rm -f "$FAIL_FILE"
|
||||||
rm -rf "$OUT_DIR"
|
rm -rf "$OUT_DIR"
|
||||||
return 1
|
return 1
|
||||||
@@ -166,15 +124,9 @@ elastic_fleet_integration_check() {
|
|||||||
|
|
||||||
JSON_STRING=$2
|
JSON_STRING=$2
|
||||||
|
|
||||||
NAME=$(jq -r .name "$JSON_STRING")
|
NAME=$(jq -r .name $JSON_STRING)
|
||||||
INTEGRATION_ID=""
|
|
||||||
|
|
||||||
local POLICY_JSON
|
INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$AGENT_POLICY" | jq -r '.item.package_policies[] | select(.name=="'"$NAME"'") | .id')
|
||||||
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$AGENT_POLICY"); then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
INTEGRATION_ID=$(jq -r --arg n "$NAME" '.item.package_policies[]? | select(.name==$n) | .id' <<<"$POLICY_JSON")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,16 +148,7 @@ elastic_fleet_integration_remove() {
|
|||||||
|
|
||||||
NAME=$2
|
NAME=$2
|
||||||
|
|
||||||
local POLICY_JSON
|
INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$AGENT_POLICY" | jq -r '.item.package_policies[] | select(.name=="'"$NAME"'") | .id')
|
||||||
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$AGENT_POLICY"); then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
INTEGRATION_ID=$(jq -r --arg n "$NAME" '.item.package_policies[]? | select(.name==$n) | .id' <<<"$POLICY_JSON")
|
|
||||||
if [ -z "$INTEGRATION_ID" ]; then
|
|
||||||
echo "Error: Integration '$NAME' was not found in agent policy '$AGENT_POLICY'." >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
JSON_STRING=$( jq -n \
|
JSON_STRING=$( jq -n \
|
||||||
--arg INTEGRATIONID "$INTEGRATION_ID" \
|
--arg INTEGRATIONID "$INTEGRATION_ID" \
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ ERROR=false
|
|||||||
for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/elastic-defend/*.json
|
for INTEGRATION in /opt/so/conf/elastic-fleet/integrations/elastic-defend/*.json
|
||||||
do
|
do
|
||||||
printf "\n\nInitial Endpoints Policy - Loading $INTEGRATION\n"
|
printf "\n\nInitial Endpoints Policy - Loading $INTEGRATION\n"
|
||||||
if ! elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"; then
|
elastic_fleet_integration_check "endpoints-initial" "$INTEGRATION"
|
||||||
ERROR=true
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
if [ -n "$INTEGRATION_ID" ]; then
|
if [ -n "$INTEGRATION_ID" ]; then
|
||||||
printf "\n\nIntegration $NAME exists - Upgrading integration policy\n"
|
printf "\n\nIntegration $NAME exists - Upgrading integration policy\n"
|
||||||
if ! elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"; then
|
if ! elastic_fleet_integration_policy_upgrade "$INTEGRATION_ID"; then
|
||||||
|
|||||||
+5
-20
@@ -7,35 +7,20 @@
|
|||||||
. /usr/sbin/so-elastic-fleet-common
|
. /usr/sbin/so-elastic-fleet-common
|
||||||
|
|
||||||
# Get all the fleet policies
|
# Get all the fleet policies
|
||||||
if ! json_output=$(fleet_api "agent_policies" -H 'kbn-xsrf: true'); then
|
json_output=$(curl -s -K /opt/so/conf/elasticsearch/curl.config -L -X GET "localhost:5601/api/fleet/agent_policies" -H 'kbn-xsrf: true')
|
||||||
echo "Error: Failed to retrieve Fleet agent policies." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! jq -e '.items' <<<"$json_output" >/dev/null 2>&1; then
|
|
||||||
echo "Error: Invalid Fleet agent policies response." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract the IDs that start with "FleetServer_"
|
# Extract the IDs that start with "FleetServer_"
|
||||||
POLICY=$(jq -r '.items[] | select(.id | startswith("FleetServer_")) | .id' <<<"$json_output")
|
POLICY=$(echo "$json_output" | jq -r '.items[] | select(.id | startswith("FleetServer_")) | .id')
|
||||||
|
|
||||||
# Iterate over each ID in the POLICY variable
|
# Iterate over each ID in the POLICY variable
|
||||||
for POLICYNAME in $POLICY; do
|
for POLICYNAME in $POLICY; do
|
||||||
printf "\nUpdating Policy: $POLICYNAME\n"
|
printf "\nUpdating Policy: $POLICYNAME\n"
|
||||||
|
|
||||||
if ! POLICY_JSON=$(elastic_fleet_require_agent_policy "$POLICYNAME"); then
|
# First get the Integration ID
|
||||||
exit 1
|
INTEGRATION_ID=$(/usr/sbin/so-elastic-fleet-agent-policy-view "$POLICYNAME" | jq -r '.item.package_policies[] | select(.package.name == "fleet_server") | .id')
|
||||||
fi
|
|
||||||
|
|
||||||
INTEGRATION_ID=$(jq -r '.item.package_policies[]? | select(.package.name == "fleet_server") | .id' <<<"$POLICY_JSON")
|
|
||||||
if [ -z "$INTEGRATION_ID" ]; then
|
|
||||||
echo "Error: fleet_server integration was not found in agent policy '$POLICYNAME'." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Modify the default integration policy to update the policy_id and an with the correct naming
|
# Modify the default integration policy to update the policy_id and an with the correct naming
|
||||||
UPDATED_INTEGRATION_POLICY=$(jq --arg policy_id "$POLICYNAME" --arg name "fleet_server-$POLICYNAME" '
|
UPDATED_INTEGRATION_POLICY=$(jq --arg policy_id "$POLICYNAME" --arg name "fleet_server-$POLICYNAME" '
|
||||||
.policy_id = $policy_id |
|
.policy_id = $policy_id |
|
||||||
.name = $name' /opt/so/conf/elastic-fleet/integrations/fleet-server/fleet-server.json)
|
.name = $name' /opt/so/conf/elastic-fleet/integrations/fleet-server/fleet-server.json)
|
||||||
|
|
||||||
|
|||||||
@@ -22,19 +22,12 @@ NUM_RUNNING=$(pgrep -cf "/bin/bash /sbin/so-elastic-agent-gen-installers")
|
|||||||
|
|
||||||
for i in {1..30}
|
for i in {1..30}
|
||||||
do
|
do
|
||||||
ENROLLMENTOKEN=$(elastic_fleet_active_enrollment_token "endpoints-initial")
|
ENROLLMENTOKEN=$(curl -K /opt/so/conf/elasticsearch/curl.config -L "localhost:5601/api/fleet/enrollment_api_keys?perPage=100" -H 'kbn-xsrf: true' -H 'Content-Type: application/json' | jq .list | jq -r -c '.[] | select(.policy_id | contains("endpoints-initial")) | .api_key')
|
||||||
TOKEN_RC=$?
|
|
||||||
if [ "$TOKEN_RC" -eq 2 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
FLEETHOST=$(curl -K /opt/so/conf/elasticsearch/curl.config 'http://localhost:5601/api/fleet/fleet_server_hosts/grid-default' | jq -r '.item.host_urls[]' | paste -sd ',')
|
FLEETHOST=$(curl -K /opt/so/conf/elasticsearch/curl.config 'http://localhost:5601/api/fleet/fleet_server_hosts/grid-default' | jq -r '.item.host_urls[]' | paste -sd ',')
|
||||||
if [[ -n "$FLEETHOST" ]] && [[ -n "$ENROLLMENTOKEN" ]]; then
|
if [[ $FLEETHOST ]] && [[ $ENROLLMENTOKEN ]]; then break; else sleep 10; fi
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 10
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -z "$FLEETHOST" ]] || [[ -z "$ENROLLMENTOKEN" ]]; then
|
if [[ -z $FLEETHOST ]] || [[ -z $ENROLLMENTOKEN ]]; then
|
||||||
printf "\nFleet Host URL, Enrollment Token or Elastic Version empty - exiting..."
|
printf "\nFleet Host URL, Enrollment Token or Elastic Version empty - exiting..."
|
||||||
printf "\nFleet Host: $FLEETHOST, Enrollment Token: $ENROLLMENTOKEN\n"
|
printf "\nFleet Host: $FLEETHOST, Enrollment Token: $ENROLLMENTOKEN\n"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -74,25 +67,19 @@ for GOOS in "${GOTARGETOS[@]}"; do
|
|||||||
GOARCH="amd64"
|
GOARCH="amd64"
|
||||||
if [[ $GOOS == 'darwin/arm64' ]]; then GOOS="darwin" && GOARCH="arm64"; fi
|
if [[ $GOOS == 'darwin/arm64' ]]; then GOOS="darwin" && GOARCH="arm64"; fi
|
||||||
printf "\n\n### Generating $GOOS/$GOARCH Installer...\n"
|
printf "\n\n### Generating $GOOS/$GOARCH Installer...\n"
|
||||||
if ! docker run -e CGO_ENABLED=0 -e GOOS=$GOOS -e GOARCH=$GOARCH \
|
docker run -e CGO_ENABLED=0 -e GOOS=$GOOS -e GOARCH=$GOARCH \
|
||||||
--mount type=bind,source=/etc/pki/tls/certs/,target=/workspace/files/cert/ \
|
--mount type=bind,source=/etc/pki/tls/certs/,target=/workspace/files/cert/ \
|
||||||
--mount type=bind,source=/nsm/elastic-agent-workspace/,target=/workspace/files/elastic-agent/ \
|
--mount type=bind,source=/nsm/elastic-agent-workspace/,target=/workspace/files/elastic-agent/ \
|
||||||
--mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ \
|
--mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ \
|
||||||
{{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} go build -ldflags "-X main.fleetHostURLsList=$FLEETHOST -X main.enrollmentToken=$ENROLLMENTOKEN" -o /output/so-elastic-agent_${GOOS}_${GOARCH}; then
|
{{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} go build -ldflags "-X main.fleetHostURLsList=$FLEETHOST -X main.enrollmentToken=$ENROLLMENTOKEN" -o /output/so-elastic-agent_${GOOS}_${GOARCH}
|
||||||
printf "\n### ERROR: Failed to generate $GOOS/$GOARCH installer. Exiting...\n"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
printf "\n### $GOOS/$GOARCH Installer Generated...\n"
|
printf "\n### $GOOS/$GOARCH Installer Generated...\n"
|
||||||
done
|
done
|
||||||
|
|
||||||
printf "\n\n### Generating MSI...\n"
|
printf "\n\n### Generating MSI...\n"
|
||||||
cp /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64 /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64.exe
|
cp /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64 /opt/so/saltstack/local/salt/elasticfleet/files/so-elastic-agent_windows_amd64.exe
|
||||||
if ! docker run \
|
docker run \
|
||||||
--mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ -w /output \
|
--mount type=bind,source=/opt/so/saltstack/local/salt/elasticfleet/files/,target=/output/ -w /output \
|
||||||
{{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} wixl -o so-elastic-agent_windows_amd64_msi --arch x64 /workspace/so-elastic-agent.wxs; then
|
{{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elastic-agent-builder:{{ GLOBALS.so_version }} wixl -o so-elastic-agent_windows_amd64_msi --arch x64 /workspace/so-elastic-agent.wxs
|
||||||
printf "\n### ERROR: Failed to generate MSI. Exiting...\n"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
printf "\n### MSI Generated...\n"
|
printf "\n### MSI Generated...\n"
|
||||||
|
|
||||||
# Verify installers were created
|
# Verify installers were created
|
||||||
|
|||||||
@@ -202,9 +202,26 @@ fi
|
|||||||
### Finalization ###
|
### Finalization ###
|
||||||
|
|
||||||
# Query for Enrollment Tokens for default policies
|
# Query for Enrollment Tokens for default policies
|
||||||
ENDPOINTSENROLLMENTOKEN=$(elastic_fleet_active_enrollment_token "endpoints-initial") || exit 1
|
if ENDPOINTSENROLLMENTOKEN_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
|
||||||
GRIDNODESENROLLMENTOKENGENERAL=$(elastic_fleet_active_enrollment_token "so-grid-nodes_general") || exit 1
|
ENDPOINTSENROLLMENTOKEN=$(echo "$ENDPOINTSENROLLMENTOKEN_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("endpoints-initial")) | .api_key')
|
||||||
GRIDNODESENROLLMENTOKENHEAVY=$(elastic_fleet_active_enrollment_token "so-grid-nodes_heavy") || exit 1
|
else
|
||||||
|
echo -e "\nFailed to query for Endpoints enrollment token"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if GRIDNODESENROLLMENTOKENGENERAL_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
|
||||||
|
GRIDNODESENROLLMENTOKENGENERAL=$(echo "$GRIDNODESENROLLMENTOKENGENERAL_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_general")) | .api_key')
|
||||||
|
else
|
||||||
|
echo -e "\nFailed to query for Grid nodes - General enrollment token"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if GRIDNODESENROLLMENTOKENHEAVY_RAW=$(fleet_api "enrollment_api_keys" -H 'kbn-xsrf: true' -H 'Content-Type: application/json'); then
|
||||||
|
GRIDNODESENROLLMENTOKENHEAVY=$(echo "$GRIDNODESENROLLMENTOKENHEAVY_RAW" | jq .list | jq -r -c '.[] | select(.policy_id | contains("so-grid-nodes_heavy")) | .api_key')
|
||||||
|
else
|
||||||
|
echo -e "\nFailed to query for Grid nodes - Heavy enrollment token"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Store needed data in minion pillar
|
# Store needed data in minion pillar
|
||||||
pillar_file=/opt/so/saltstack/local/pillar/minions/{{ GLOBALS.minion_id }}.sls
|
pillar_file=/opt/so/saltstack/local/pillar/minions/{{ GLOBALS.minion_id }}.sls
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# 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 %}
|
||||||
|
{% if sls.split('.')[0] in allowed_states or sls in allowed_states %}
|
||||||
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
|
|
||||||
|
# Move our new CA over so Elastic and Logstash can use SSL with the internal CA
|
||||||
|
catrustdir:
|
||||||
|
file.directory:
|
||||||
|
- name: /opt/so/conf/ca
|
||||||
|
- user: 939
|
||||||
|
- group: 939
|
||||||
|
- makedirs: True
|
||||||
|
|
||||||
|
{% if GLOBALS.is_manager %}
|
||||||
|
# We have to add the Manager CA to the CA list
|
||||||
|
catrustscript:
|
||||||
|
cmd.script:
|
||||||
|
- source: salt://elasticsearch/tools/sbin_jinja/so-catrust
|
||||||
|
- template: jinja
|
||||||
|
- cwd: /opt/so
|
||||||
|
- defaults:
|
||||||
|
GLOBALS: {{ GLOBALS }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
elasticsearch_cacerts:
|
||||||
|
file.managed:
|
||||||
|
- name: /opt/so/conf/ca/cacerts
|
||||||
|
- source: salt://elasticsearch/cacerts
|
||||||
|
- user: 939
|
||||||
|
- group: 939
|
||||||
|
|
||||||
|
elasticsearch_capems:
|
||||||
|
file.managed:
|
||||||
|
- name: /opt/so/conf/ca/tls-ca-bundle.pem
|
||||||
|
- source: salt://elasticsearch/tls-ca-bundle.pem
|
||||||
|
- user: 939
|
||||||
|
- group: 939
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{{sls}}_state_not_allowed:
|
||||||
|
test.fail_without_changes:
|
||||||
|
- name: {{sls}}_state_not_allowed
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
@@ -107,12 +107,6 @@ so-elasticsearch-system-indices-patch:
|
|||||||
- file: so-elasticsearch-system-indices-patch-script
|
- file: so-elasticsearch-system-indices-patch-script
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
so-elasticsearch-ilm-policy-reload:
|
|
||||||
file.absent:
|
|
||||||
- name: /opt/so/state/esilm.txt
|
|
||||||
- onchanges:
|
|
||||||
- file: so-elasticsearch-ilm-policy-load-script
|
|
||||||
|
|
||||||
# heavynodes will only load ILM policies for SO managed indices. (Indicies defined in elasticsearch/defaults.yaml)
|
# heavynodes will only load ILM policies for SO managed indices. (Indicies defined in elasticsearch/defaults.yaml)
|
||||||
so-elasticsearch-ilm-policy-load:
|
so-elasticsearch-ilm-policy-load:
|
||||||
cmd.run:
|
cmd.run:
|
||||||
@@ -121,9 +115,8 @@ so-elasticsearch-ilm-policy-load:
|
|||||||
- require:
|
- require:
|
||||||
- docker_container: so-elasticsearch
|
- docker_container: so-elasticsearch
|
||||||
- file: so-elasticsearch-ilm-policy-load-script
|
- file: so-elasticsearch-ilm-policy-load-script
|
||||||
- http: wait_for_so-elasticsearch
|
- onchanges:
|
||||||
- file: so-elasticsearch-ilm-policy-reload
|
- file: so-elasticsearch-ilm-policy-load-script
|
||||||
- unless: test -f /opt/so/state/esilm.txt
|
|
||||||
|
|
||||||
so-elasticsearch-templates-reload:
|
so-elasticsearch-templates-reload:
|
||||||
file.absent:
|
file.absent:
|
||||||
|
|||||||
@@ -32,14 +32,13 @@ elasticsearch:
|
|||||||
- gid: 930
|
- gid: 930
|
||||||
- home: /opt/so/conf/elasticsearch
|
- home: /opt/so/conf/elasticsearch
|
||||||
- createhome: False
|
- createhome: False
|
||||||
- shell: /sbin/nologin
|
|
||||||
|
|
||||||
elasticsearch_sbin:
|
elasticsearch_sbin:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://elasticsearch/tools/sbin
|
- source: salt://elasticsearch/tools/sbin
|
||||||
- user: root
|
- user: 930
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- exclude_pat:
|
- exclude_pat:
|
||||||
- so-elasticsearch-pipelines # exclude this because we need to watch it for changes, we sync it in another state
|
- so-elasticsearch-pipelines # exclude this because we need to watch it for changes, we sync it in another state
|
||||||
@@ -50,8 +49,8 @@ so-elasticsearch-system-indices-patch-script:
|
|||||||
file.managed:
|
file.managed:
|
||||||
- name: /usr/sbin/so-elasticsearch-system-indices-patch
|
- name: /usr/sbin/so-elasticsearch-system-indices-patch
|
||||||
- source: salt://elasticsearch/tools/sbin/so-elasticsearch-system-indices-patch
|
- source: salt://elasticsearch/tools/sbin/so-elasticsearch-system-indices-patch
|
||||||
- user: root
|
- user: 930
|
||||||
- group: root
|
- group: 939
|
||||||
- mode: 755
|
- mode: 755
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
|
|
||||||
@@ -59,8 +58,8 @@ elasticsearch_sbin_jinja:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://elasticsearch/tools/sbin_jinja
|
- source: salt://elasticsearch/tools/sbin_jinja
|
||||||
- user: root
|
- user: 939
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- exclude_pat:
|
- exclude_pat:
|
||||||
@@ -73,8 +72,8 @@ so-elasticsearch-ilm-policy-load-script:
|
|||||||
file.managed:
|
file.managed:
|
||||||
- name: /usr/sbin/so-elasticsearch-ilm-policy-load
|
- name: /usr/sbin/so-elasticsearch-ilm-policy-load
|
||||||
- source: salt://elasticsearch/tools/sbin_jinja/so-elasticsearch-ilm-policy-load
|
- source: salt://elasticsearch/tools/sbin_jinja/so-elasticsearch-ilm-policy-load
|
||||||
- user: root
|
- user: 930
|
||||||
- group: root
|
- group: 939
|
||||||
- mode: 754
|
- mode: 754
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- defaults:
|
- defaults:
|
||||||
@@ -85,8 +84,8 @@ so-elasticsearch-pipelines-script:
|
|||||||
file.managed:
|
file.managed:
|
||||||
- name: /usr/sbin/so-elasticsearch-pipelines
|
- name: /usr/sbin/so-elasticsearch-pipelines
|
||||||
- source: salt://elasticsearch/tools/sbin/so-elasticsearch-pipelines
|
- source: salt://elasticsearch/tools/sbin/so-elasticsearch-pipelines
|
||||||
- user: root
|
- user: 930
|
||||||
- group: root
|
- group: 939
|
||||||
- mode: 754
|
- mode: 754
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
elasticsearch:
|
elasticsearch:
|
||||||
enabled: false
|
enabled: false
|
||||||
esheap: '600m'
|
esheap: '600m'
|
||||||
version: 9.4.8
|
version: 9.4.5
|
||||||
index_clean: true
|
index_clean: true
|
||||||
data_retention_method: DLM
|
data_retention_method: DLM
|
||||||
vm:
|
vm:
|
||||||
@@ -1160,7 +1160,6 @@ elasticsearch:
|
|||||||
- so-fleet_agent_id_verification-1
|
- so-fleet_agent_id_verification-1
|
||||||
- so-logs-mappings
|
- so-logs-mappings
|
||||||
- so-logs-settings
|
- so-logs-settings
|
||||||
- detections-alerts-mappings
|
|
||||||
data_stream:
|
data_stream:
|
||||||
allow_custom_routing: false
|
allow_custom_routing: false
|
||||||
hidden: false
|
hidden: false
|
||||||
@@ -3310,7 +3309,6 @@ elasticsearch:
|
|||||||
composed_of:
|
composed_of:
|
||||||
- event-mappings
|
- event-mappings
|
||||||
- logs-system.security@package
|
- logs-system.security@package
|
||||||
- so-fleet_system.security_caseless-1
|
|
||||||
- logs-system.security@custom
|
- logs-system.security@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
@@ -4177,7 +4175,6 @@ elasticsearch:
|
|||||||
index_template:
|
index_template:
|
||||||
composed_of:
|
composed_of:
|
||||||
- logs-windows.forwarded@package
|
- logs-windows.forwarded@package
|
||||||
- so-fleet_process_caseless-1
|
|
||||||
- logs-windows.forwarded@custom
|
- logs-windows.forwarded@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
@@ -4227,7 +4224,6 @@ elasticsearch:
|
|||||||
index_template:
|
index_template:
|
||||||
composed_of:
|
composed_of:
|
||||||
- logs-windows.powershell@package
|
- logs-windows.powershell@package
|
||||||
- so-fleet_process_caseless-1
|
|
||||||
- logs-windows.powershell@custom
|
- logs-windows.powershell@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
@@ -4277,7 +4273,6 @@ elasticsearch:
|
|||||||
index_template:
|
index_template:
|
||||||
composed_of:
|
composed_of:
|
||||||
- logs-windows.powershell_operational@package
|
- logs-windows.powershell_operational@package
|
||||||
- so-fleet_process_caseless-1
|
|
||||||
- logs-windows.powershell_operational@custom
|
- logs-windows.powershell_operational@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
@@ -4327,7 +4322,6 @@ elasticsearch:
|
|||||||
index_template:
|
index_template:
|
||||||
composed_of:
|
composed_of:
|
||||||
- logs-windows.sysmon_operational@package
|
- logs-windows.sysmon_operational@package
|
||||||
- so-fleet_process_caseless-1
|
|
||||||
- logs-windows.sysmon_operational@custom
|
- logs-windows.sysmon_operational@custom
|
||||||
- so-fleet_integrations.ip_mappings-1
|
- so-fleet_integrations.ip_mappings-1
|
||||||
- so-fleet_globals-1
|
- so-fleet_globals-1
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_NODES %}
|
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_NODES %}
|
||||||
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_SEED_HOSTS %}
|
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCH_SEED_HOSTS %}
|
||||||
@@ -14,6 +13,7 @@
|
|||||||
|
|
||||||
include:
|
include:
|
||||||
- ca
|
- ca
|
||||||
|
- elasticsearch.ca
|
||||||
- elasticsearch.ssl
|
- elasticsearch.ssl
|
||||||
- elasticsearch.config
|
- elasticsearch.config
|
||||||
- elasticsearch.sostatus
|
- elasticsearch.sostatus
|
||||||
@@ -63,14 +63,15 @@ so-elasticsearch:
|
|||||||
- {{ BINDING }}
|
- {{ BINDING }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- /opt/so/conf/elasticsearch/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro
|
- /opt/so/conf/elasticsearch/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml:ro
|
||||||
- /opt/so/conf/elasticsearch/log4j2.properties:/usr/share/elasticsearch/config/log4j2.properties:ro
|
- /opt/so/conf/elasticsearch/log4j2.properties:/usr/share/elasticsearch/config/log4j2.properties:ro
|
||||||
- /nsm/elasticsearch:/usr/share/elasticsearch/data:rw
|
- /nsm/elasticsearch:/usr/share/elasticsearch/data:rw
|
||||||
- /opt/so/log/elasticsearch:/var/log/elasticsearch:rw
|
- /opt/so/log/elasticsearch:/var/log/elasticsearch:rw
|
||||||
|
- /opt/so/conf/ca/cacerts:/usr/share/elasticsearch/jdk/lib/security/cacerts:ro
|
||||||
- /etc/pki/tls/certs/intca.crt:/usr/share/elasticsearch/config/ca.crt:ro
|
- /etc/pki/tls/certs/intca.crt:/usr/share/elasticsearch/config/ca.crt:ro
|
||||||
- /etc/pki/elasticsearch.crt:/usr/share/elasticsearch/config/elasticsearch.crt:ro
|
- /etc/pki/elasticsearch.crt:/usr/share/elasticsearch/config/elasticsearch.crt:ro
|
||||||
- /etc/pki/elasticsearch.key:/usr/share/elasticsearch/config/elasticsearch.key:ro
|
- /etc/pki/elasticsearch.key:/usr/share/elasticsearch/config/elasticsearch.key:ro
|
||||||
|
- /etc/pki/elasticsearch.p12:/usr/share/elasticsearch/config/elasticsearch.p12:ro
|
||||||
- /opt/so/conf/elasticsearch/users_roles:/usr/share/elasticsearch/config/users_roles:ro
|
- /opt/so/conf/elasticsearch/users_roles:/usr/share/elasticsearch/config/users_roles:ro
|
||||||
- /opt/so/conf/elasticsearch/users:/usr/share/elasticsearch/config/users:ro
|
- /opt/so/conf/elasticsearch/users:/usr/share/elasticsearch/config/users:ro
|
||||||
{% if ELASTICSEARCHMERGED.config.path.get('repo', False) %}
|
{% if ELASTICSEARCHMERGED.config.path.get('repo', False) %}
|
||||||
@@ -84,19 +85,21 @@ so-elasticsearch:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: trusttheca
|
- file: trusttheca
|
||||||
- x509: elasticsearch_crt
|
- x509: elasticsearch_crt
|
||||||
- x509: elasticsearch_key
|
- x509: elasticsearch_key
|
||||||
|
- file: elasticsearch_cacerts
|
||||||
- file: esyml
|
- file: esyml
|
||||||
- require:
|
- require:
|
||||||
- file: trusttheca
|
- file: trusttheca
|
||||||
- x509: elasticsearch_crt
|
- x509: elasticsearch_crt
|
||||||
- x509: elasticsearch_key
|
- x509: elasticsearch_key
|
||||||
|
- file: elasticsearch_cacerts
|
||||||
- file: esyml
|
- file: esyml
|
||||||
- file: eslog4jfile
|
- file: eslog4jfile
|
||||||
- file: nsmesdir
|
- file: nsmesdir
|
||||||
- file: eslogdir
|
- file: eslogdir
|
||||||
|
- file: elasticp12perms
|
||||||
- cmd: auth_users_roles_inode
|
- cmd: auth_users_roles_inode
|
||||||
- cmd: auth_users_inode
|
- cmd: auth_users_inode
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": 4,
|
"version": 3,
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"managed_by": "securityonion",
|
"managed_by": "securityonion",
|
||||||
"managed": true
|
"managed": true
|
||||||
@@ -99,7 +99,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"if": "ctx.tags != null && ctx.tags.contains('import') && ctx._index != null && ctx._index.startsWith('logs-import-')",
|
"if": "ctx.tags != null && ctx.tags.contains('import')",
|
||||||
"override": true,
|
"override": true,
|
||||||
"field": "data_stream.dataset",
|
"field": "data_stream.dataset",
|
||||||
"value": "import"
|
"value": "import"
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"set": {
|
"set": {
|
||||||
"if": "ctx.tags != null && ctx.tags.contains('import') && ctx._index != null && ctx._index.startsWith('logs-import-')",
|
"if": "ctx.tags != null && ctx.tags.contains('import')",
|
||||||
"override": true,
|
"override": true,
|
||||||
"field": "data_stream.namespace",
|
"field": "data_stream.namespace",
|
||||||
"value": "so"
|
"value": "so"
|
||||||
@@ -177,32 +177,6 @@
|
|||||||
"description": "Extract IPs from Elastic Agent events (host.ip) and adds them to related.ip"
|
"description": "Extract IPs from Elastic Agent events (host.ip) and adds them to related.ip"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"trim": {
|
|
||||||
"if": "ctx.event?.dataset == 'system.auth'",
|
|
||||||
"description": "Grok leaves a leading space on 'invalid user' names (elastic/integrations#12174) and, before 2.23.2, sudo padding",
|
|
||||||
"field": "user.name",
|
|
||||||
"ignore_missing": true,
|
|
||||||
"ignore_failure": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"trim": {
|
|
||||||
"if": "ctx.event?.dataset == 'system.auth'",
|
|
||||||
"description": "Appended from the untrimmed user.name",
|
|
||||||
"field": "related.user",
|
|
||||||
"ignore_missing": true,
|
|
||||||
"ignore_failure": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"script": {
|
|
||||||
"if": "ctx.event?.dataset == 'system.auth' && ctx.related?.user instanceof List",
|
|
||||||
"description": "Dedupe after trimming",
|
|
||||||
"source": "ctx.related.user = new ArrayList(new LinkedHashSet(ctx.related.user));",
|
|
||||||
"ignore_failure": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"script": {
|
"script": {
|
||||||
"description": "Snapshot event.ingested into _tmp.event_ingested_pre_fleet before .fleet_final_pipeline-1 overwrites it with ES ingest time",
|
"description": "Snapshot event.ingested into _tmp.event_ingested_pre_fleet before .fleet_final_pipeline-1 overwrites it with ES ingest time",
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"description" : "import.evtx: normalize imported EVTX and reroute to logs-<dataset>-import",
|
|
||||||
"processors" : [
|
|
||||||
{ "script": {
|
|
||||||
"description": "Host from the event, not the importing node",
|
|
||||||
"lang": "painless",
|
|
||||||
"source": "Map host = ['os': ['type': 'windows', 'family': 'windows', 'platform': 'windows']]; def cn = ctx.winlog?.computer_name; if (cn != null && cn.toString().length() > 0) { String name = cn.toString(); int dot = name.indexOf('.'); if (dot > 0) { name = name.substring(0, dot); } host.put('hostname', name); host.put('name', name.toLowerCase()); } ctx.host = host;"
|
|
||||||
} },
|
|
||||||
{ "script": {
|
|
||||||
"description": "String event IDs, as Winlogbeat sends",
|
|
||||||
"lang": "painless",
|
|
||||||
"source": "if (ctx.winlog?.event_id != null) { ctx.winlog.event_id = ctx.winlog.event_id.toString(); } if (ctx.event?.code != null) { ctx.event.code = ctx.event.code.toString(); }"
|
|
||||||
} },
|
|
||||||
{ "script": {
|
|
||||||
"description": "Unnamed <Data> to param1..N, as Winlogbeat",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx.winlog?.event_data?.Data instanceof Map && ctx.winlog.event_data.Data['#text'] != null",
|
|
||||||
"source": "def t = ctx.winlog.event_data.Data['#text']; List vals = t instanceof List ? t : [t]; for (int i = 0; i < vals.size(); i++) { ctx.winlog.event_data['param' + (i + 1)] = vals.get(i); } ctx.winlog.event_data.remove('Data');"
|
|
||||||
} },
|
|
||||||
{ "script": {
|
|
||||||
"description": "String values and LF line endings, as Winlogbeat",
|
|
||||||
"lang": "painless",
|
|
||||||
"if": "ctx.winlog?.event_data instanceof Map || ctx.winlog?.user_data instanceof Map",
|
|
||||||
"source": "String lf = String.valueOf((char) 10); String crlf = String.valueOf((char) 13) + lf; for (def key : ['event_data', 'user_data']) { def m = ctx.winlog[key]; if (!(m instanceof Map)) { continue; } for (def e : m.entrySet()) { def v = e.getValue(); if (v instanceof String) { e.setValue(v.replace(crlf, lf)); } else if (v instanceof Number || v instanceof Boolean) { e.setValue(v.toString()); } } }"
|
|
||||||
} },
|
|
||||||
{ "set": { "description": "event.kind, as Winlogbeat", "field": "event.kind", "value": "event", "override": false } },
|
|
||||||
{ "set": { "field": "data_stream.dataset", "copy_from": "event.dataset", "override": true, "ignore_empty_value": true } },
|
|
||||||
{ "set": { "field": "data_stream.namespace", "value": "import", "override": true } },
|
|
||||||
{ "reroute": { "dataset": "{{data_stream.dataset}}", "namespace": "{{data_stream.namespace}}" } }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,7 @@
|
|||||||
{ "rename": { "field": "message2.proto", "target_field": "network.transport", "ignore_missing": true } },
|
{ "rename": { "field": "message2.proto", "target_field": "network.transport", "ignore_missing": true } },
|
||||||
{ "rename": { "field": "message2.app_proto", "target_field": "network.protocol", "ignore_missing": true } },
|
{ "rename": { "field": "message2.app_proto", "target_field": "network.protocol", "ignore_missing": true } },
|
||||||
{ "rename": { "field": "message2.fileinfo.filename", "target_field": "file.name", "ignore_missing": true } },
|
{ "rename": { "field": "message2.fileinfo.filename", "target_field": "file.name", "ignore_missing": true } },
|
||||||
{ "rename": { "field": "message2.fileinfo.gaps", "target_field": "suricata.fileinfo.gaps", "ignore_missing": true } },
|
{ "rename": { "field": "message2.fileinfo.gaps", "target_field": "file.bytes.missing", "ignore_missing": true } },
|
||||||
{ "set": { "if": "ctx.suricata?.fileinfo?.gaps == false", "field": "file.bytes.missing", "value": 0 } },
|
|
||||||
{ "rename": { "field": "message2.fileinfo.magic", "target_field": "file.mime_type", "ignore_missing": true } },
|
{ "rename": { "field": "message2.fileinfo.magic", "target_field": "file.mime_type", "ignore_missing": true } },
|
||||||
{ "rename": { "field": "message2.fileinfo.md5", "target_field": "hash.md5", "ignore_missing": true } },
|
{ "rename": { "field": "message2.fileinfo.md5", "target_field": "hash.md5", "ignore_missing": true } },
|
||||||
{ "rename": { "field": "message2.fileinfo.sha1", "target_field": "hash.sha1", "ignore_missing": true } },
|
{ "rename": { "field": "message2.fileinfo.sha1", "target_field": "hash.sha1", "ignore_missing": true } },
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ elasticsearch_crt:
|
|||||||
- retry:
|
- retry:
|
||||||
attempts: 5
|
attempts: 5
|
||||||
interval: 30
|
interval: 30
|
||||||
|
cmd.run:
|
||||||
|
- name: "/usr/bin/openssl pkcs12 -inkey /etc/pki/elasticsearch.key -in /etc/pki/elasticsearch.crt -export -out /etc/pki/elasticsearch.p12 -nodes -passout pass:"
|
||||||
|
- onchanges:
|
||||||
|
- x509: /etc/pki/elasticsearch.key
|
||||||
|
|
||||||
elastickeyperms:
|
elastickeyperms:
|
||||||
file.managed:
|
file.managed:
|
||||||
@@ -46,6 +50,13 @@ elastickeyperms:
|
|||||||
- mode: 640
|
- mode: 640
|
||||||
- group: 930
|
- group: 930
|
||||||
|
|
||||||
|
elasticp12perms:
|
||||||
|
file.managed:
|
||||||
|
- replace: False
|
||||||
|
- name: /etc/pki/elasticsearch.p12
|
||||||
|
- mode: 640
|
||||||
|
- group: 930
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
{{sls}}_state_not_allowed:
|
{{sls}}_state_not_allowed:
|
||||||
|
|||||||
-123
@@ -1,123 +0,0 @@
|
|||||||
{
|
|
||||||
"_meta": {
|
|
||||||
"managed_by": "security_onion",
|
|
||||||
"managed": true,
|
|
||||||
"description": "Adds .caseless for Lucene queries. Restates each field's package type and .text."
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"mappings": {
|
|
||||||
"properties": {
|
|
||||||
"process": {
|
|
||||||
"properties": {
|
|
||||||
"executable": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"type": "match_only_text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"type": "match_only_text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"command_line": {
|
|
||||||
"type": "wildcard",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"type": "match_only_text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parent": {
|
|
||||||
"properties": {
|
|
||||||
"executable": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"type": "match_only_text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"type": "match_only_text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"command_line": {
|
|
||||||
"type": "wildcard",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"type": "match_only_text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"file": {
|
|
||||||
"properties": {
|
|
||||||
"path": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
},
|
|
||||||
"text": {
|
|
||||||
"type": "match_only_text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-80
@@ -1,80 +0,0 @@
|
|||||||
{
|
|
||||||
"_meta": {
|
|
||||||
"managed_by": "security_onion",
|
|
||||||
"managed": true,
|
|
||||||
"description": "Adds .caseless for Lucene queries. Keeps each field's existing keyword type."
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"mappings": {
|
|
||||||
"properties": {
|
|
||||||
"process": {
|
|
||||||
"properties": {
|
|
||||||
"command_line": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parent": {
|
|
||||||
"properties": {
|
|
||||||
"executable": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"command_line": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"file": {
|
|
||||||
"properties": {
|
|
||||||
"path": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"fields": {
|
|
||||||
"caseless": {
|
|
||||||
"type": "keyword",
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"normalizer": "lowercase"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -154,20 +154,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"runtime": {
|
|
||||||
"so_detection.sigmaKind": {
|
|
||||||
"type": "keyword",
|
|
||||||
"script": {
|
|
||||||
"source": "if (doc['so_detection.language'].size() == 0 || doc['so_detection.language'].value != 'sigma') { return; } def d = params._source.so_detection; if (d == null || d.content == null) { return; } String c = d.content; String NL = String.valueOf((char) 10); emit(c.startsWith('correlation:') || c.contains(NL + 'correlation:') ? 'correlation' : 'single');"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"so_detection.sigmaCorrelationType": {
|
|
||||||
"type": "keyword",
|
|
||||||
"script": {
|
|
||||||
"source": "if (doc['so_detection.language'].size() == 0 || doc['so_detection.language'].value != 'sigma') { return; } def d = params._source.so_detection; if (d == null || d.content == null) { return; } String c = d.content; String NL = String.valueOf((char) 10); int i = c.startsWith('correlation:') ? 0 : c.indexOf(NL + 'correlation:'); if (i < 0) { return; } i = c.indexOf(NL, i + 1); while (i >= 0 && i + 1 < c.length()) { int e = c.indexOf(NL, i + 1); String line = e < 0 ? c.substring(i + 1) : c.substring(i + 1, e); if (line.length() > 0 && !Character.isWhitespace(line.charAt(0)) && line.charAt(0) != (char) 35) { return; } String t = line.trim(); if (t.startsWith('type:')) { String v = t.substring(5).trim(); int h = v.indexOf(' ' + String.valueOf((char) 35)); if (h >= 0) { v = v.substring(0, h).trim(); } if (v.length() > 1 && (v.charAt(0) == (char) 39 || v.charAt(0) == (char) 34)) { v = v.substring(1, v.length() - 1); } emit(v); return; } i = e; }"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
{
|
|
||||||
"template": {
|
|
||||||
"mappings": {
|
|
||||||
"properties": {
|
|
||||||
"tags": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"sigma_level": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"rule": {
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"uuid": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"product": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"service": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"correlation": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"event": {
|
|
||||||
"properties": {
|
|
||||||
"severity": {
|
|
||||||
"type": "long"
|
|
||||||
},
|
|
||||||
"severity_label": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"module": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"dataset": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"kind": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"reason": {
|
|
||||||
"type": "match_only_text",
|
|
||||||
"fields": {
|
|
||||||
"keyword": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"type": "keyword",
|
|
||||||
"index": false,
|
|
||||||
"doc_values": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"event_data": {
|
|
||||||
"properties": {
|
|
||||||
"@timestamp": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"window_start": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"event_count": {
|
|
||||||
"type": "long"
|
|
||||||
},
|
|
||||||
"value_count": {
|
|
||||||
"type": "long"
|
|
||||||
},
|
|
||||||
"event_type_count": {
|
|
||||||
"type": "long"
|
|
||||||
},
|
|
||||||
"value_sum": {
|
|
||||||
"type": "double"
|
|
||||||
},
|
|
||||||
"value_avg": {
|
|
||||||
"type": "double"
|
|
||||||
},
|
|
||||||
"value_percentile": {
|
|
||||||
"type": "double"
|
|
||||||
},
|
|
||||||
"value_median": {
|
|
||||||
"type": "double"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"labels": {
|
|
||||||
"properties": {
|
|
||||||
"correlation_group_by": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"correlation_group": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"related": {
|
|
||||||
"properties": {
|
|
||||||
"ip": {
|
|
||||||
"type": "ip"
|
|
||||||
},
|
|
||||||
"user": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"hosts": {
|
|
||||||
"ignore_above": 1024,
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"error": {
|
|
||||||
"properties": {
|
|
||||||
"message": {
|
|
||||||
"type": "match_only_text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"_meta": {
|
|
||||||
"description": "Fields written by the ElastAlert SecurityOnionESAlerter to logs-detections.alerts-so"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#!/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
|
||||||
|
get_elastic_agent_vars
|
||||||
|
|
||||||
|
# Exit on errors, since all lines must succeed
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Check to see if we have extracted the ca cert.
|
||||||
|
if [ ! -f /opt/so/saltstack/local/salt/elasticsearch/cacerts ]; then
|
||||||
|
docker run -v /etc/pki/ca.crt:/etc/ssl/ca.crt --name so-elasticsearchca --user root --entrypoint jdk/bin/keytool {{ GLOBALS.registry_host }}:5000/{{ GLOBALS.image_repo }}/so-elasticsearch:$ELASTIC_AGENT_TARBALL_VERSION -keystore /usr/share/elasticsearch/jdk/lib/security/cacerts -alias SOSCA -import -file /etc/ssl/ca.crt -storepass changeit -noprompt
|
||||||
|
# Make sure symbolic links are followed when copying from container
|
||||||
|
docker cp -L so-elasticsearchca:/usr/share/elasticsearch/jdk/lib/security/cacerts /opt/so/saltstack/local/salt/elasticsearch/cacerts
|
||||||
|
docker cp -L so-elasticsearchca:/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem /opt/so/saltstack/local/salt/elasticsearch/tls-ca-bundle.pem
|
||||||
|
docker rm so-elasticsearchca
|
||||||
|
echo "" >> /opt/so/saltstack/local/salt/elasticsearch/tls-ca-bundle.pem
|
||||||
|
echo "sosca" >> /opt/so/saltstack/local/salt/elasticsearch/tls-ca-bundle.pem
|
||||||
|
cat /etc/pki/ca.crt >> /opt/so/saltstack/local/salt/elasticsearch/tls-ca-bundle.pem
|
||||||
|
else
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
@@ -7,17 +7,11 @@
|
|||||||
. /usr/sbin/so-common
|
. /usr/sbin/so-common
|
||||||
|
|
||||||
MAX_JOBS=${MAX_ILM_JOBS:-10}
|
MAX_JOBS=${MAX_ILM_JOBS:-10}
|
||||||
ILM_STATEFILE=/opt/so/state/esilm.txt
|
|
||||||
rm -f "$ILM_STATEFILE"
|
|
||||||
|
|
||||||
check_elasticsearch_responsive
|
|
||||||
|
|
||||||
ILM_TMP_DIR=$(mktemp -d)
|
|
||||||
trap 'rm -rf "$ILM_TMP_DIR"' EXIT
|
|
||||||
# Lock used to serialize block writes so concurrent jobs never interleave their output.
|
# Lock used to serialize block writes so concurrent jobs never interleave their output.
|
||||||
ILM_OUTPUT_LOCK="$ILM_TMP_DIR/output.lock"
|
ILM_OUTPUT_LOCK=$(mktemp)
|
||||||
ILM_FAIL_FILE="$ILM_TMP_DIR/failed"
|
ILM_FAIL_FILE=$(mktemp)
|
||||||
touch "$ILM_FAIL_FILE"
|
trap 'rm -f "$ILM_OUTPUT_LOCK" "$ILM_FAIL_FILE"' EXIT
|
||||||
|
|
||||||
# Policies are loaded concurrently (up to MAX_JOBS at a time) for speed. Each policy's block is
|
# Policies are loaded concurrently (up to MAX_JOBS at a time) for speed. Each policy's block is
|
||||||
# printed the moment its curl returns, so output appears in COMPLETION ORDER, not the order
|
# printed the moment its curl returns, so output appears in COMPLETION ORDER, not the order
|
||||||
@@ -26,30 +20,13 @@ echo "Loading ILM policies concurrently; output below appears in completion orde
|
|||||||
echo
|
echo
|
||||||
|
|
||||||
put_policy() {
|
put_policy() {
|
||||||
local desc="$1" policyname="$2" data="$3" rc=1 attempt out http_code body result reason tmpdata
|
local desc="$1" policyname="$2" data="$3" result rc=0
|
||||||
tmpdata=$(mktemp "$ILM_TMP_DIR/${policyname}.XXXXXX.json")
|
if ! result=$(curl -K /opt/so/conf/elasticsearch/curl.config -s -k -L --fail \
|
||||||
printf '%s' "$data" >"$tmpdata"
|
-X PUT "https://localhost:9200/_ilm/policy/${policyname}" \
|
||||||
|
-H 'Content-Type: application/json' -d"${data}" 2>&1); then
|
||||||
for attempt in 1 2 3; do
|
rc=1
|
||||||
out=$(so-elasticsearch-query "_ilm/policy/${policyname}" -d@"${tmpdata}" -XPUT -w '\n%{http_code}' 2>/dev/null)
|
elif ! jq -e '.acknowledged == true' <<<"$result" >/dev/null 2>&1; then
|
||||||
http_code=${out##*$'\n'} body=${out%$'\n'*}
|
rc=1
|
||||||
|
|
||||||
if [[ "$http_code" == 200 ]] && jq -e '.acknowledged == true' <<<"$body" >/dev/null 2>&1; then
|
|
||||||
rc=0
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
# 429 = ES rejected/throttled (may succeed later); other 4xx will not
|
|
||||||
[[ "$http_code" == 4?? && "$http_code" != 429 ]] && break
|
|
||||||
(( attempt < 3 )) && sleep 3
|
|
||||||
done
|
|
||||||
|
|
||||||
if (( rc == 0 )); then
|
|
||||||
result='{"acknowledged":true}'
|
|
||||||
else
|
|
||||||
result=${body:-"HTTP ${http_code:-000}"}
|
|
||||||
# flatten tabs/newlines so the TAB fail-file stays one record
|
|
||||||
reason=$(jq -r '(if (.error|type) == "object" then (.error.reason // .error.type) elif (.error|type) == "string" then .error else empty end) // empty | tostring | gsub("[\t\r\n]+"; " ")' <<<"$body" 2>/dev/null)
|
|
||||||
reason="HTTP ${http_code:-000}${reason:+: ${reason}}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# curl above ran in parallel; serialize just this block write so concurrent jobs never interleave.
|
# curl above ran in parallel; serialize just this block write so concurrent jobs never interleave.
|
||||||
@@ -57,7 +34,7 @@ put_policy() {
|
|||||||
flock 200
|
flock 200
|
||||||
printf 'Setting up %s policy...\n%s\n\n' "${desc}" "${result}"
|
printf 'Setting up %s policy...\n%s\n\n' "${desc}" "${result}"
|
||||||
if (( rc != 0 )); then
|
if (( rc != 0 )); then
|
||||||
printf '%s\t%s\n' "${policyname}" "${reason}" >>"$ILM_FAIL_FILE"
|
printf '%s\n' "${policyname}" >>"$ILM_FAIL_FILE"
|
||||||
fi
|
fi
|
||||||
} 200>>"${ILM_OUTPUT_LOCK}"
|
} 200>>"${ILM_OUTPUT_LOCK}"
|
||||||
|
|
||||||
@@ -105,15 +82,10 @@ wait || true
|
|||||||
|
|
||||||
if [[ -s "$ILM_FAIL_FILE" ]]; then
|
if [[ -s "$ILM_FAIL_FILE" ]]; then
|
||||||
echo "ERROR: Failed to load ILM policy(s):"
|
echo "ERROR: Failed to load ILM policy(s):"
|
||||||
while IFS=$'\t' read -r POLICY REASON; do
|
while read -r POLICY; do
|
||||||
if [[ -n "$REASON" ]]; then
|
echo " - $POLICY"
|
||||||
echo " - ${POLICY}: ${REASON}"
|
|
||||||
else
|
|
||||||
echo " - ${POLICY}"
|
|
||||||
fi
|
|
||||||
done < "$ILM_FAIL_FILE"
|
done < "$ILM_FAIL_FILE"
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
echo "Successfully loaded all ILM policies."
|
echo "Successfully loaded all ILM policies."
|
||||||
touch "$ILM_STATEFILE"
|
|
||||||
fi
|
fi
|
||||||
@@ -4,19 +4,11 @@
|
|||||||
{%- set role = GLOBALS.role.split('-')[1] %}
|
{%- set role = GLOBALS.role.split('-')[1] %}
|
||||||
{%- from 'firewall/containers.map.jinja' import NODE_CONTAINERS %}
|
{%- from 'firewall/containers.map.jinja' import NODE_CONTAINERS %}
|
||||||
|
|
||||||
{%- set NODE_NETWORKS = [] %}
|
|
||||||
{%- for NETNAME, NETWORK in DOCKERMERGED.networks.items() %}
|
|
||||||
{%- if not NETWORK.get('manager_only') or GLOBALS.get('is_manager', False) %}
|
|
||||||
{%- do NODE_NETWORKS.append(NETNAME) %}
|
|
||||||
{%- endif %}
|
|
||||||
{%- endfor %}
|
|
||||||
|
|
||||||
{%- set PR = [] %}
|
{%- set PR = [] %}
|
||||||
{%- set D1 = [] %}
|
{%- set D1 = [] %}
|
||||||
{%- set D2 = [] %}
|
{%- set D2 = [] %}
|
||||||
{%- for container in NODE_CONTAINERS %}
|
{%- for container in NODE_CONTAINERS %}
|
||||||
{%- set IP = DOCKERMERGED.containers[container].ip %}
|
{%- set IP = DOCKERMERGED.containers[container].ip %}
|
||||||
{%- set BRIDGE = DOCKERMERGED.containers[container].network %}
|
|
||||||
{%- if DOCKERMERGED.containers[container].port_bindings is defined %}
|
{%- if DOCKERMERGED.containers[container].port_bindings is defined %}
|
||||||
{%- for binding in DOCKERMERGED.containers[container].port_bindings %}
|
{%- for binding in DOCKERMERGED.containers[container].port_bindings %}
|
||||||
{#- cant split int so we convert to string #}
|
{#- cant split int so we convert to string #}
|
||||||
@@ -43,11 +35,11 @@
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- do PR.append("-A POSTROUTING -s " ~ DOCKERMERGED.containers[container].ip ~ "/32 -d " ~ DOCKERMERGED.containers[container].ip ~ "/32 -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j MASQUERADE") %}
|
{%- do PR.append("-A POSTROUTING -s " ~ DOCKERMERGED.containers[container].ip ~ "/32 -d " ~ DOCKERMERGED.containers[container].ip ~ "/32 -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j MASQUERADE") %}
|
||||||
{%- if bindip | length and bindip != '0.0.0.0' %}
|
{%- if bindip | length and bindip != '0.0.0.0' %}
|
||||||
{%- do D1.append("-A DOCKER -d " ~ bindip ~ "/32 ! -i " ~ BRIDGE ~ " -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %}
|
{%- do D1.append("-A DOCKER -d " ~ bindip ~ "/32 ! -i sobridge -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %}
|
||||||
{%- else %}
|
{%- else %}
|
||||||
{%- do D1.append("-A DOCKER ! -i " ~ BRIDGE ~ " -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %}
|
{%- do D1.append("-A DOCKER ! -i sobridge -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ hostPort ~ " -j DNAT --to-destination " ~ DOCKERMERGED.containers[container].ip ~ ":" ~ containerPort) %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- do D2.append("-A DOCKER -d " ~ DOCKERMERGED.containers[container].ip ~ "/32 ! -i " ~ BRIDGE ~ " -o " ~ BRIDGE ~ " -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j ACCEPT") %}
|
{%- do D2.append("-A DOCKER -d " ~ DOCKERMERGED.containers[container].ip ~ "/32 ! -i sobridge -o sobridge -p " ~ proto ~ " -m " ~ proto ~ " --dport " ~ containerPort ~ " -j ACCEPT") %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
@@ -60,15 +52,11 @@
|
|||||||
:DOCKER - [0:0]
|
:DOCKER - [0:0]
|
||||||
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
|
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
|
||||||
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
|
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
|
||||||
{%- for NETNAME in NODE_NETWORKS %}
|
-A POSTROUTING -s {{DOCKERMERGED.range}} ! -o sobridge -j MASQUERADE
|
||||||
-A POSTROUTING -s {{ DOCKERMERGED.networks[NETNAME].range }} ! -o {{ NETNAME }} -j MASQUERADE
|
|
||||||
{%- endfor %}
|
|
||||||
{%- for rule in PR %}
|
{%- for rule in PR %}
|
||||||
{{ rule }}
|
{{ rule }}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{%- for NETNAME in NODE_NETWORKS %}
|
-A DOCKER -i sobridge -j RETURN
|
||||||
-A DOCKER -i {{ NETNAME }} -j RETURN
|
|
||||||
{%- endfor %}
|
|
||||||
{%- for rule in D1 %}
|
{%- for rule in D1 %}
|
||||||
{{ rule }}
|
{{ rule }}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
@@ -109,12 +97,10 @@ COMMIT
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
-A FORWARD -j DOCKER-USER
|
-A FORWARD -j DOCKER-USER
|
||||||
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
|
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
|
||||||
{%- for NETNAME in NODE_NETWORKS %}
|
-A FORWARD -o sobridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
-A FORWARD -o {{ NETNAME }} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
-A FORWARD -o sobridge -j DOCKER
|
||||||
-A FORWARD -o {{ NETNAME }} -j DOCKER
|
-A FORWARD -i sobridge ! -o sobridge -j ACCEPT
|
||||||
-A FORWARD -i {{ NETNAME }} ! -o {{ NETNAME }} -j ACCEPT
|
-A FORWARD -i sobridge -o sobridge -j ACCEPT
|
||||||
-A FORWARD -i {{ NETNAME }} -o {{ NETNAME }} -j ACCEPT
|
|
||||||
{%- endfor %}
|
|
||||||
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
-A FORWARD -i lo -j ACCEPT
|
-A FORWARD -i lo -j ACCEPT
|
||||||
-A FORWARD -m conntrack --ctstate INVALID -j DROP
|
-A FORWARD -m conntrack --ctstate INVALID -j DROP
|
||||||
@@ -126,18 +112,13 @@ COMMIT
|
|||||||
{%- for rule in D2 %}
|
{%- for rule in D2 %}
|
||||||
{{ rule }}
|
{{ rule }}
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
{% for NETNAME in NODE_NETWORKS %}
|
|
||||||
-A DOCKER-ISOLATION-STAGE-1 -i {{ NETNAME }} ! -o {{ NETNAME }} -j DOCKER-ISOLATION-STAGE-2
|
-A DOCKER-ISOLATION-STAGE-1 -i sobridge ! -o sobridge -j DOCKER-ISOLATION-STAGE-2
|
||||||
{%- endfor %}
|
|
||||||
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
|
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
|
||||||
{%- for NETNAME in NODE_NETWORKS %}
|
-A DOCKER-ISOLATION-STAGE-2 -o sobridge -j DROP
|
||||||
-A DOCKER-ISOLATION-STAGE-2 -o {{ NETNAME }} -j DROP
|
|
||||||
{%- endfor %}
|
|
||||||
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
|
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
|
||||||
{%- for NETNAME in NODE_NETWORKS %}
|
-A DOCKER-USER ! -i sobridge -o sobridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
-A DOCKER-USER ! -i {{ NETNAME }} -o {{ NETNAME }} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
-A DOCKER-USER ! -i sobridge -o sobridge -j LOGGING
|
||||||
-A DOCKER-USER ! -i {{ NETNAME }} -o {{ NETNAME }} -j LOGGING
|
|
||||||
{%- endfor %}
|
|
||||||
-A DOCKER-USER -j RETURN
|
-A DOCKER-USER -j RETURN
|
||||||
-A LOGGING -m limit --limit 2/min -j LOG --log-prefix "IPTables-dropped: "
|
-A LOGGING -m limit --limit 2/min -j LOG --log-prefix "IPTables-dropped: "
|
||||||
-A LOGGING -j DROP
|
-A LOGGING -j DROP
|
||||||
|
|||||||
@@ -4,12 +4,8 @@
|
|||||||
|
|
||||||
{# add our ip to self #}
|
{# add our ip to self #}
|
||||||
{% do FIREWALL_DEFAULT.firewall.hostgroups.self.append(GLOBALS.node_ip) %}
|
{% do FIREWALL_DEFAULT.firewall.hostgroups.self.append(GLOBALS.node_ip) %}
|
||||||
{# add dockernet ranges #}
|
{# add dockernet range #}
|
||||||
{% for NETNAME, NETWORK in DOCKERMERGED.networks.items() %}
|
{% do FIREWALL_DEFAULT.firewall.hostgroups.dockernet.append(DOCKERMERGED.range) %}
|
||||||
{% if not NETWORK.get('manager_only') or GLOBALS.get('is_manager', False) %}
|
|
||||||
{% do FIREWALL_DEFAULT.firewall.hostgroups.dockernet.append(NETWORK.range) %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if GLOBALS.role == 'so-idh' %}
|
{% if GLOBALS.role == 'so-idh' %}
|
||||||
{% from 'idh/opencanary_config.map.jinja' import IDH_PORTGROUPS %}
|
{% from 'idh/opencanary_config.map.jinja' import IDH_PORTGROUPS %}
|
||||||
|
|||||||
@@ -13,11 +13,9 @@
|
|||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% if 'api' in salt['pillar.get']('features', []) %}
|
{% if 'api' in salt['pillar.get']('features', []) %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- hydra.config
|
- hydra.config
|
||||||
- hydra.sostatus
|
- hydra.sostatus
|
||||||
|
|
||||||
@@ -28,10 +26,9 @@ so-hydra:
|
|||||||
- hostname: hydra
|
- hostname: hydra
|
||||||
- name: so-hydra
|
- name: so-hydra
|
||||||
- networks:
|
- networks:
|
||||||
- soauth:
|
- sobridge:
|
||||||
- ipv4_address: {{ DOCKERMERGED.containers['so-hydra'].ips['soauth'] }}
|
- ipv4_address: {{ DOCKERMERGED.containers['so-hydra'].ip }}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- /opt/so/conf/hydra/:/hydra-conf:ro
|
- /opt/so/conf/hydra/:/hydra-conf:ro
|
||||||
- /opt/so/log/hydra/:/hydra-log:rw
|
- /opt/so/log/hydra/:/hydra-log:rw
|
||||||
- /nsm/hydra/db:/hydra-data:rw
|
- /nsm/hydra/db:/hydra-data:rw
|
||||||
@@ -63,7 +60,6 @@ so-hydra:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: hydraconfig
|
- file: hydraconfig
|
||||||
- require:
|
- require:
|
||||||
- file: hydraconfig
|
- file: hydraconfig
|
||||||
@@ -77,7 +73,7 @@ delete_so-hydra_so-status.disabled:
|
|||||||
|
|
||||||
wait_for_hydra:
|
wait_for_hydra:
|
||||||
http.wait_for_successful_query:
|
http.wait_for_successful_query:
|
||||||
- name: 'http://{{ DOCKERMERGED.containers['so-hydra'].ips['soauth'] }}:4444/health/alive'
|
- name: 'http://{{ GLOBALS.manager }}:4444/health/alive'
|
||||||
- ssl: True
|
- ssl: True
|
||||||
- verify_ssl: False
|
- verify_ssl: False
|
||||||
- status:
|
- status:
|
||||||
|
|||||||
@@ -21,16 +21,12 @@ hypervisor_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://hypervisor/tools/sbin
|
- source: salt://hypervisor/tools/sbin
|
||||||
- user: root
|
|
||||||
- group: root
|
|
||||||
- file_mode: 744
|
- file_mode: 744
|
||||||
|
|
||||||
hypervisor_sbin_jinja:
|
hypervisor_sbin_jinja:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://hypervisor/tools/sbin_jinja
|
- source: salt://hypervisor/tools/sbin_jinja
|
||||||
- user: root
|
|
||||||
- group: root
|
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- file_mode: 744
|
- file_mode: 744
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -86,8 +86,8 @@ idh_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://idh/tools/sbin
|
- source: salt://idh/tools/sbin
|
||||||
- user: root
|
- user: 939
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#idh_sbin_jinja:
|
#idh_sbin_jinja:
|
||||||
|
|||||||
@@ -6,11 +6,9 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- idh.config
|
- idh.config
|
||||||
- idh.sostatus
|
- idh.sostatus
|
||||||
|
|
||||||
@@ -22,7 +20,6 @@ so-idh:
|
|||||||
- detach: True
|
- detach: True
|
||||||
- network_mode: host
|
- network_mode: host
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- /nsm/idh:/var/tmp:rw
|
- /nsm/idh:/var/tmp:rw
|
||||||
- /opt/so/conf/idh/http-skins:/opt/opencanary/http-skins:ro
|
- /opt/so/conf/idh/http-skins:/opt/opencanary/http-skins:ro
|
||||||
- /opt/so/conf/idh/opencanary.conf:/etc/opencanaryd/opencanary.conf:ro
|
- /opt/so/conf/idh/opencanary.conf:/etc/opencanaryd/opencanary.conf:ro
|
||||||
@@ -37,13 +34,12 @@ so-idh:
|
|||||||
- {{ XTRAHOST }}
|
- {{ XTRAHOST }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if DOCKERMERGED.containers['so-idh'].extra_env %}
|
||||||
- environment:
|
- environment:
|
||||||
- REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt
|
{% for XTRAENV in DOCKERMERGED.containers['so-idh'].extra_env %}
|
||||||
{% if DOCKERMERGED.containers['so-idh'].extra_env %}
|
|
||||||
{% for XTRAENV in DOCKERMERGED.containers['so-idh'].extra_env %}
|
|
||||||
- {{ XTRAENV }}
|
- {{ XTRAENV }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if DOCKERMERGED.containers['so-idh'].ulimits %}
|
{% if DOCKERMERGED.containers['so-idh'].ulimits %}
|
||||||
- ulimits:
|
- ulimits:
|
||||||
{% for ULIMIT in DOCKERMERGED.containers['so-idh'].ulimits %}
|
{% for ULIMIT in DOCKERMERGED.containers['so-idh'].ulimits %}
|
||||||
@@ -51,7 +47,6 @@ so-idh:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: opencanary_config
|
- file: opencanary_config
|
||||||
- require:
|
- require:
|
||||||
- file: opencanary_config
|
- file: opencanary_config
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ influxdb_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://influxdb/tools/sbin
|
- source: salt://influxdb/tools/sbin
|
||||||
- user: root
|
- user: 939
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#influxdb_sbin_jinja:
|
#influxdb_sbin_jinja:
|
||||||
|
|||||||
@@ -6,13 +6,11 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% set PASSWORD = salt['pillar.get']('secrets:influx_pass') %}
|
{% set PASSWORD = salt['pillar.get']('secrets:influx_pass') %}
|
||||||
{% set TOKEN = salt['pillar.get']('influxdb:token') %}
|
{% set TOKEN = salt['pillar.get']('influxdb:token') %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- influxdb.ssl
|
- influxdb.ssl
|
||||||
- influxdb.config
|
- influxdb.config
|
||||||
- influxdb.sostatus
|
- influxdb.sostatus
|
||||||
@@ -40,7 +38,6 @@ so-influxdb:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.pem }}:/etc/ssl/certs/ca-certificates.crt:ro
|
|
||||||
- /opt/so/log/influxdb/:/log:rw
|
- /opt/so/log/influxdb/:/log:rw
|
||||||
- /opt/so/conf/influxdb/config.yaml:/conf/config.yaml:ro
|
- /opt/so/conf/influxdb/config.yaml:/conf/config.yaml:ro
|
||||||
- /opt/so/conf/influxdb/etc:/etc/influxdb2:rw
|
- /opt/so/conf/influxdb/etc:/etc/influxdb2:rw
|
||||||
@@ -69,7 +66,6 @@ so-influxdb:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: influxdbconf
|
- file: influxdbconf
|
||||||
- x509: influxdb_key
|
- x509: influxdb_key
|
||||||
- x509: influxdb_crt
|
- x509: influxdb_crt
|
||||||
@@ -98,11 +94,9 @@ metrics_link_file:
|
|||||||
- docker_container: so-influxdb
|
- docker_container: so-influxdb
|
||||||
|
|
||||||
# Install cron job to determine size of influxdb for telegraf
|
# Install cron job to determine size of influxdb for telegraf
|
||||||
# telegraf reads this while the cron rewrites it, so write aside and rename rather than
|
|
||||||
# truncating in place. tgraflogdir recurses ownership, so the temp file is chowned to match
|
|
||||||
get_influxdb_size:
|
get_influxdb_size:
|
||||||
cron.present:
|
cron.present:
|
||||||
- name: 'du -s -k /nsm/influxdb | cut -f1 > /opt/so/log/telegraf/influxdb_size.log.tmp 2>&1; chown 939:939 /opt/so/log/telegraf/influxdb_size.log.tmp; mv -f /opt/so/log/telegraf/influxdb_size.log.tmp /opt/so/log/telegraf/influxdb_size.log'
|
- name: 'du -s -k /nsm/influxdb | cut -f1 > /opt/so/log/telegraf/influxdb_size.log 2>&1'
|
||||||
- identifier: get_influxdb_size
|
- identifier: get_influxdb_size
|
||||||
- user: root
|
- user: root
|
||||||
- minute: '*/1'
|
- minute: '*/1'
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ kafka_user:
|
|||||||
- gid: 960
|
- gid: 960
|
||||||
- home: /opt/so/conf/kafka
|
- home: /opt/so/conf/kafka
|
||||||
- createhome: False
|
- createhome: False
|
||||||
- shell: /sbin/nologin
|
|
||||||
|
|
||||||
kafka_home_dir:
|
kafka_home_dir:
|
||||||
file.absent:
|
file.absent:
|
||||||
@@ -31,16 +30,16 @@ kafka_sbin_tools:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://kafka/tools/sbin
|
- source: salt://kafka/tools/sbin
|
||||||
- user: root
|
- user: 960
|
||||||
- group: root
|
- group: 960
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
kafka_sbin_jinja_tools:
|
kafka_sbin_jinja_tools:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://kafka/tools/sbin_jinja
|
- source: salt://kafka/tools/sbin_jinja
|
||||||
- user: root
|
- user: 960
|
||||||
- group: root
|
- group: 960
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- defaults:
|
- defaults:
|
||||||
|
|||||||
@@ -12,14 +12,12 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% set KAFKANODES = salt['pillar.get']('kafka:nodes') %}
|
{% set KAFKANODES = salt['pillar.get']('kafka:nodes') %}
|
||||||
{% set KAFKA_EXTERNAL_ACCESS = salt['pillar.get']('kafka:config:external_access:enabled', default=False) %}
|
{% set KAFKA_EXTERNAL_ACCESS = salt['pillar.get']('kafka:config:external_access:enabled', default=False) %}
|
||||||
{% if 'gmd' in salt['pillar.get']('features', []) %}
|
{% if 'gmd' in salt['pillar.get']('features', []) %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- kafka.ca
|
- kafka.ca
|
||||||
- kafka.config
|
- kafka.config
|
||||||
- kafka.ssl
|
- kafka.ssl
|
||||||
@@ -53,7 +51,6 @@ so-kafka:
|
|||||||
- {{ BINDING }}
|
- {{ BINDING }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.java }}:/opt/java/openjdk/lib/security/cacerts:ro
|
|
||||||
- /etc/pki/kafka.p12:/etc/pki/kafka.p12:ro
|
- /etc/pki/kafka.p12:/etc/pki/kafka.p12:ro
|
||||||
- /opt/so/conf/kafka/kafka-truststore.jks:/etc/pki/kafka-truststore.jks:ro
|
- /opt/so/conf/kafka/kafka-truststore.jks:/etc/pki/kafka-truststore.jks:ro
|
||||||
- /nsm/kafka/data/:/nsm/kafka/data/:rw
|
- /nsm/kafka/data/:/nsm/kafka/data/:rw
|
||||||
@@ -64,11 +61,6 @@ so-kafka:
|
|||||||
{% if KAFKA_EXTERNAL_ACCESS %}
|
{% if KAFKA_EXTERNAL_ACCESS %}
|
||||||
- /opt/so/conf/kafka/kafka_server_jaas.conf:/opt/kafka/config/kafka_server_jaas.conf:ro
|
- /opt/so/conf/kafka/kafka_server_jaas.conf:/opt/kafka/config/kafka_server_jaas.conf:ro
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if DOCKERMERGED.containers['so-kafka'].custom_bind_mounts %}
|
|
||||||
{% for BIND in DOCKERMERGED.containers['so-kafka'].custom_bind_mounts %}
|
|
||||||
- {{ BIND }}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if DOCKERMERGED.containers['so-kafka'].ulimits %}
|
{% if DOCKERMERGED.containers['so-kafka'].ulimits %}
|
||||||
- ulimits:
|
- ulimits:
|
||||||
{% for ULIMIT in DOCKERMERGED.containers['so-kafka'].ulimits %}
|
{% for ULIMIT in DOCKERMERGED.containers['so-kafka'].ulimits %}
|
||||||
@@ -76,7 +68,6 @@ so-kafka:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
{% for sc in ['server', 'client'] %}
|
{% for sc in ['server', 'client'] %}
|
||||||
- file: kafka_kraft_{{sc}}_properties
|
- file: kafka_kraft_{{sc}}_properties
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ kibana:
|
|||||||
- gid: 932
|
- gid: 932
|
||||||
- home: /opt/so/conf/kibana
|
- home: /opt/so/conf/kibana
|
||||||
- createhome: False
|
- createhome: False
|
||||||
- shell: /sbin/nologin
|
|
||||||
|
|
||||||
# Drop the correct nginx config based on role
|
# Drop the correct nginx config based on role
|
||||||
|
|
||||||
@@ -37,16 +36,16 @@ kibana_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://kibana/tools/sbin
|
- source: salt://kibana/tools/sbin
|
||||||
- user: root
|
- user: 932
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
kibana_sbin_jinja:
|
kibana_sbin_jinja:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://kibana/tools/sbin_jinja
|
- source: salt://kibana/tools/sbin_jinja
|
||||||
- user: root
|
- user: 932
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- defaults:
|
- defaults:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ kibana:
|
|||||||
- default
|
- default
|
||||||
- file
|
- file
|
||||||
migrations:
|
migrations:
|
||||||
discardCorruptObjects: "9.4.8"
|
discardCorruptObjects: "9.4.5"
|
||||||
telemetry:
|
telemetry:
|
||||||
enabled: False
|
enabled: False
|
||||||
xpack:
|
xpack:
|
||||||
|
|||||||
@@ -8,10 +8,8 @@
|
|||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCHMERGED %}
|
{% from 'elasticsearch/config.map.jinja' import ELASTICSEARCHMERGED %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- kibana.config
|
- kibana.config
|
||||||
- kibana.sostatus
|
- kibana.sostatus
|
||||||
|
|
||||||
@@ -29,7 +27,6 @@ so-kibana:
|
|||||||
- ELASTICSEARCH_HOST={{ GLOBALS.manager }}
|
- ELASTICSEARCH_HOST={{ GLOBALS.manager }}
|
||||||
- ELASTICSEARCH_PORT=9200
|
- ELASTICSEARCH_PORT=9200
|
||||||
- MANAGER={{ GLOBALS.manager }}
|
- MANAGER={{ GLOBALS.manager }}
|
||||||
- NODE_OPTIONS=--use-openssl-ca
|
|
||||||
{% if DOCKERMERGED.containers['so-kibana'].extra_env %}
|
{% if DOCKERMERGED.containers['so-kibana'].extra_env %}
|
||||||
{% for XTRAENV in DOCKERMERGED.containers['so-kibana'].extra_env %}
|
{% for XTRAENV in DOCKERMERGED.containers['so-kibana'].extra_env %}
|
||||||
- {{ XTRAENV }}
|
- {{ XTRAENV }}
|
||||||
@@ -43,7 +40,6 @@ so-kibana:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- /opt/so/conf/kibana/etc:/usr/share/kibana/config:rw
|
- /opt/so/conf/kibana/etc:/usr/share/kibana/config:rw
|
||||||
- /opt/so/log/kibana:/var/log/kibana:rw
|
- /opt/so/log/kibana:/var/log/kibana:rw
|
||||||
- /opt/so/conf/kibana/customdashboards:/usr/share/kibana/custdashboards:ro
|
- /opt/so/conf/kibana/customdashboards:/usr/share/kibana/custdashboards:ro
|
||||||
@@ -64,7 +60,6 @@ so-kibana:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: kibanaconfig
|
- file: kibanaconfig
|
||||||
|
|
||||||
wait_for_so-kibana:
|
wait_for_so-kibana:
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ kratos:
|
|||||||
- uid: 928
|
- uid: 928
|
||||||
- gid: 928
|
- gid: 928
|
||||||
- home: /opt/so/conf/kratos
|
- home: /opt/so/conf/kratos
|
||||||
- shell: /sbin/nologin
|
|
||||||
|
|
||||||
kratosdir:
|
kratosdir:
|
||||||
file.directory:
|
file.directory:
|
||||||
|
|||||||
@@ -7,10 +7,8 @@
|
|||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- kratos.config
|
- kratos.config
|
||||||
- kratos.sostatus
|
- kratos.sostatus
|
||||||
|
|
||||||
@@ -21,10 +19,9 @@ so-kratos:
|
|||||||
- hostname: kratos
|
- hostname: kratos
|
||||||
- name: so-kratos
|
- name: so-kratos
|
||||||
- networks:
|
- networks:
|
||||||
- soauth:
|
- sobridge:
|
||||||
- ipv4_address: {{ DOCKERMERGED.containers['so-kratos'].ips['soauth'] }}
|
- ipv4_address: {{ DOCKERMERGED.containers['so-kratos'].ip }}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- /opt/so/conf/kratos/:/kratos-conf:ro
|
- /opt/so/conf/kratos/:/kratos-conf:ro
|
||||||
- /opt/so/log/kratos/:/kratos-log:rw
|
- /opt/so/log/kratos/:/kratos-log:rw
|
||||||
- /nsm/kratos/db:/kratos-data:rw
|
- /nsm/kratos/db:/kratos-data:rw
|
||||||
@@ -56,7 +53,6 @@ so-kratos:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: kratosschema
|
- file: kratosschema
|
||||||
- file: kratosconfig
|
- file: kratosconfig
|
||||||
- require:
|
- require:
|
||||||
@@ -75,7 +71,7 @@ delete_so-kratos_so-status.disabled:
|
|||||||
|
|
||||||
wait_for_kratos:
|
wait_for_kratos:
|
||||||
http.wait_for_successful_query:
|
http.wait_for_successful_query:
|
||||||
- name: 'http://{{ DOCKERMERGED.containers['so-kratos'].ips['soauth'] }}:4434/'
|
- name: 'http://{{ GLOBALS.manager }}:4434/'
|
||||||
- ssl: True
|
- ssl: True
|
||||||
- verify_ssl: False
|
- verify_ssl: False
|
||||||
- status:
|
- status:
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ so-fix-salt-ldap_script:
|
|||||||
file.managed:
|
file.managed:
|
||||||
- name: /usr/sbin/so-fix-salt-ldap.py
|
- name: /usr/sbin/so-fix-salt-ldap.py
|
||||||
- source: salt://libvirt/64962/scripts/so-fix-salt-ldap.py
|
- source: salt://libvirt/64962/scripts/so-fix-salt-ldap.py
|
||||||
- user: root
|
|
||||||
- group: root
|
|
||||||
- mode: 744
|
- mode: 744
|
||||||
|
|
||||||
fix-salt-ldap:
|
fix-salt-ldap:
|
||||||
|
|||||||
@@ -35,14 +35,13 @@ logstash:
|
|||||||
- uid: 931
|
- uid: 931
|
||||||
- gid: 931
|
- gid: 931
|
||||||
- home: /opt/so/conf/logstash
|
- home: /opt/so/conf/logstash
|
||||||
- shell: /sbin/nologin
|
|
||||||
|
|
||||||
logstash_sbin:
|
logstash_sbin:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://logstash/tools/sbin
|
- source: salt://logstash/tools/sbin
|
||||||
- user: root
|
- user: 931
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#logstash_sbin_jinja:
|
#logstash_sbin_jinja:
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% from 'logstash/map.jinja' import LOGSTASH_MERGED %}
|
{% from 'logstash/map.jinja' import LOGSTASH_MERGED %}
|
||||||
{% from 'logstash/map.jinja' import LOGSTASH_NODES %}
|
{% from 'logstash/map.jinja' import LOGSTASH_NODES %}
|
||||||
@@ -14,6 +13,9 @@
|
|||||||
|
|
||||||
include:
|
include:
|
||||||
- ca
|
- ca
|
||||||
|
{% if GLOBALS.role not in ['so-receiver','so-fleet'] %}
|
||||||
|
- elasticsearch.ca
|
||||||
|
{% endif %}
|
||||||
{# Kafka ca runs on nodes that can run logstash for Kafka input / output. Only when Kafka is global pipeline #}
|
{# Kafka ca runs on nodes that can run logstash for Kafka input / output. Only when Kafka is global pipeline #}
|
||||||
{% if GLOBALS.role in ['so-searchnode', 'so-manager', 'so-managerhype', 'so-managersearch', 'so-receiver', 'so-standalone'] and GLOBALS.pipeline == 'KAFKA' %}
|
{% if GLOBALS.role in ['so-searchnode', 'so-manager', 'so-managerhype', 'so-managersearch', 'so-receiver', 'so-standalone'] and GLOBALS.pipeline == 'KAFKA' %}
|
||||||
- kafka.ca
|
- kafka.ca
|
||||||
@@ -56,8 +58,6 @@ so-logstash:
|
|||||||
- {{ BINDING }}
|
- {{ BINDING }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- {{ CATRUST.java }}:/usr/share/logstash/jdk/lib/security/cacerts:ro
|
|
||||||
- /opt/so/conf/elasticsearch/templates/:/templates/:ro
|
- /opt/so/conf/elasticsearch/templates/:/templates/:ro
|
||||||
- /opt/so/conf/logstash/etc/:/usr/share/logstash/config/:ro
|
- /opt/so/conf/logstash/etc/:/usr/share/logstash/config/:ro
|
||||||
- /opt/so/conf/logstash/pipelines:/usr/share/logstash/pipelines:ro
|
- /opt/so/conf/logstash/pipelines:/usr/share/logstash/pipelines:ro
|
||||||
@@ -78,6 +78,10 @@ so-logstash:
|
|||||||
- /etc/pki/filebeat.p8:/usr/share/logstash/filebeat.key:ro
|
- /etc/pki/filebeat.p8:/usr/share/logstash/filebeat.key:ro
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if GLOBALS.role not in ['so-receiver','so-fleet'] %}
|
||||||
|
- /opt/so/conf/ca/cacerts:/etc/pki/ca-trust/extracted/java/cacerts:ro
|
||||||
|
- /opt/so/conf/ca/tls-ca-bundle.pem:/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem:ro
|
||||||
|
{% endif %}
|
||||||
{% if GLOBALS.pipeline == "KAFKA" and GLOBALS.role in ['so-manager', 'so-managerhype', 'so-managersearch', 'so-standalone', 'so-searchnode'] %}
|
{% if GLOBALS.pipeline == "KAFKA" and GLOBALS.role in ['so-manager', 'so-managerhype', 'so-managersearch', 'so-standalone', 'so-searchnode'] %}
|
||||||
- /etc/pki/kafka-logstash.p12:/usr/share/logstash/kafka-logstash.p12:ro
|
- /etc/pki/kafka-logstash.p12:/usr/share/logstash/kafka-logstash.p12:ro
|
||||||
- /opt/so/conf/kafka/kafka-truststore.jks:/etc/pki/kafka-truststore.jks:ro
|
- /opt/so/conf/kafka/kafka-truststore.jks:/etc/pki/kafka-truststore.jks:ro
|
||||||
@@ -100,11 +104,14 @@ so-logstash:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: lsetcsync
|
- file: lsetcsync
|
||||||
- file: lslog4j2
|
- file: lslog4j2
|
||||||
- file: lspipelinesyml
|
- file: lspipelinesyml
|
||||||
- file: trusttheca
|
- file: trusttheca
|
||||||
|
{% if GLOBALS.is_manager %}
|
||||||
|
- file: elasticsearch_cacerts
|
||||||
|
- file: elasticsearch_capems
|
||||||
|
{% endif %}
|
||||||
{% if GLOBALS.is_manager or GLOBALS.role in ['so-fleet', 'so-heavynode', 'so-receiver'] %}
|
{% if GLOBALS.is_manager or GLOBALS.role in ['so-fleet', 'so-heavynode', 'so-receiver'] %}
|
||||||
- x509: etc_elasticfleet_logstash_crt
|
- x509: etc_elasticfleet_logstash_crt
|
||||||
- x509: etc_elasticfleet_logstash_key
|
- x509: etc_elasticfleet_logstash_key
|
||||||
@@ -126,6 +133,10 @@ so-logstash:
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
- require:
|
- require:
|
||||||
- file: trusttheca
|
- file: trusttheca
|
||||||
|
{% if GLOBALS.is_manager %}
|
||||||
|
- file: elasticsearch_cacerts
|
||||||
|
- file: elasticsearch_capems
|
||||||
|
{% endif %}
|
||||||
{% if GLOBALS.is_manager or GLOBALS.role in ['so-fleet', 'so-heavynode', 'so-receiver'] %}
|
{% if GLOBALS.is_manager or GLOBALS.role in ['so-fleet', 'so-heavynode', 'so-receiver'] %}
|
||||||
- x509: etc_elasticfleet_logstash_crt
|
- x509: etc_elasticfleet_logstash_crt
|
||||||
- x509: etc_elasticfleet_logstash_key
|
- x509: etc_elasticfleet_logstash_key
|
||||||
|
|||||||
+8
-12
@@ -113,8 +113,8 @@ manager_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://manager/tools/sbin
|
- source: salt://manager/tools/sbin
|
||||||
- user: root
|
- user: 939
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- exclude_pat:
|
- exclude_pat:
|
||||||
- "*_test.py"
|
- "*_test.py"
|
||||||
@@ -124,8 +124,8 @@ manager_sbin_jinja:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin/
|
- name: /usr/sbin/
|
||||||
- source: salt://manager/tools/sbin_jinja/
|
- source: salt://manager/tools/sbin_jinja/
|
||||||
- user: root
|
- user: socore
|
||||||
- group: root
|
- group: socore
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
- show_changes: False
|
- show_changes: False
|
||||||
@@ -166,7 +166,7 @@ so-repo-sync:
|
|||||||
|
|
||||||
so_fleetagent_status:
|
so_fleetagent_status:
|
||||||
cron.present:
|
cron.present:
|
||||||
- name: '/usr/sbin/so-elasticagent-status > /opt/so/log/agents/agentstatus.log.tmp 2>&1; mv -f /opt/so/log/agents/agentstatus.log.tmp /opt/so/log/agents/agentstatus.log'
|
- name: /usr/sbin/so-elasticagent-status > /opt/so/log/agents/agentstatus.log 2>&1
|
||||||
- identifier: so_fleetagent_status
|
- identifier: so_fleetagent_status
|
||||||
- user: root
|
- user: root
|
||||||
- minute: '*/5'
|
- minute: '*/5'
|
||||||
@@ -190,15 +190,11 @@ so_fleetagent_monitor:
|
|||||||
- month: '*'
|
- month: '*'
|
||||||
- dayweek: '*'
|
- dayweek: '*'
|
||||||
|
|
||||||
# This tree is the source of every root-executed script (/usr/sbin, reactors, _runners,
|
socore_own_saltstack_default:
|
||||||
# engines, salt-relay.sh). SOC mounts /opt/so/saltstack rw as uid 939 but only writes
|
|
||||||
# under local/. Do not add dir_mode/file_mode here -- SOC reads default/ and 750/640
|
|
||||||
# would break its config load.
|
|
||||||
root_own_saltstack_default:
|
|
||||||
file.directory:
|
file.directory:
|
||||||
- name: /opt/so/saltstack/default
|
- name: /opt/so/saltstack/default
|
||||||
- user: root
|
- user: socore
|
||||||
- group: root
|
- group: socore
|
||||||
- recurse:
|
- recurse:
|
||||||
- user
|
- user
|
||||||
- group
|
- group
|
||||||
|
|||||||
@@ -106,8 +106,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
hydraContainer=${HYDRA_CONTAINER:-so-hydra}
|
hydraUrl=${HYDRA_URL:-http://127.0.0.1:4445}
|
||||||
hydraUrl=${HYDRA_URL:-http://localhost:4445}
|
|
||||||
socRolesFile=${SOC_ROLES_FILE:-/opt/so/conf/soc/soc_clients_roles}
|
socRolesFile=${SOC_ROLES_FILE:-/opt/so/conf/soc/soc_clients_roles}
|
||||||
soUID=${SOCORE_UID:-939}
|
soUID=${SOCORE_UID:-939}
|
||||||
soGID=${SOCORE_GID:-939}
|
soGID=${SOCORE_GID:-939}
|
||||||
@@ -125,10 +124,6 @@ function fail() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function hydraCurl() {
|
|
||||||
docker exec "$hydraContainer" curl "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
function require() {
|
function require() {
|
||||||
cmd=$1
|
cmd=$1
|
||||||
which "$1" 2>&1 > /dev/null
|
which "$1" 2>&1 > /dev/null
|
||||||
@@ -138,8 +133,8 @@ function require() {
|
|||||||
# Verify this environment is capable of running this script
|
# Verify this environment is capable of running this script
|
||||||
function verifyEnvironment() {
|
function verifyEnvironment() {
|
||||||
require "jq"
|
require "jq"
|
||||||
require "docker"
|
require "curl"
|
||||||
response=$(hydraCurl -Ss -L ${hydraUrl}/health/alive)
|
response=$(curl -Ss -L ${hydraUrl}/health/alive)
|
||||||
[[ "$response" != '{"status":"ok"}' ]] && fail "Unable to communicate with Hydra; specify URL via HYDRA_URL environment variable"
|
[[ "$response" != '{"status":"ok"}' ]] && fail "Unable to communicate with Hydra; specify URL via HYDRA_URL environment variable"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +164,7 @@ function ensureRoleFileExists() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listClients() {
|
function listClients() {
|
||||||
response=$(hydraCurl -Ss -L -f ${hydraUrl}/admin/clients)
|
response=$(curl -Ss -L -f ${hydraUrl}/admin/clients)
|
||||||
[[ $? != 0 ]] && fail "Unable to communicate with Hydra"
|
[[ $? != 0 ]] && fail "Unable to communicate with Hydra"
|
||||||
|
|
||||||
clientIds=$(echo "${response}" | jq -r ".[] | .client_id" | sort)
|
clientIds=$(echo "${response}" | jq -r ".[] | .client_id" | sort)
|
||||||
@@ -256,7 +251,7 @@ function createClient() {
|
|||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
response=$(hydraCurl -Ss -L --fail-with-body -X POST ${hydraUrl}/admin/clients -d "$body")
|
response=$(curl -Ss -L --fail-with-body -X POST ${hydraUrl}/admin/clients -d "$body")
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
error=$(echo $response | jq .error)
|
error=$(echo $response | jq .error)
|
||||||
fail "Failed to submit request to Hydra: $error"
|
fail "Failed to submit request to Hydra: $error"
|
||||||
@@ -288,7 +283,7 @@ function update() {
|
|||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
response=$(hydraCurl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
|
response=$(curl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
error=$(echo $response | jq .error)
|
error=$(echo $response | jq .error)
|
||||||
fail "Failed to submit request to Hydra: $error"
|
fail "Failed to submit request to Hydra: $error"
|
||||||
@@ -310,7 +305,7 @@ function generateSecret() {
|
|||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
response=$(hydraCurl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
|
response=$(curl -Ss -L --fail-with-body -X PATCH ${hydraUrl}/admin/clients/$id -d "$body")
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
error=$(echo $response | jq .error)
|
error=$(echo $response | jq .error)
|
||||||
fail "Failed to submit request to Hydra: $error"
|
fail "Failed to submit request to Hydra: $error"
|
||||||
@@ -322,7 +317,7 @@ function deleteClient() {
|
|||||||
|
|
||||||
[[ ${identityId} == "" ]] && fail "Client not found"
|
[[ ${identityId} == "" ]] && fail "Client not found"
|
||||||
|
|
||||||
response=$(hydraCurl -Ss -XDELETE -L --fail-with-body "${hydraUrl}/admin/clients/$identityId")
|
response=$(curl -Ss -XDELETE -L --fail-with-body "${hydraUrl}/admin/clients/$identityId")
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
error=$(echo $response | jq .error)
|
error=$(echo $response | jq .error)
|
||||||
fail "Failed to submit request to Hydra: $error"
|
fail "Failed to submit request to Hydra: $error"
|
||||||
|
|||||||
@@ -121,14 +121,8 @@ for i in "$@"; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ -n "$MINION_ID" && ! "$MINION_ID" =~ ^[A-Za-z0-9._-]{1,253}$ ]]; then
|
PILLARFILE=/opt/so/saltstack/local/pillar/minions/$MINION_ID.sls
|
||||||
echo "Invalid minion id: $MINION_ID"
|
ADVPILLARFILE=/opt/so/saltstack/local/pillar/minions/adv_$MINION_ID.sls
|
||||||
log "ERROR" "Invalid minion id: $MINION_ID"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
readonly PILLARFILE=/opt/so/saltstack/local/pillar/minions/$MINION_ID.sls
|
|
||||||
readonly ADVPILLARFILE=/opt/so/saltstack/local/pillar/minions/adv_$MINION_ID.sls
|
|
||||||
|
|
||||||
function getinstallinfo() {
|
function getinstallinfo() {
|
||||||
log "INFO" "Getting install info for minion $MINION_ID"
|
log "INFO" "Getting install info for minion $MINION_ID"
|
||||||
@@ -139,23 +133,10 @@ function getinstallinfo() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install.txt is controlled by the minion; only accept known keys and never eval or export them
|
while read -r var; do export "$var"; done <<< "$INSTALLVARS"
|
||||||
local line key
|
if [ $? -ne 0 ]; then
|
||||||
while IFS= read -r line; do
|
log "ERROR" "Failed to source install variables"
|
||||||
[[ "$line" == *=* ]] || continue
|
return 1
|
||||||
key=${line%%=*}
|
|
||||||
case "$key" in
|
|
||||||
MAINIP|MNIC|NODE_DESCRIPTION|ES_HEAP_SIZE|PATCHSCHEDULENAME|INTERFACE|NODETYPE|CORECOUNT|LSHOSTNAME|LSHEAP|CPUCORES|IDH_MGTRESTRICT|IDH_SERVICES)
|
|
||||||
printf -v "$key" '%s' "${line#*=}"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
log "WARN" "Ignoring unexpected install var from $MINION_ID: ${key:0:64}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done <<< "$INSTALLVARS"
|
|
||||||
|
|
||||||
if [[ "$NODE_DESCRIPTION" == \'*\' ]]; then
|
|
||||||
NODE_DESCRIPTION=${NODE_DESCRIPTION:1:-1}
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "INFO" "Fetched install info for $MINION_ID (node type: ${NODETYPE:-unset})"
|
log "INFO" "Fetched install info for $MINION_ID (node type: ${NODETYPE:-unset})"
|
||||||
@@ -195,12 +176,6 @@ function pcapspace() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Must be checked before arithmetic expansion, which evaluates array subscripts
|
|
||||||
if [[ ! "$SPACESIZE" =~ ^[0-9]+$ ]]; then
|
|
||||||
log "ERROR" "Invalid disk size for $MINION_ID: ${SPACESIZE:0:64}"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local s=$(( $SPACESIZE / 1000000 ))
|
local s=$(( $SPACESIZE / 1000000 ))
|
||||||
local s1=$(( $s / 4 * $PCAP_PERCENTAGE ))
|
local s1=$(( $s / 4 * $PCAP_PERCENTAGE ))
|
||||||
|
|
||||||
@@ -1075,57 +1050,6 @@ function updateMineAndApplyStates() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Values end up in a Jinja-rendered pillar and in bash, and may come from the minion
|
|
||||||
function validate_minion_vars() {
|
|
||||||
local error_msg=""
|
|
||||||
# Inline rather than valid_ip4: so-common is not installed yet when setup runs -o=setup
|
|
||||||
local octet='(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])'
|
|
||||||
local ip4_re="^($octet\.){3}$octet$"
|
|
||||||
|
|
||||||
case "$NODETYPE" in
|
|
||||||
EVAL|STANDALONE|MANAGER|MANAGERSEARCH|MANAGERHYPE|IMPORT)
|
|
||||||
# Manager pillars also rewrite the CA pillar, so never accept them from a remote node
|
|
||||||
[[ "$OPERATION" == "setup" ]] || error_msg="Node type $NODETYPE can only be configured during setup"
|
|
||||||
;;
|
|
||||||
FLEET|IDH|HEAVYNODE|SENSOR|SEARCHNODE|RECEIVER|HYPERVISOR|DESKTOP)
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
error_msg="Invalid node type: ${NODETYPE:0:64}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [[ -z "$error_msg" ]]; then
|
|
||||||
if [[ ! "$MAINIP" =~ $ip4_re ]]; then
|
|
||||||
error_msg="Invalid MAINIP: ${MAINIP:0:64}"
|
|
||||||
elif [[ ! "$MNIC" =~ ^[A-Za-z0-9._-]*$ ]]; then
|
|
||||||
error_msg="Invalid MNIC: ${MNIC:0:64}"
|
|
||||||
elif [[ ! "$INTERFACE" =~ ^[A-Za-z0-9._-]*$ ]]; then
|
|
||||||
error_msg="Invalid INTERFACE: ${INTERFACE:0:64}"
|
|
||||||
elif [[ ! "$LSHOSTNAME" =~ ^[A-Za-z0-9._-]*$ ]]; then
|
|
||||||
error_msg="Invalid LSHOSTNAME: ${LSHOSTNAME:0:64}"
|
|
||||||
elif [[ ! "$ES_HEAP_SIZE" =~ ^([0-9]+[kKmMgG]?)?$ ]]; then
|
|
||||||
error_msg="Invalid ES_HEAP_SIZE: ${ES_HEAP_SIZE:0:64}"
|
|
||||||
elif [[ ! "$LSHEAP" =~ ^([0-9]+[kKmMgG]?)?$ ]]; then
|
|
||||||
error_msg="Invalid LSHEAP: ${LSHEAP:0:64}"
|
|
||||||
elif [[ ! "$CORECOUNT" =~ ^[0-9]*$ ]]; then
|
|
||||||
error_msg="Invalid CORECOUNT: ${CORECOUNT:0:64}"
|
|
||||||
elif [[ ! "$CPUCORES" =~ ^[0-9]*$ ]]; then
|
|
||||||
error_msg="Invalid CPUCORES: ${CPUCORES:0:64}"
|
|
||||||
elif [[ ! "$IDH_MGTRESTRICT" =~ ^(True|False)?$ ]]; then
|
|
||||||
error_msg="Invalid IDH_MGTRESTRICT: ${IDH_MGTRESTRICT:0:64}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "$error_msg" ]]; then
|
|
||||||
log "ERROR" "$error_msg"
|
|
||||||
echo "$error_msg"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Free text; removing braces is enough to prevent any Jinja delimiter
|
|
||||||
NODE_DESCRIPTION=${NODE_DESCRIPTION//[\{\}[:cntrl:]]/}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupMinionFiles() {
|
function setupMinionFiles() {
|
||||||
log "INFO" "Setting up minion files for $MINION_ID (pillar: $PILLARFILE)"
|
log "INFO" "Setting up minion files for $MINION_ID (pillar: $PILLARFILE)"
|
||||||
|
|
||||||
@@ -1137,8 +1061,6 @@ function setupMinionFiles() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
validate_minion_vars || return 1
|
|
||||||
|
|
||||||
# Create the base minion files
|
# Create the base minion files
|
||||||
create_minion_files || return 1
|
create_minion_files || return 1
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ is older than debounce_seconds, this script:
|
|||||||
* dispatches a single `salt-run state.orchestrate orch.push_batch --async`
|
* dispatches a single `salt-run state.orchestrate orch.push_batch --async`
|
||||||
with the deduped actions list passed as pillar kwargs
|
with the deduped actions list passed as pillar kwargs
|
||||||
* deletes the contributed intent files on successful dispatch
|
* deletes the contributed intent files on successful dispatch
|
||||||
* records the orchestration jid under /opt/so/state/push_dispatched and, on
|
|
||||||
later passes, looks up its result and logs success or per-minion failures
|
|
||||||
|
|
||||||
Reactor sls files (push_files, push_pillar) write intents
|
Reactor sls files (push_files, push_pillar) write intents
|
||||||
but never dispatch directly
|
but never dispatch directly
|
||||||
@@ -32,7 +30,6 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
@@ -43,22 +40,8 @@ PENDING_DIR = '/opt/so/state/push_pending'
|
|||||||
LOCK_FILE = os.path.join(PENDING_DIR, '.lock')
|
LOCK_FILE = os.path.join(PENDING_DIR, '.lock')
|
||||||
LOG_FILE = '/opt/so/log/salt/so-push-drainer.log'
|
LOG_FILE = '/opt/so/log/salt/so-push-drainer.log'
|
||||||
|
|
||||||
DISPATCHED_DIR = '/opt/so/state/push_dispatched'
|
|
||||||
|
|
||||||
HIGHSTATE_SENTINEL = '__highstate__'
|
HIGHSTATE_SENTINEL = '__highstate__'
|
||||||
|
|
||||||
RESULT_CHECK_DELAY = 30
|
|
||||||
RESULT_RECHECK_MAX = 300
|
|
||||||
RESULT_MAX_AGE = 7200
|
|
||||||
RESULT_CHECKS_PER_PASS = 5
|
|
||||||
TEXT_LIMIT = 500
|
|
||||||
|
|
||||||
# Lead-in salt puts on the comment of an orchestration step that raised.
|
|
||||||
STEP_RAISED = 'An exception occurred in this state:'
|
|
||||||
|
|
||||||
# salt-run --async reports the jid only in a log line (stderr by default).
|
|
||||||
JID_RE = re.compile(r'salt/run/(\d{20})')
|
|
||||||
|
|
||||||
|
|
||||||
def _make_logger():
|
def _make_logger():
|
||||||
logger = logging.getLogger('so-push-drainer')
|
logger = logging.getLogger('so-push-drainer')
|
||||||
@@ -130,189 +113,14 @@ def _dispatch(actions, log):
|
|||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
log.error('dispatch failed (rc=%s): stdout=%s stderr=%s',
|
log.error('dispatch failed (rc=%s): stdout=%s stderr=%s',
|
||||||
exc.returncode, exc.stdout, exc.stderr)
|
exc.returncode, exc.stdout, exc.stderr)
|
||||||
return None
|
return False
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
log.error('dispatch timed out after 60s')
|
log.error('dispatch timed out after 60s')
|
||||||
return None
|
return False
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception('dispatch raised')
|
log.exception('dispatch raised')
|
||||||
return None
|
|
||||||
output = '{}\n{}'.format(result.stderr or '', result.stdout or '')
|
|
||||||
match = JID_RE.search(output)
|
|
||||||
if not match:
|
|
||||||
log.warning('dispatch accepted but no jid found, result will not be tracked: output=%s',
|
|
||||||
_trim(output))
|
|
||||||
return ''
|
|
||||||
log.info('dispatch accepted: jid=%s', match.group(1))
|
|
||||||
return match.group(1)
|
|
||||||
|
|
||||||
|
|
||||||
def _trim(value):
|
|
||||||
text = value if isinstance(value, str) else json.dumps(value, default=str)
|
|
||||||
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
|
||||||
if 'Traceback (most recent call last):' in text:
|
|
||||||
# Keep the lead-in and the raised exception; the frames are noise in a log line.
|
|
||||||
lines = [text.split('Traceback (most recent call last):', 1)[0].strip(), lines[-1]]
|
|
||||||
text = ' '.join(line for line in lines if line)
|
|
||||||
return text if len(text) <= TEXT_LIMIT else text[:TEXT_LIMIT] + '...'
|
|
||||||
|
|
||||||
|
|
||||||
def _unlink(path, log):
|
|
||||||
try:
|
|
||||||
os.unlink(path)
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
except OSError:
|
|
||||||
log.exception('failed to remove %s', path)
|
|
||||||
|
|
||||||
|
|
||||||
def _write_record(path, record, log):
|
|
||||||
try:
|
|
||||||
os.makedirs(DISPATCHED_DIR, exist_ok=True)
|
|
||||||
tmp_path = path + '.tmp'
|
|
||||||
with open(tmp_path, 'w') as f:
|
|
||||||
json.dump(record, f)
|
|
||||||
os.rename(tmp_path, path)
|
|
||||||
except Exception:
|
|
||||||
log.exception('failed to record dispatch %s', record.get('jid'))
|
|
||||||
|
|
||||||
|
|
||||||
def _record_dispatch(jid, actions, paths, log):
|
|
||||||
record = {'jid': jid, 'dispatched_at': time.time(), 'actions': actions, 'paths': paths}
|
|
||||||
_write_record(os.path.join(DISPATCHED_DIR, '{}.json'.format(jid)), record, log)
|
|
||||||
|
|
||||||
|
|
||||||
def _lookup_jid(jid, log):
|
|
||||||
"""Returns the job cache entry for jid, {} while it is still running, or None on error."""
|
|
||||||
cmd = ['salt-run', 'jobs.lookup_jid', jid, '--out=json']
|
|
||||||
try:
|
|
||||||
result = subprocess.run(cmd, check=True, capture_output=True, text=True, timeout=60)
|
|
||||||
return json.loads(result.stdout or '{}')
|
|
||||||
except (subprocess.CalledProcessError, subprocess.TimeoutExpired, ValueError) as exc:
|
|
||||||
log.warning('lookup of jid %s failed: %s', jid, exc)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _minion_failure(minion_ret):
|
|
||||||
if isinstance(minion_ret, dict):
|
|
||||||
return '; '.join(
|
|
||||||
'{}: {}'.format(state.get('__id__', state_key), _trim(state.get('comment', '')))
|
|
||||||
for state_key, state in minion_ret.items()
|
|
||||||
if isinstance(state, dict) and state.get('result') is False
|
|
||||||
)
|
|
||||||
# A state run rejected before it starts (e.g. another state run is in
|
|
||||||
# progress) returns a list of error strings instead of state results.
|
|
||||||
if isinstance(minion_ret, (list, str)):
|
|
||||||
return _trim(minion_ret)
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
def _step_failures(step):
|
|
||||||
if not isinstance(step, dict) or step.get('result') is not False:
|
|
||||||
return []
|
|
||||||
failures = ['{}: {}'.format(step.get('__id__', step.get('name')), _trim(step.get('comment', '')))]
|
|
||||||
changes = step.get('changes')
|
|
||||||
minion_rets = changes.get('ret') if isinstance(changes, dict) else None
|
|
||||||
if isinstance(minion_rets, dict):
|
|
||||||
for minion, minion_ret in minion_rets.items():
|
|
||||||
text = _minion_failure(minion_ret)
|
|
||||||
if text:
|
|
||||||
failures.append('{}: {}'.format(minion, text))
|
|
||||||
return failures
|
|
||||||
|
|
||||||
|
|
||||||
def _orch_failures(ret):
|
|
||||||
if not isinstance(ret, dict):
|
|
||||||
return [_trim(ret)]
|
|
||||||
failures = []
|
|
||||||
for job in ret.values():
|
|
||||||
if not isinstance(job, dict):
|
|
||||||
continue
|
|
||||||
job_ret = job.get('return')
|
|
||||||
data = job_ret.get('data') if isinstance(job_ret, dict) else {}
|
|
||||||
if not isinstance(data, dict):
|
|
||||||
if data:
|
|
||||||
failures.append(_trim(data))
|
|
||||||
data = {}
|
|
||||||
for steps in data.values():
|
|
||||||
if not isinstance(steps, dict):
|
|
||||||
failures.append(_trim(steps))
|
|
||||||
continue
|
|
||||||
for step in steps.values():
|
|
||||||
failures.extend(_step_failures(step))
|
|
||||||
if job.get('success') is False and not failures:
|
|
||||||
failures.append('orchestration reported failure: {}'.format(_trim(job.get('return'))))
|
|
||||||
return failures
|
|
||||||
|
|
||||||
|
|
||||||
def _failed_steps(ret):
|
|
||||||
steps = []
|
|
||||||
for job in ret.values() if isinstance(ret, dict) else []:
|
|
||||||
job_ret = job.get('return') if isinstance(job, dict) else None
|
|
||||||
data = job_ret.get('data') if isinstance(job_ret, dict) else None
|
|
||||||
for group in data.values() if isinstance(data, dict) else []:
|
|
||||||
if isinstance(group, dict):
|
|
||||||
steps.extend(step for step in group.values() if isinstance(step, dict) and step.get('result') is False)
|
|
||||||
return steps
|
|
||||||
|
|
||||||
|
|
||||||
def _result_unknown(ret):
|
|
||||||
# A step that raised (e.g. salt-master restarted while it waited on a queued state run)
|
|
||||||
# never collected the minion's return, so the state run may still have completed.
|
|
||||||
steps = _failed_steps(ret)
|
|
||||||
return bool(steps) and all(str(step.get('comment', '')).startswith(STEP_RAISED) for step in steps)
|
|
||||||
|
|
||||||
|
|
||||||
def _recheck_delay(age):
|
|
||||||
return min(RESULT_RECHECK_MAX, max(RESULT_CHECK_DELAY, age / 4))
|
|
||||||
|
|
||||||
|
|
||||||
def _check_dispatched(log, now):
|
|
||||||
due = []
|
|
||||||
for path in glob.glob(os.path.join(DISPATCHED_DIR, '*.json')):
|
|
||||||
record = _read_intent(path, log)
|
|
||||||
if not isinstance(record, dict) or not record.get('jid'):
|
|
||||||
_unlink(path, log)
|
|
||||||
continue
|
|
||||||
age = now - record.get('dispatched_at', 0)
|
|
||||||
last_check = record.get('checked_at', record.get('dispatched_at', 0))
|
|
||||||
if now - last_check >= _recheck_delay(age):
|
|
||||||
due.append((last_check, path, record, age))
|
|
||||||
# Least recently checked first, so pushes that are still running can't starve finished ones.
|
|
||||||
for _, path, record, age in sorted(due, key=lambda item: item[:2])[:RESULT_CHECKS_PER_PASS]:
|
|
||||||
jid = record['jid']
|
|
||||||
try:
|
|
||||||
if _report_result(record, age, log):
|
|
||||||
_unlink(path, log)
|
|
||||||
else:
|
|
||||||
record['checked_at'] = now
|
|
||||||
_write_record(path, record, log)
|
|
||||||
except Exception:
|
|
||||||
# Drop the record so one unreadable result can't fail every pass ahead of the drain.
|
|
||||||
log.exception('cannot evaluate result for jid=%s; no longer tracking', jid)
|
|
||||||
_unlink(path, log)
|
|
||||||
|
|
||||||
|
|
||||||
def _report_result(record, age, log):
|
|
||||||
"""Logs the outcome of a dispatched push. Returns True once the record is finished with."""
|
|
||||||
jid = record['jid']
|
|
||||||
paths = record.get('paths', [])
|
|
||||||
ret = _lookup_jid(jid, log)
|
|
||||||
if not ret:
|
|
||||||
if age > RESULT_MAX_AGE:
|
|
||||||
log.warning('no result for jid=%s after %ds, no longer tracking; paths=%s', jid, age, paths)
|
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
failures = _orch_failures(ret)
|
log.info('dispatch accepted: %s', (result.stdout or '').strip())
|
||||||
if failures and _result_unknown(ret):
|
|
||||||
log.warning('push result unknown jid=%s paths=%s; the orchestration lost track of the state run, which may '
|
|
||||||
'still have completed (if not, the change will be applied at the next scheduled highstate): %s',
|
|
||||||
jid, paths, ' | '.join(failures))
|
|
||||||
elif failures:
|
|
||||||
log.error('push failed jid=%s paths=%s; change will be applied at the next scheduled highstate: %s',
|
|
||||||
jid, paths, ' | '.join(failures))
|
|
||||||
else:
|
|
||||||
log.info('push succeeded jid=%s paths=%s', jid, paths)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@@ -335,9 +143,6 @@ def main():
|
|||||||
|
|
||||||
debounce_seconds = int(push.get('debounce_seconds', 30))
|
debounce_seconds = int(push.get('debounce_seconds', 30))
|
||||||
|
|
||||||
# Outside the lock: lookups are slow and the reactors take the same lock.
|
|
||||||
_check_dispatched(log, time.time())
|
|
||||||
|
|
||||||
os.makedirs(PENDING_DIR, exist_ok=True)
|
os.makedirs(PENDING_DIR, exist_ok=True)
|
||||||
lock_fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR, 0o644)
|
lock_fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR, 0o644)
|
||||||
try:
|
try:
|
||||||
@@ -403,16 +208,10 @@ def main():
|
|||||||
len(ready), len(deduped), len(combined_actions),
|
len(ready), len(deduped), len(combined_actions),
|
||||||
debounce_duration, all_paths[:20],
|
debounce_duration, all_paths[:20],
|
||||||
)
|
)
|
||||||
for action in deduped:
|
|
||||||
log.info('action: %s tgt=%s', 'highstate' if action.get('highstate') else action.get('state'),
|
|
||||||
action.get('tgt'))
|
|
||||||
|
|
||||||
jid = _dispatch(deduped, log)
|
if not _dispatch(deduped, log):
|
||||||
if jid is None:
|
|
||||||
log.warning('dispatch failed; leaving intent files in place for retry')
|
log.warning('dispatch failed; leaving intent files in place for retry')
|
||||||
return 1
|
return 1
|
||||||
if jid:
|
|
||||||
_record_dispatch(jid, deduped, all_paths[:20], log)
|
|
||||||
|
|
||||||
for path, _ in ready:
|
for path, _ in ready:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,501 +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.
|
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import time
|
|
||||||
import unittest
|
|
||||||
from importlib.machinery import SourceFileLoader
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
SCRIPT = os.path.join(HERE, 'so-push-drainer')
|
|
||||||
_loader = SourceFileLoader('so_push_drainer', SCRIPT)
|
|
||||||
_spec = importlib.util.spec_from_loader('so_push_drainer', _loader)
|
|
||||||
drainer = importlib.util.module_from_spec(_spec)
|
|
||||||
|
|
||||||
# salt is not installed where these tests run; the drainer only needs salt.client.Caller.
|
|
||||||
# Mocked only while the drainer loads: run from the repo root, 'salt' is this repo's salt/ directory.
|
|
||||||
_salt = MagicMock()
|
|
||||||
with patch.dict(sys.modules, {'salt': _salt, 'salt.client': _salt.client}):
|
|
||||||
_loader.exec_module(drainer)
|
|
||||||
|
|
||||||
MASTER = 'manager.localdomain_master'
|
|
||||||
JID = '20260930171554259426'
|
|
||||||
ASYNC_STDERR = ('[WARNING ] Running in asynchronous mode. Results of this execution may be collected '
|
|
||||||
'by attaching to the master event bus or by examining the master job cache, if '
|
|
||||||
'configured. This execution is running under tag salt/run/{}\n'.format(JID))
|
|
||||||
CONFLICT = ('The function "state.sls" is running as PID 372218 and was started at '
|
|
||||||
'2026, Sep 30 17:15:40.466233 with jid 20260930171540466233')
|
|
||||||
|
|
||||||
|
|
||||||
def _orch_ret(steps, success=True):
|
|
||||||
return {MASTER: {
|
|
||||||
'fun': 'runner.state.orchestrate',
|
|
||||||
'jid': JID,
|
|
||||||
'return': {'data': {MASTER: steps}, 'outputter': 'highstate', 'retcode': 0 if success else 1},
|
|
||||||
'success': success,
|
|
||||||
}}
|
|
||||||
|
|
||||||
|
|
||||||
REFRESH_STEP = {
|
|
||||||
'salt_|-refresh_pillar_1_|-saltutil.refresh_pillar_|-function': {
|
|
||||||
'__id__': 'refresh_pillar_1', 'result': True,
|
|
||||||
'changes': {'ret': {'manager_standalone': True}},
|
|
||||||
'comment': 'Function ran successfully.',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
CONFLICT_RET = _orch_ret(dict(REFRESH_STEP, **{
|
|
||||||
'salt_|-apply_soc_1_|-apply_soc_1_|-state': {
|
|
||||||
'__id__': 'apply_soc_1', 'result': False,
|
|
||||||
'changes': {'out': 'highstate', 'ret': {'manager_standalone': [CONFLICT]}},
|
|
||||||
'comment': 'Run failed on minions: manager_standalone',
|
|
||||||
},
|
|
||||||
}), success=False)
|
|
||||||
|
|
||||||
STATE_FAIL_RET = _orch_ret({
|
|
||||||
'salt_|-apply_hydra_1_|-apply_hydra_1_|-state': {
|
|
||||||
'__id__': 'apply_hydra_1', 'result': False,
|
|
||||||
'changes': {'out': 'highstate', 'ret': {'manager_standalone': {
|
|
||||||
'test_|-no_license_|-no_license_|-fail_without_changes': {
|
|
||||||
'__id__': 'hydra.enabled_no_license_detected', 'result': False,
|
|
||||||
'comment': 'This is a feature supported only for customers with a valid license.',
|
|
||||||
},
|
|
||||||
'file_|-hydra_conf_|-/opt/so/conf/hydra_|-managed': {'result': True, 'comment': 'ok'},
|
|
||||||
}}},
|
|
||||||
'comment': 'Run failed on minions: manager_standalone',
|
|
||||||
},
|
|
||||||
}, success=False)
|
|
||||||
|
|
||||||
RAISED = ('An exception occurred in this state: Traceback (most recent call last):\n'
|
|
||||||
' File "salt/client/__init__.py", line 1934, in pub\n'
|
|
||||||
' raise AuthenticationError(err_msg)\n'
|
|
||||||
'salt.exceptions.AuthenticationError: Authentication error occurred.\n')
|
|
||||||
|
|
||||||
RAISED_RET = _orch_ret(dict(REFRESH_STEP, **{
|
|
||||||
'salt_|-apply_hydra_1_|-apply_hydra_1_|-state': {
|
|
||||||
'__id__': 'apply_hydra_1', 'result': False, 'changes': {}, 'comment': RAISED,
|
|
||||||
},
|
|
||||||
}), success=False)
|
|
||||||
|
|
||||||
SUCCESS_RET = _orch_ret(dict(REFRESH_STEP, **{
|
|
||||||
'salt_|-apply_telegraf_1_|-apply_telegraf_1_|-state': {
|
|
||||||
'__id__': 'apply_telegraf_1', 'result': True,
|
|
||||||
'changes': {'out': 'highstate', 'ret': {'manager_standalone': {
|
|
||||||
'file_|-tgrafconf_|-/opt/so/conf/telegraf/etc/telegraf.conf_|-managed': {'result': True},
|
|
||||||
}}},
|
|
||||||
'comment': 'States ran successfully.',
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
|
|
||||||
class DrainerTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.tmpdir = tempfile.mkdtemp()
|
|
||||||
self.pending = os.path.join(self.tmpdir, 'push_pending')
|
|
||||||
self.dispatched = os.path.join(self.tmpdir, 'push_dispatched')
|
|
||||||
os.makedirs(self.pending)
|
|
||||||
for name, value in (
|
|
||||||
('PENDING_DIR', self.pending),
|
|
||||||
('LOCK_FILE', os.path.join(self.pending, '.lock')),
|
|
||||||
('DISPATCHED_DIR', self.dispatched),
|
|
||||||
('LOG_FILE', os.path.join(self.tmpdir, 'log', 'so-push-drainer.log')),
|
|
||||||
):
|
|
||||||
patcher = patch.object(drainer, name, value)
|
|
||||||
patcher.start()
|
|
||||||
self.addCleanup(patcher.stop)
|
|
||||||
self.log = MagicMock()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
shutil.rmtree(self.tmpdir, ignore_errors=True)
|
|
||||||
|
|
||||||
def write_json(self, directory, name, data):
|
|
||||||
os.makedirs(directory, exist_ok=True)
|
|
||||||
path = os.path.join(directory, name)
|
|
||||||
with open(path, 'w') as f:
|
|
||||||
if isinstance(data, str):
|
|
||||||
f.write(data)
|
|
||||||
else:
|
|
||||||
json.dump(data, f)
|
|
||||||
return path
|
|
||||||
|
|
||||||
def logged(self, level):
|
|
||||||
return ' '.join(c.args[0] % c.args[1:] for c in getattr(self.log, level).call_args_list)
|
|
||||||
|
|
||||||
|
|
||||||
class TestHelpers(DrainerTestCase):
|
|
||||||
|
|
||||||
def test_make_logger_adds_handler_once(self):
|
|
||||||
logger = logging.getLogger('so-push-drainer')
|
|
||||||
|
|
||||||
def close_handlers():
|
|
||||||
for handler in logger.handlers:
|
|
||||||
handler.close()
|
|
||||||
logger.handlers.clear()
|
|
||||||
|
|
||||||
self.addCleanup(close_handlers)
|
|
||||||
logger.handlers.clear()
|
|
||||||
self.assertIs(drainer._make_logger(), logger)
|
|
||||||
drainer._make_logger()
|
|
||||||
self.assertEqual(len(logger.handlers), 1)
|
|
||||||
self.assertTrue(os.path.isdir(os.path.dirname(drainer.LOG_FILE)))
|
|
||||||
|
|
||||||
def test_load_push_cfg(self):
|
|
||||||
with patch.object(drainer.salt.client, 'Caller') as caller:
|
|
||||||
caller.return_value.cmd.return_value = {'enabled': False}
|
|
||||||
self.assertEqual(drainer._load_push_cfg(), {'enabled': False})
|
|
||||||
caller.return_value.cmd.return_value = 'garbage'
|
|
||||||
self.assertEqual(drainer._load_push_cfg(), {})
|
|
||||||
|
|
||||||
def test_read_intent(self):
|
|
||||||
good = self.write_json(self.pending, 'good.json', {'a': 1})
|
|
||||||
bad = self.write_json(self.pending, 'bad.json', '{nope')
|
|
||||||
self.assertEqual(drainer._read_intent(good, self.log), {'a': 1})
|
|
||||||
self.assertIsNone(drainer._read_intent(bad, self.log))
|
|
||||||
with patch('builtins.open', side_effect=RuntimeError('boom')):
|
|
||||||
self.assertIsNone(drainer._read_intent(good, self.log))
|
|
||||||
self.log.exception.assert_called_once()
|
|
||||||
|
|
||||||
def test_dedupe_actions(self):
|
|
||||||
actions = [
|
|
||||||
'not a dict',
|
|
||||||
{'state': 'soc'},
|
|
||||||
{'state': 'soc', 'tgt': '*'},
|
|
||||||
{'state': 'soc', 'tgt': '*', 'tgt_type': 'compound'},
|
|
||||||
{'highstate': True, 'tgt': '*'},
|
|
||||||
{'state': 'soc', 'tgt': 'node1', 'tgt_type': 'glob'},
|
|
||||||
]
|
|
||||||
self.assertEqual(drainer._dedupe_actions(actions), [actions[2], actions[4], actions[5]])
|
|
||||||
|
|
||||||
def test_trim(self):
|
|
||||||
self.assertEqual(drainer._trim(' text \n'), 'text')
|
|
||||||
self.assertEqual(drainer._trim(['a']), '["a"]')
|
|
||||||
self.assertEqual(drainer._trim(None), 'null')
|
|
||||||
self.assertEqual(drainer._trim('x' * 600), 'x' * drainer.TEXT_LIMIT + '...')
|
|
||||||
|
|
||||||
def test_trim_traceback(self):
|
|
||||||
comment = ('An exception occurred in this state: Traceback (most recent call last):\n'
|
|
||||||
' File "salt/client/__init__.py", line 1934, in pub\n'
|
|
||||||
' raise AuthenticationError(err_msg)\n'
|
|
||||||
'salt.exceptions.AuthenticationError: Authentication error occurred.\n')
|
|
||||||
self.assertEqual(drainer._trim(comment), 'An exception occurred in this state: '
|
|
||||||
'salt.exceptions.AuthenticationError: Authentication error occurred.')
|
|
||||||
self.assertEqual(drainer._trim('line one\n line two\n'), 'line one line two')
|
|
||||||
|
|
||||||
def test_unlink(self):
|
|
||||||
drainer._unlink(os.path.join(self.tmpdir, 'missing'), self.log)
|
|
||||||
self.log.exception.assert_not_called()
|
|
||||||
drainer._unlink(self.tmpdir, self.log)
|
|
||||||
self.log.exception.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
class TestDispatch(DrainerTestCase):
|
|
||||||
|
|
||||||
def run_dispatch(self, **kwargs):
|
|
||||||
with patch.object(drainer.subprocess, 'run', **kwargs) as run:
|
|
||||||
jid = drainer._dispatch([{'state': 'soc', 'tgt': '*'}], self.log)
|
|
||||||
return jid, run
|
|
||||||
|
|
||||||
def test_jid_parsed_from_stderr(self):
|
|
||||||
jid, run = self.run_dispatch(return_value=MagicMock(stdout='', stderr=ASYNC_STDERR))
|
|
||||||
self.assertEqual(jid, JID)
|
|
||||||
cmd = run.call_args[0][0]
|
|
||||||
self.assertEqual(cmd[:3], ['salt-run', 'state.orchestrate', 'orch.push_batch'])
|
|
||||||
self.assertIn('--async', cmd)
|
|
||||||
|
|
||||||
def test_jid_parsed_from_stdout(self):
|
|
||||||
jid, _ = self.run_dispatch(return_value=MagicMock(stdout=ASYNC_STDERR, stderr=None))
|
|
||||||
self.assertEqual(jid, JID)
|
|
||||||
|
|
||||||
def test_no_jid(self):
|
|
||||||
jid, _ = self.run_dispatch(return_value=MagicMock(stdout='unexpected output', stderr=None))
|
|
||||||
self.assertEqual(jid, '')
|
|
||||||
self.assertIn('output=unexpected output', self.logged('warning'))
|
|
||||||
|
|
||||||
def test_failures_return_none(self):
|
|
||||||
for exc in (subprocess.CalledProcessError(1, 'salt-run', 'out', 'err'),
|
|
||||||
subprocess.TimeoutExpired('salt-run', 60),
|
|
||||||
RuntimeError('boom')):
|
|
||||||
jid, _ = self.run_dispatch(side_effect=exc)
|
|
||||||
self.assertIsNone(jid)
|
|
||||||
|
|
||||||
def test_record_dispatch(self):
|
|
||||||
drainer._record_dispatch(JID, [{'state': 'soc'}], ['audit:soc.config.licenseKey'], self.log)
|
|
||||||
with open(os.path.join(self.dispatched, JID + '.json')) as f:
|
|
||||||
record = json.load(f)
|
|
||||||
self.assertEqual(record['jid'], JID)
|
|
||||||
self.assertEqual(record['paths'], ['audit:soc.config.licenseKey'])
|
|
||||||
self.assertIn('dispatched_at', record)
|
|
||||||
|
|
||||||
def test_record_dispatch_errors(self):
|
|
||||||
with patch.object(drainer.os, 'makedirs', side_effect=OSError('ro')):
|
|
||||||
drainer._record_dispatch(JID, [], [], self.log)
|
|
||||||
drainer._record_dispatch(JID, [object()], [], self.log)
|
|
||||||
self.assertEqual(self.log.exception.call_count, 2)
|
|
||||||
self.assertFalse(os.path.exists(os.path.join(self.dispatched, JID + '.json')))
|
|
||||||
|
|
||||||
|
|
||||||
class TestResults(DrainerTestCase):
|
|
||||||
|
|
||||||
def test_lookup_jid(self):
|
|
||||||
with patch.object(drainer.subprocess, 'run') as run:
|
|
||||||
run.return_value = MagicMock(stdout=json.dumps(SUCCESS_RET))
|
|
||||||
self.assertEqual(drainer._lookup_jid(JID, self.log), SUCCESS_RET)
|
|
||||||
self.assertEqual(run.call_args[0][0], ['salt-run', 'jobs.lookup_jid', JID, '--out=json'])
|
|
||||||
run.return_value = MagicMock(stdout='')
|
|
||||||
self.assertEqual(drainer._lookup_jid(JID, self.log), {})
|
|
||||||
run.return_value = MagicMock(stdout='not json')
|
|
||||||
self.assertIsNone(drainer._lookup_jid(JID, self.log))
|
|
||||||
run.side_effect = subprocess.TimeoutExpired('salt-run', 60)
|
|
||||||
self.assertIsNone(drainer._lookup_jid(JID, self.log))
|
|
||||||
|
|
||||||
def test_minion_failure_shapes(self):
|
|
||||||
self.assertEqual(drainer._minion_failure([CONFLICT]), json.dumps([CONFLICT]))
|
|
||||||
self.assertEqual(drainer._minion_failure('Rendering SLS failed'), 'Rendering SLS failed')
|
|
||||||
self.assertEqual(drainer._minion_failure(True), '')
|
|
||||||
self.assertEqual(drainer._minion_failure({'a': {'result': True}}), '')
|
|
||||||
|
|
||||||
def test_orch_failures_conflict(self):
|
|
||||||
failures = drainer._orch_failures(CONFLICT_RET)
|
|
||||||
self.assertEqual(failures[0], 'apply_soc_1: Run failed on minions: manager_standalone')
|
|
||||||
self.assertIn('manager_standalone', failures[1])
|
|
||||||
self.assertIn('is running as PID 372218', failures[1])
|
|
||||||
self.assertEqual(len(failures), 2)
|
|
||||||
|
|
||||||
def test_orch_failures_failed_state(self):
|
|
||||||
failures = drainer._orch_failures(STATE_FAIL_RET)
|
|
||||||
self.assertEqual(len(failures), 2)
|
|
||||||
self.assertIn('hydra.enabled_no_license_detected: This is a feature', failures[1])
|
|
||||||
self.assertNotIn('hydra_conf', failures[1])
|
|
||||||
|
|
||||||
def test_orch_failures_success(self):
|
|
||||||
self.assertEqual(drainer._orch_failures(SUCCESS_RET), [])
|
|
||||||
|
|
||||||
def test_orch_failures_render_error(self):
|
|
||||||
ret = {MASTER: {'return': {'data': {MASTER: ['Rendering SLS failed']}}, 'success': False}}
|
|
||||||
self.assertEqual(drainer._orch_failures(ret), ['["Rendering SLS failed"]'])
|
|
||||||
|
|
||||||
def test_orch_failures_not_a_dict(self):
|
|
||||||
self.assertEqual(drainer._orch_failures(['No minions matched']), ['["No minions matched"]'])
|
|
||||||
self.assertEqual(drainer._orch_failures('Runner error'), ['Runner error'])
|
|
||||||
|
|
||||||
def test_orch_failures_data_not_a_dict(self):
|
|
||||||
ret = {MASTER: {'return': {'data': ["Rendering SLS 'orch.push_batch' failed"]}, 'success': False}}
|
|
||||||
self.assertEqual(drainer._orch_failures(ret), ['["Rendering SLS \'orch.push_batch\' failed"]'])
|
|
||||||
|
|
||||||
def test_orch_failures_odd_changes(self):
|
|
||||||
for changes in ('Run failed', {'ret': ['manager_standalone']}):
|
|
||||||
ret = _orch_ret({'salt_|-apply_soc_1_|-apply_soc_1_|-state': {
|
|
||||||
'__id__': 'apply_soc_1', 'result': False, 'changes': changes, 'comment': 'Run failed on minions',
|
|
||||||
}}, success=False)
|
|
||||||
self.assertEqual(drainer._orch_failures(ret), ['apply_soc_1: Run failed on minions'])
|
|
||||||
|
|
||||||
def test_orch_failures_unparsed(self):
|
|
||||||
self.assertEqual(drainer._orch_failures({MASTER: 'odd'}), [])
|
|
||||||
ret = {MASTER: {'return': 'Exception occurred', 'success': False}}
|
|
||||||
self.assertEqual(drainer._orch_failures(ret), ['orchestration reported failure: Exception occurred'])
|
|
||||||
|
|
||||||
def test_result_unknown(self):
|
|
||||||
self.assertTrue(drainer._result_unknown(RAISED_RET))
|
|
||||||
for ret in (CONFLICT_RET, STATE_FAIL_RET, SUCCESS_RET, ['No minions matched'], {MASTER: 'odd'},
|
|
||||||
{MASTER: {'return': {'data': {MASTER: ['Rendering SLS failed']}}, 'success': False}}):
|
|
||||||
self.assertFalse(drainer._result_unknown(ret), ret)
|
|
||||||
mixed = _orch_ret(dict(RAISED_RET[MASTER]['return']['data'][MASTER],
|
|
||||||
**CONFLICT_RET[MASTER]['return']['data'][MASTER]), success=False)
|
|
||||||
self.assertFalse(drainer._result_unknown(mixed))
|
|
||||||
|
|
||||||
def record(self, jid, age, now):
|
|
||||||
return self.write_json(self.dispatched, jid + '.json', {
|
|
||||||
'jid': jid, 'dispatched_at': now - age, 'actions': [], 'paths': ['audit:' + jid],
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_check_dispatched(self):
|
|
||||||
now = time.time()
|
|
||||||
results = {
|
|
||||||
'1_failed': CONFLICT_RET,
|
|
||||||
'2_ok': SUCCESS_RET,
|
|
||||||
'3_pending': {},
|
|
||||||
'4_expired': None,
|
|
||||||
'6_unknown': RAISED_RET,
|
|
||||||
}
|
|
||||||
young = self.record('0_young', 5, now)
|
|
||||||
paths = {jid: self.record(jid, 60, now) for jid in results}
|
|
||||||
paths['4_expired'] = self.record('4_expired', drainer.RESULT_MAX_AGE + 1, now)
|
|
||||||
bad = self.write_json(self.dispatched, '5_bad.json', '{nope')
|
|
||||||
with patch.object(drainer, '_lookup_jid', side_effect=lambda jid, log: results[jid]):
|
|
||||||
drainer._check_dispatched(self.log, now)
|
|
||||||
|
|
||||||
self.assertTrue(os.path.exists(young))
|
|
||||||
with open(paths['3_pending']) as f:
|
|
||||||
self.assertEqual(json.load(f)['checked_at'], now)
|
|
||||||
for jid in ('1_failed', '2_ok', '4_expired', '6_unknown'):
|
|
||||||
self.assertFalse(os.path.exists(paths[jid]), jid)
|
|
||||||
self.assertFalse(os.path.exists(bad))
|
|
||||||
self.assertIn('push failed jid=1_failed', self.logged('error'))
|
|
||||||
self.assertIn('is running as PID 372218', self.logged('error'))
|
|
||||||
self.assertIn('push succeeded jid=2_ok', self.logged('info'))
|
|
||||||
self.assertIn('no result for jid=4_expired', self.logged('warning'))
|
|
||||||
self.assertIn('push result unknown jid=6_unknown', self.logged('warning'))
|
|
||||||
self.assertIn('AuthenticationError: Authentication error occurred.', self.logged('warning'))
|
|
||||||
self.assertNotIn('6_unknown', self.logged('error'))
|
|
||||||
|
|
||||||
def test_check_dispatched_survives_bad_result(self):
|
|
||||||
now = time.time()
|
|
||||||
bad = self.record('1_bad', 60, now)
|
|
||||||
good = self.record('2_ok', 60, now)
|
|
||||||
|
|
||||||
def orch_failures(ret):
|
|
||||||
if ret == 'boom':
|
|
||||||
raise ValueError('unexpected shape')
|
|
||||||
return []
|
|
||||||
|
|
||||||
with patch.object(drainer, '_lookup_jid', side_effect=lambda jid, log: 'boom' if jid == '1_bad' else SUCCESS_RET), \
|
|
||||||
patch.object(drainer, '_orch_failures', side_effect=orch_failures):
|
|
||||||
drainer._check_dispatched(self.log, now)
|
|
||||||
self.assertFalse(os.path.exists(bad))
|
|
||||||
self.assertFalse(os.path.exists(good))
|
|
||||||
self.log.exception.assert_called_once()
|
|
||||||
self.assertIn('jid=1_bad', self.log.exception.call_args[0][0] % self.log.exception.call_args[0][1:])
|
|
||||||
self.assertIn('push succeeded jid=2_ok', self.logged('info'))
|
|
||||||
|
|
||||||
def test_recheck_delay(self):
|
|
||||||
self.assertEqual(drainer._recheck_delay(10), drainer.RESULT_CHECK_DELAY)
|
|
||||||
self.assertEqual(drainer._recheck_delay(400), 100)
|
|
||||||
self.assertEqual(drainer._recheck_delay(drainer.RESULT_MAX_AGE), drainer.RESULT_RECHECK_MAX)
|
|
||||||
|
|
||||||
def test_check_dispatched_limit_rotates(self):
|
|
||||||
now = time.time()
|
|
||||||
limit = drainer.RESULT_CHECKS_PER_PASS
|
|
||||||
jids = ['{:02d}'.format(i) for i in range(limit + 2)]
|
|
||||||
for jid in jids:
|
|
||||||
self.record(jid, 60, now)
|
|
||||||
with patch.object(drainer, '_lookup_jid', return_value={}) as lookup:
|
|
||||||
drainer._check_dispatched(self.log, now)
|
|
||||||
self.assertEqual([c.args[0] for c in lookup.call_args_list], jids[:limit])
|
|
||||||
lookup.reset_mock()
|
|
||||||
drainer._check_dispatched(self.log, now + 40)
|
|
||||||
self.assertEqual([c.args[0] for c in lookup.call_args_list], jids[limit:] + jids[:limit - 2])
|
|
||||||
|
|
||||||
def test_check_dispatched_not_blocked_by_running(self):
|
|
||||||
now = time.time()
|
|
||||||
for i in range(drainer.RESULT_CHECKS_PER_PASS):
|
|
||||||
self.record('1_running{}'.format(i), 600, now)
|
|
||||||
done = self.record('2_done', 60, now)
|
|
||||||
|
|
||||||
def lookup(jid, log):
|
|
||||||
return SUCCESS_RET if jid == '2_done' else {}
|
|
||||||
|
|
||||||
with patch.object(drainer, '_lookup_jid', side_effect=lookup) as lookup_jid:
|
|
||||||
drainer._check_dispatched(self.log, now)
|
|
||||||
self.assertTrue(os.path.exists(done))
|
|
||||||
lookup_jid.reset_mock()
|
|
||||||
drainer._check_dispatched(self.log, now + 15)
|
|
||||||
self.assertEqual([c.args[0] for c in lookup_jid.call_args_list], ['2_done'])
|
|
||||||
self.assertFalse(os.path.exists(done))
|
|
||||||
self.assertIn('push succeeded jid=2_done', self.logged('info'))
|
|
||||||
|
|
||||||
|
|
||||||
class TestMain(DrainerTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.cfg = {'enabled': True, 'debounce_seconds': 30}
|
|
||||||
for name, kwargs in (
|
|
||||||
('_make_logger', {'return_value': self.log}),
|
|
||||||
('_load_push_cfg', {'side_effect': lambda: self.cfg}),
|
|
||||||
('_check_dispatched', {}),
|
|
||||||
):
|
|
||||||
patcher = patch.object(drainer, name, **kwargs)
|
|
||||||
setattr(self, name, patcher.start())
|
|
||||||
self.addCleanup(patcher.stop)
|
|
||||||
|
|
||||||
def intent(self, name, age=60, actions=None, paths=None):
|
|
||||||
now = time.time()
|
|
||||||
return self.write_json(self.pending, name, {
|
|
||||||
'first_touch': now - age - 5, 'last_touch': now - age,
|
|
||||||
'actions': [{'state': 'soc', 'tgt': '*'}] if actions is None else actions,
|
|
||||||
'paths': paths or ['audit:soc.config.licenseKey'],
|
|
||||||
})
|
|
||||||
|
|
||||||
def test_no_pending_dir(self):
|
|
||||||
shutil.rmtree(self.pending)
|
|
||||||
self.assertEqual(drainer.main(), 0)
|
|
||||||
self._load_push_cfg.assert_not_called()
|
|
||||||
|
|
||||||
def test_cfg_error(self):
|
|
||||||
self._load_push_cfg.side_effect = RuntimeError('no salt')
|
|
||||||
self.assertEqual(drainer.main(), 1)
|
|
||||||
|
|
||||||
def test_disabled(self):
|
|
||||||
self.cfg['enabled'] = False
|
|
||||||
self.assertEqual(drainer.main(), 0)
|
|
||||||
self._check_dispatched.assert_not_called()
|
|
||||||
|
|
||||||
def test_no_intents_still_checks_results(self):
|
|
||||||
self.assertEqual(drainer.main(), 0)
|
|
||||||
self._check_dispatched.assert_called_once()
|
|
||||||
|
|
||||||
def test_debounce_and_broken(self):
|
|
||||||
young = self.intent('young.json', age=1)
|
|
||||||
broken = self.write_json(self.pending, 'broken.json', '{nope')
|
|
||||||
with patch.object(drainer, '_dispatch') as dispatch:
|
|
||||||
self.assertEqual(drainer.main(), 0)
|
|
||||||
dispatch.assert_not_called()
|
|
||||||
self.assertTrue(os.path.exists(young))
|
|
||||||
self.assertFalse(os.path.exists(broken))
|
|
||||||
|
|
||||||
def test_broken_unlink_error_ignored(self):
|
|
||||||
self.write_json(self.pending, 'broken.json', '{nope')
|
|
||||||
with patch.object(drainer.os, 'unlink', side_effect=OSError('busy')):
|
|
||||||
self.assertEqual(drainer.main(), 0)
|
|
||||||
|
|
||||||
def test_no_usable_actions(self):
|
|
||||||
path = self.intent('empty.json', actions=[{'state': 'soc'}])
|
|
||||||
self.assertEqual(drainer.main(), 0)
|
|
||||||
self.assertFalse(os.path.exists(path))
|
|
||||||
self.intent('empty.json', actions=[{'state': 'soc'}])
|
|
||||||
with patch.object(drainer.os, 'unlink', side_effect=OSError('busy')):
|
|
||||||
self.assertEqual(drainer.main(), 0)
|
|
||||||
|
|
||||||
def test_dispatch_failure_keeps_intents(self):
|
|
||||||
path = self.intent('pillar_soc.json')
|
|
||||||
with patch.object(drainer, '_dispatch', return_value=None):
|
|
||||||
self.assertEqual(drainer.main(), 1)
|
|
||||||
self.assertTrue(os.path.exists(path))
|
|
||||||
|
|
||||||
def test_dispatch_records_jid(self):
|
|
||||||
soc = self.intent('pillar_soc.json')
|
|
||||||
hs = self.intent('pillar_global.json', actions=[{'highstate': True, 'tgt': '*'}], paths=['audit:global.x'])
|
|
||||||
with patch.object(drainer, '_dispatch', return_value=JID) as dispatch, \
|
|
||||||
patch.object(drainer, '_record_dispatch') as record:
|
|
||||||
self.assertEqual(drainer.main(), 0)
|
|
||||||
self.assertEqual(len(dispatch.call_args[0][0]), 2)
|
|
||||||
record.assert_called_once()
|
|
||||||
self.assertEqual(record.call_args[0][0], JID)
|
|
||||||
self.assertEqual(sorted(record.call_args[0][2]), ['audit:global.x', 'audit:soc.config.licenseKey'])
|
|
||||||
self.assertFalse(os.path.exists(soc))
|
|
||||||
self.assertFalse(os.path.exists(hs))
|
|
||||||
self.assertIn('action: highstate tgt=*', self.logged('info'))
|
|
||||||
|
|
||||||
def test_dispatch_without_jid_not_recorded(self):
|
|
||||||
self.intent('pillar_soc.json')
|
|
||||||
with patch.object(drainer, '_dispatch', return_value=''), \
|
|
||||||
patch.object(drainer, '_record_dispatch') as record, \
|
|
||||||
patch.object(drainer.os, 'unlink', side_effect=OSError('busy')):
|
|
||||||
self.assertEqual(drainer.main(), 0)
|
|
||||||
record.assert_not_called()
|
|
||||||
self.log.exception.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
@@ -124,8 +124,8 @@ copy_new_files() {
|
|||||||
|
|
||||||
rsync -a salt $default_salt_dir/
|
rsync -a salt $default_salt_dir/
|
||||||
rsync -a pillar $default_salt_dir/
|
rsync -a pillar $default_salt_dir/
|
||||||
chown -R root:root $default_salt_dir/salt
|
chown -R socore:socore $default_salt_dir/salt
|
||||||
chown -R root:root $default_salt_dir/pillar
|
chown -R socore:socore $default_salt_dir/pillar
|
||||||
chmod 755 $default_salt_dir/pillar/firewall/addfirewall.sh
|
chmod 755 $default_salt_dir/pillar/firewall/addfirewall.sh
|
||||||
|
|
||||||
rm -rf /tmp/sogh
|
rm -rf /tmp/sogh
|
||||||
|
|||||||
@@ -129,8 +129,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
kratosContainer=${KRATOS_CONTAINER:-so-kratos}
|
kratosUrl=${KRATOS_URL:-http://127.0.0.1:4434/admin}
|
||||||
kratosUrl=${KRATOS_URL:-http://localhost:4434/admin}
|
|
||||||
databasePath=${KRATOS_DB_PATH:-/nsm/kratos/db/db.sqlite}
|
databasePath=${KRATOS_DB_PATH:-/nsm/kratos/db/db.sqlite}
|
||||||
databaseTimeout=${KRATOS_DB_TIMEOUT:-5000}
|
databaseTimeout=${KRATOS_DB_TIMEOUT:-5000}
|
||||||
bcryptRounds=${BCRYPT_ROUNDS:-12}
|
bcryptRounds=${BCRYPT_ROUNDS:-12}
|
||||||
@@ -155,10 +154,6 @@ function fail() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function kratosCurl() {
|
|
||||||
docker exec "$kratosContainer" curl "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
function require() {
|
function require() {
|
||||||
cmd=$1
|
cmd=$1
|
||||||
which "$1" 2>&1 > /dev/null
|
which "$1" 2>&1 > /dev/null
|
||||||
@@ -169,18 +164,18 @@ function require() {
|
|||||||
function verifyEnvironment() {
|
function verifyEnvironment() {
|
||||||
require "htpasswd"
|
require "htpasswd"
|
||||||
require "jq"
|
require "jq"
|
||||||
require "docker"
|
require "curl"
|
||||||
require "openssl"
|
require "openssl"
|
||||||
require "sqlite3"
|
require "sqlite3"
|
||||||
[[ ! -f $databasePath ]] && fail "Unable to find database file; specify path via KRATOS_DB_PATH environment variable"
|
[[ ! -f $databasePath ]] && fail "Unable to find database file; specify path via KRATOS_DB_PATH environment variable"
|
||||||
response=$(kratosCurl -Ss -L ${kratosUrl}/)
|
response=$(curl -Ss -L ${kratosUrl}/)
|
||||||
[[ "$response" != "404 page not found" ]] && fail "Unable to communicate with Kratos; specify URL via KRATOS_URL environment variable"
|
[[ "$response" != "404 page not found" ]] && fail "Unable to communicate with Kratos; specify URL via KRATOS_URL environment variable"
|
||||||
}
|
}
|
||||||
|
|
||||||
function findIdByEmail() {
|
function findIdByEmail() {
|
||||||
email=${1,,}
|
email=${1,,}
|
||||||
|
|
||||||
response=$(kratosCurl -Ss -L ${kratosUrl}/identities)
|
response=$(curl -Ss -L ${kratosUrl}/identities)
|
||||||
identityId=$(echo "${response}" | jq -r ".[] | select(.verifiable_addresses[0].value == \"$email\") | .id")
|
identityId=$(echo "${response}" | jq -r ".[] | select(.verifiable_addresses[0].value == \"$email\") | .id")
|
||||||
echo $identityId
|
echo $identityId
|
||||||
}
|
}
|
||||||
@@ -421,7 +416,7 @@ function syncAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listUsers() {
|
function listUsers() {
|
||||||
response=$(kratosCurl -Ss -L ${kratosUrl}/identities)
|
response=$(curl -Ss -L ${kratosUrl}/identities)
|
||||||
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
||||||
|
|
||||||
users=$(echo "${response}" | jq -r ".[] | .verifiable_addresses[0].value" | sort)
|
users=$(echo "${response}" | jq -r ".[] | .verifiable_addresses[0].value" | sort)
|
||||||
@@ -500,7 +495,7 @@ function createUser() {
|
|||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
response=$(kratosCurl -Ss -L ${kratosUrl}/identities -d "$addUserJson")
|
response=$(curl -Ss -L ${kratosUrl}/identities -d "$addUserJson")
|
||||||
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
||||||
|
|
||||||
identityId=$(echo "${response}" | jq -r ".id")
|
identityId=$(echo "${response}" | jq -r ".id")
|
||||||
@@ -523,7 +518,7 @@ function updateStatus() {
|
|||||||
identityId=$(findIdByEmail "$email")
|
identityId=$(findIdByEmail "$email")
|
||||||
[[ ${identityId} == "" ]] && fail "User not found"
|
[[ ${identityId} == "" ]] && fail "User not found"
|
||||||
|
|
||||||
response=$(kratosCurl -Ss -L "${kratosUrl}/identities/$identityId")
|
response=$(curl -Ss -L "${kratosUrl}/identities/$identityId")
|
||||||
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
||||||
|
|
||||||
schemaId=$(echo "$response" | jq -r .schema_id)
|
schemaId=$(echo "$response" | jq -r .schema_id)
|
||||||
@@ -536,7 +531,7 @@ function updateStatus() {
|
|||||||
state="inactive"
|
state="inactive"
|
||||||
fi
|
fi
|
||||||
body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }"
|
body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }"
|
||||||
response=$(kratosCurl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
response=$(curl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
||||||
[[ $? != 0 ]] && fail "Unable to update user"
|
[[ $? != 0 ]] && fail "Unable to update user"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +550,7 @@ function updateUserProfile() {
|
|||||||
identityId=$(findIdByEmail "$email")
|
identityId=$(findIdByEmail "$email")
|
||||||
[[ ${identityId} == "" ]] && fail "User not found"
|
[[ ${identityId} == "" ]] && fail "User not found"
|
||||||
|
|
||||||
response=$(kratosCurl -Ss -L "${kratosUrl}/identities/$identityId")
|
response=$(curl -Ss -L "${kratosUrl}/identities/$identityId")
|
||||||
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
||||||
|
|
||||||
schemaId=$(echo "$response" | jq -r .schema_id)
|
schemaId=$(echo "$response" | jq -r .schema_id)
|
||||||
@@ -564,7 +559,7 @@ function updateUserProfile() {
|
|||||||
traitBlock="{\"email\":\"$email\",\"firstName\":\"$firstName\",\"lastName\":\"$lastName\",\"note\":\"$note\"}"
|
traitBlock="{\"email\":\"$email\",\"firstName\":\"$firstName\",\"lastName\":\"$lastName\",\"note\":\"$note\"}"
|
||||||
|
|
||||||
body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }"
|
body="{ \"schema_id\": \"$schemaId\", \"state\": \"$state\", \"traits\": $traitBlock }"
|
||||||
response=$(kratosCurl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
response=$(curl -fSsL -XPUT -H "Content-Type: application/json" "${kratosUrl}/identities/$identityId" -d "$body")
|
||||||
[[ $? != 0 ]] && fail "Unable to update user"
|
[[ $? != 0 ]] && fail "Unable to update user"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,7 +569,7 @@ function deleteUser() {
|
|||||||
identityId=$(findIdByEmail "$email")
|
identityId=$(findIdByEmail "$email")
|
||||||
[[ ${identityId} == "" ]] && fail "User not found"
|
[[ ${identityId} == "" ]] && fail "User not found"
|
||||||
|
|
||||||
response=$(kratosCurl -Ss -XDELETE -L "${kratosUrl}/identities/$identityId")
|
response=$(curl -Ss -XDELETE -L "${kratosUrl}/identities/$identityId")
|
||||||
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
[[ $? != 0 ]] && fail "Unable to communicate with Kratos"
|
||||||
|
|
||||||
rolesTmpFile="${socRolesFile}.tmp"
|
rolesTmpFile="${socRolesFile}.tmp"
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ def loadYaml(filename):
|
|||||||
try:
|
try:
|
||||||
with open(filename, "r") as file:
|
with open(filename, "r") as file:
|
||||||
content = file.read()
|
content = file.read()
|
||||||
loaded = yaml.safe_load(content)
|
return yaml.safe_load(content)
|
||||||
return loaded if loaded is not None else {}
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"File not found: {filename}", file=sys.stderr)
|
print(f"File not found: {filename}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -95,20 +95,6 @@ class TestRemove(unittest.TestCase):
|
|||||||
expected = "key1:\n child1: 123\n child2:\n deep2: ab\nkey2: false\n"
|
expected = "key1:\n child1: 123\n child2:\n deep2: ab\nkey2: false\n"
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_remove_empty_file(self):
|
|
||||||
filename = "/tmp/so-yaml_test-remove-empty.yaml"
|
|
||||||
file = open(filename, "w")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
code = soyaml.remove([filename, "key1"])
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
|
|
||||||
file = open(filename, "r")
|
|
||||||
actual = file.read()
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
self.assertEqual(actual, "{}\n")
|
|
||||||
|
|
||||||
def test_remove_missing_args(self):
|
def test_remove_missing_args(self):
|
||||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||||
@@ -308,36 +294,6 @@ class TestRemove(unittest.TestCase):
|
|||||||
expected = "key1:\n child1: 123\n child2:\n deep1: 45\n deep2: d\nkey2: false\nkey3:\n- e\n- f\n- g\n"
|
expected = "key1:\n child1: 123\n child2:\n deep1: 45\n deep2: d\nkey2: false\nkey3:\n- e\n- f\n- g\n"
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_add_empty_file(self):
|
|
||||||
filename = "/tmp/so-yaml_test-add-empty.yaml"
|
|
||||||
file = open(filename, "w")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
code = soyaml.add([filename, "telegraf.output", "BOTH"])
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
|
|
||||||
file = open(filename, "r")
|
|
||||||
actual = file.read()
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
expected = "telegraf:\n output: BOTH\n"
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
def test_add_empty_file_simple(self):
|
|
||||||
filename = "/tmp/so-yaml_test-add-empty-simple.yaml"
|
|
||||||
file = open(filename, "w")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
code = soyaml.add([filename, "telegraf", "BOTH"])
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
|
|
||||||
file = open(filename, "r")
|
|
||||||
actual = file.read()
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
expected = "telegraf: BOTH\n"
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
def test_replace_missing_arg(self):
|
def test_replace_missing_arg(self):
|
||||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||||
@@ -390,21 +346,6 @@ class TestRemove(unittest.TestCase):
|
|||||||
expected = "key1:\n child1: 123\n child2:\n deep1: 46\nkey2: false\nkey3:\n- e\n- f\n- g\n"
|
expected = "key1:\n child1: 123\n child2:\n deep1: 46\nkey2: false\nkey3:\n- e\n- f\n- g\n"
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
def test_replace_empty_file(self):
|
|
||||||
filename = "/tmp/so-yaml_test-replace-empty.yaml"
|
|
||||||
file = open(filename, "w")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
code = soyaml.replace([filename, "telegraf.output", "BOTH"])
|
|
||||||
self.assertEqual(code, 0)
|
|
||||||
|
|
||||||
file = open(filename, "r")
|
|
||||||
actual = file.read()
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
expected = "telegraf:\n output: BOTH\n"
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
def test_convert(self):
|
def test_convert(self):
|
||||||
self.assertEqual(soyaml.convertType("foo"), "foo")
|
self.assertEqual(soyaml.convertType("foo"), "foo")
|
||||||
self.assertEqual(soyaml.convertType("foo.bar"), "foo.bar")
|
self.assertEqual(soyaml.convertType("foo.bar"), "foo.bar")
|
||||||
@@ -565,18 +506,6 @@ class TestRemove(unittest.TestCase):
|
|||||||
self.assertEqual(result, 2)
|
self.assertEqual(result, 2)
|
||||||
self.assertEqual("", mock_stdout.getvalue())
|
self.assertEqual("", mock_stdout.getvalue())
|
||||||
|
|
||||||
def test_get_empty_file(self):
|
|
||||||
with patch('sys.stdout', new=StringIO()) as mock_stdout:
|
|
||||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
|
||||||
filename = "/tmp/so-yaml_test-get-empty.yaml"
|
|
||||||
file = open(filename, "w")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
result = soyaml.get([filename, "telegraf.output"])
|
|
||||||
self.assertEqual(result, 2)
|
|
||||||
self.assertEqual("", mock_stdout.getvalue())
|
|
||||||
self.assertIn("Key 'telegraf.output' not found by so-yaml.py", mock_stderr.getvalue())
|
|
||||||
|
|
||||||
def test_get_usage(self):
|
def test_get_usage(self):
|
||||||
with patch('sys.exit', new=MagicMock()) as sysmock:
|
with patch('sys.exit', new=MagicMock()) as sysmock:
|
||||||
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
with patch('sys.stderr', new=StringIO()) as mock_stderr:
|
||||||
@@ -1062,29 +991,3 @@ class TestLoadYaml(unittest.TestCase):
|
|||||||
soyaml.loadYaml("/tmp/so-yaml_test-unreadable.yaml")
|
soyaml.loadYaml("/tmp/so-yaml_test-unreadable.yaml")
|
||||||
sysmock.assert_called_with(1)
|
sysmock.assert_called_with(1)
|
||||||
self.assertIn("Error reading file", mock_stderr.getvalue())
|
self.assertIn("Error reading file", mock_stderr.getvalue())
|
||||||
|
|
||||||
def test_load_yaml_empty_file(self):
|
|
||||||
filename = "/tmp/so-yaml_test-load-empty.yaml"
|
|
||||||
file = open(filename, "w")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
result = soyaml.loadYaml(filename)
|
|
||||||
self.assertEqual(result, {})
|
|
||||||
|
|
||||||
def test_load_yaml_whitespace_only(self):
|
|
||||||
filename = "/tmp/so-yaml_test-load-whitespace.yaml"
|
|
||||||
file = open(filename, "w")
|
|
||||||
file.write(" \n\n \n")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
result = soyaml.loadYaml(filename)
|
|
||||||
self.assertEqual(result, {})
|
|
||||||
|
|
||||||
def test_load_yaml_comments_only(self):
|
|
||||||
filename = "/tmp/so-yaml_test-load-comments.yaml"
|
|
||||||
file = open(filename, "w")
|
|
||||||
file.write("# Just a comment\n# Another comment\n")
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
result = soyaml.loadYaml(filename)
|
|
||||||
self.assertEqual(result, {})
|
|
||||||
+104
-121
@@ -28,7 +28,6 @@ INSTALLEDSALTVERSION=$(salt --versions-report | grep Salt: | awk '{print $2}')
|
|||||||
# percentage like "25%"). Empty means so-soup-grid-highstate uses the salt:auto_apply:batch
|
# percentage like "25%"). Empty means so-soup-grid-highstate uses the salt:auto_apply:batch
|
||||||
# pillar default.
|
# pillar default.
|
||||||
BATCHSIZE=
|
BATCHSIZE=
|
||||||
DEFAULT_DOCKER_RANGE='172.17.1.0/24'
|
|
||||||
SOUP_LOG=/root/soup.log
|
SOUP_LOG=/root/soup.log
|
||||||
SOUP_DEBUG_LOG=/root/soup-debug.log
|
SOUP_DEBUG_LOG=/root/soup-debug.log
|
||||||
WHATWOULDYOUSAYYAHDOHERE=soup
|
WHATWOULDYOUSAYYAHDOHERE=soup
|
||||||
@@ -121,6 +120,9 @@ check_err() {
|
|||||||
161)
|
161)
|
||||||
echo 'Required intermediate Elasticsearch upgrade not complete'
|
echo 'Required intermediate Elasticsearch upgrade not complete'
|
||||||
;;
|
;;
|
||||||
|
162)
|
||||||
|
echo 'One or more Elastic Agent nodes do not support the x86-64-v3 CPU instruction set'
|
||||||
|
;;
|
||||||
170)
|
170)
|
||||||
echo "Intermediate upgrade completed successfully to $next_step_so_version, but next soup to Security Onion $originally_requested_so_version could not be started automatically."
|
echo "Intermediate upgrade completed successfully to $next_step_so_version, but next soup to Security Onion $originally_requested_so_version could not be started automatically."
|
||||||
echo "Start soup again manually to continue the upgrade to Security Onion $originally_requested_so_version."
|
echo "Start soup again manually to continue the upgrade to Security Onion $originally_requested_so_version."
|
||||||
@@ -348,6 +350,83 @@ check_cluster_health() {
|
|||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
no_soup_for_you() {
|
||||||
|
echo ""
|
||||||
|
echo "No soup for you!"
|
||||||
|
exit 162
|
||||||
|
}
|
||||||
|
|
||||||
|
check_cpu_compatibility() {
|
||||||
|
# Roles running a container built from the so-elastic-agent image; mirrors the
|
||||||
|
# elasticagent and elasticfleet entries in salt/reactor/pillar_push_map.yaml.
|
||||||
|
local cpu_target='G@role:so-heavynode or G@role:so-eval or G@role:so-fleet or G@role:so-import or G@role:so-manager or G@role:so-managerhype or G@role:so-managersearch or G@role:so-standalone'
|
||||||
|
local expected_nodes cpu_results node result confirm
|
||||||
|
local -a unsupported=() offline=()
|
||||||
|
|
||||||
|
echo "Checking that Elastic Agent nodes support the x86-64-v3 CPU instruction set now required by Elastic."
|
||||||
|
|
||||||
|
if [[ "$SKIP_CPU_CHECK" == "true" ]]; then
|
||||||
|
printf "\nSkipping the x86-64-v3 CPU check because --skip-cpu-check was specified.\n\n"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Nodes that never answer are absent from the results, so diff against who should have.
|
||||||
|
expected_nodes=$(salt -C "$cpu_target" --preview-target --out=json 2>/dev/null | jq -r '.[]?') || true
|
||||||
|
if [[ -z "$expected_nodes" ]]; then
|
||||||
|
printf "\nCould not determine which nodes run the Elastic Agent, so the x86-64-v3 CPU check cannot run.\n"
|
||||||
|
no_soup_for_you
|
||||||
|
fi
|
||||||
|
|
||||||
|
cpu_results=$(salt -t 30 -C "$cpu_target" cmd.run "/lib64/ld-linux-x86-64.so.2 --help | grep x86-64-v3" --out=json 2>/dev/null) || true
|
||||||
|
|
||||||
|
while IFS= read -r node; do
|
||||||
|
[[ -z "$node" ]] && continue
|
||||||
|
result=$(jq -r --arg node "$node" '.[$node] // empty' <<< "$cpu_results" 2>/dev/null)
|
||||||
|
if [[ -z "$result" || "$result" == *"did not return"* ]]; then
|
||||||
|
offline+=("$node")
|
||||||
|
elif [[ "$result" != *"x86-64-v3 (supported"* ]]; then
|
||||||
|
# glibc appends "(supported, searched)" only when supported; the open paren keeps
|
||||||
|
# this from matching a future "(unsupported".
|
||||||
|
unsupported+=("$node")
|
||||||
|
fi
|
||||||
|
done <<< "$expected_nodes"
|
||||||
|
|
||||||
|
if [[ ${#unsupported[@]} -eq 0 && ${#offline[@]} -eq 0 ]]; then
|
||||||
|
printf "\nAll Elastic Agent nodes support x86-64-v3. We can proceed with SOUP.\n\n"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [[ ${#unsupported[@]} -gt 0 ]]; then
|
||||||
|
echo "The following node(s) do NOT support the x86-64-v3 CPU instruction set:"
|
||||||
|
printf ' %s\n' "${unsupported[@]}"
|
||||||
|
echo ""
|
||||||
|
echo "Upstream Elastic now builds its binaries for x86-64-v3, so these nodes can no"
|
||||||
|
echo "longer run Elastic. Upgrading them WILL BREAK them."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
if [[ ${#offline[@]} -gt 0 ]]; then
|
||||||
|
echo "The following node(s) did not respond and could not be checked:"
|
||||||
|
printf ' %s\n' "${offline[@]}"
|
||||||
|
echo ""
|
||||||
|
echo "These nodes are offline, so we cannot confirm they support x86-64-v3, which"
|
||||||
|
echo "upstream Elastic now requires."
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $UNATTENDED ]]; then
|
||||||
|
echo "Unattended mode cannot prompt for an override. Re-run soup interactively, or pass --skip-cpu-check to bypass this check."
|
||||||
|
no_soup_for_you
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -rp "Type 'override' to continue anyway, or press Enter to exit: " confirm
|
||||||
|
if [[ "${confirm,,}" == "override" ]]; then
|
||||||
|
printf "\nOverride accepted. Continuing at your own risk.\n\n"
|
||||||
|
else
|
||||||
|
no_soup_for_you
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
check_fleet_server() {
|
check_fleet_server() {
|
||||||
echo "Checking that Elastic Fleet Server is responding."
|
echo "Checking that Elastic Fleet Server is responding."
|
||||||
# Modeled on the wait_for_so-elastic-fleet state check in elasticfleet/enabled.sls,
|
# Modeled on the wait_for_so-elastic-fleet state check in elasticfleet/enabled.sls,
|
||||||
@@ -526,7 +605,6 @@ preupgrade_changes() {
|
|||||||
[[ "$INSTALLEDVERSION" == "3.0.0" ]] && up_to_3.1.0
|
[[ "$INSTALLEDVERSION" == "3.0.0" ]] && up_to_3.1.0
|
||||||
[[ "$INSTALLEDVERSION" == "3.1.0" ]] && up_to_3.2.0
|
[[ "$INSTALLEDVERSION" == "3.1.0" ]] && up_to_3.2.0
|
||||||
[[ "$INSTALLEDVERSION" == "3.2.0" ]] && up_to_3.3.0
|
[[ "$INSTALLEDVERSION" == "3.2.0" ]] && up_to_3.3.0
|
||||||
[[ "$INSTALLEDVERSION" == "3.3.0" ]] && up_to_3.4.0
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,7 +623,6 @@ postupgrade_changes() {
|
|||||||
[[ "$POSTVERSION" == "3.0.0" ]] && post_to_3.1.0
|
[[ "$POSTVERSION" == "3.0.0" ]] && post_to_3.1.0
|
||||||
[[ "$POSTVERSION" == "3.1.0" ]] && post_to_3.2.0
|
[[ "$POSTVERSION" == "3.1.0" ]] && post_to_3.2.0
|
||||||
[[ "$POSTVERSION" == "3.2.0" ]] && post_to_3.3.0
|
[[ "$POSTVERSION" == "3.2.0" ]] && post_to_3.3.0
|
||||||
[[ "$POSTVERSION" == "3.3.0" ]] && post_to_3.4.0
|
|
||||||
# All applicable post-upgrade steps completed; clear the resume marker.
|
# All applicable post-upgrade steps completed; clear the resume marker.
|
||||||
rm -f "$POSTVERSION_FILE"
|
rm -f "$POSTVERSION_FILE"
|
||||||
true
|
true
|
||||||
@@ -1051,6 +1128,9 @@ post_to_3.2.0() {
|
|||||||
|
|
||||||
### 3.3.0 Scripts ###
|
### 3.3.0 Scripts ###
|
||||||
up_to_3.3.0() {
|
up_to_3.3.0() {
|
||||||
|
# download 9.4.5 elastic agent packages
|
||||||
|
determine_elastic_agent_upgrade
|
||||||
|
|
||||||
# remove existing (patched) elasticsearch index template to match integration naming change
|
# remove existing (patched) elasticsearch index template to match integration naming change
|
||||||
if ! remove_elasticsearch_index_template "so-logs-sentinel_one_cloud_funnel.login" "sentinel_one_cloud_funnel.login changed to sentinel_one_cloud_funnel.logins"; then
|
if ! remove_elasticsearch_index_template "so-logs-sentinel_one_cloud_funnel.login" "sentinel_one_cloud_funnel.login changed to sentinel_one_cloud_funnel.logins"; then
|
||||||
FINAL_MESSAGE_QUEUE+=("WARNING: Unable to automatically remove the so-logs-sentinel_one_cloud_funnel.login index template. This step can be performed manually using the following command:")
|
FINAL_MESSAGE_QUEUE+=("WARNING: Unable to automatically remove the so-logs-sentinel_one_cloud_funnel.login index template. This step can be performed manually using the following command:")
|
||||||
@@ -1083,125 +1163,16 @@ post_to_3.3.0() {
|
|||||||
# Recollate again since some internal DBs were excluded during 3.2.0 soup
|
# Recollate again since some internal DBs were excluded during 3.2.0 soup
|
||||||
recollate_postgres
|
recollate_postgres
|
||||||
|
|
||||||
|
# Generate 9.4.5 elastic agent installers
|
||||||
|
echo "Regenerating Elastic Agent Installers"
|
||||||
|
/sbin/so-elastic-agent-gen-installers
|
||||||
|
|
||||||
telegraf_repair
|
telegraf_repair
|
||||||
|
|
||||||
set_postversion 3.3.0
|
set_postversion 3.3.0
|
||||||
}
|
}
|
||||||
### 3.3.0 End ###
|
### 3.3.0 End ###
|
||||||
|
|
||||||
### 3.4.0 Scripts ###
|
|
||||||
up_to_3.4.0() {
|
|
||||||
# download 9.4.8 elastic agent packages
|
|
||||||
determine_elastic_agent_upgrade
|
|
||||||
|
|
||||||
set_soauth_range
|
|
||||||
|
|
||||||
echo "Removing so-kratos, so-hydra and so-soc so they are recreated on the soauth network."
|
|
||||||
docker rm -f so-kratos so-hydra so-soc >> $SOUP_LOG 2>&1
|
|
||||||
|
|
||||||
for template in so-metrics-logstash.node so-metrics-logstash.stack_monitoring.node; do
|
|
||||||
if ! remove_elasticsearch_index_template "$template" "logstash node and node_cel index patterns reversed"; then
|
|
||||||
FINAL_MESSAGE_QUEUE+=("WARNING: Unable to automatically remove the $template index template. Addon integration templates may fail to load until it is removed:")
|
|
||||||
FINAL_MESSAGE_QUEUE+=(" - sudo so-elasticsearch-query _index_template/$template -XDELETE && so-checkin")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
INSTALLEDVERSION=3.4.0
|
|
||||||
}
|
|
||||||
|
|
||||||
set_soauth_range() {
|
|
||||||
local pillar_file=/opt/so/saltstack/local/pillar/docker/soc_docker.sls
|
|
||||||
local current_range suggested authnet authgw input
|
|
||||||
|
|
||||||
[[ -f "$pillar_file" ]] || return 0
|
|
||||||
|
|
||||||
current_range=$(so-yaml.py get -r "$pillar_file" docker.range 2>/dev/null) || return 0
|
|
||||||
|
|
||||||
# A default range gets the 172.17.2.0/24 from docker/defaults.yaml, same as a fresh
|
|
||||||
# install, so there is nothing to ask about.
|
|
||||||
[[ -n "$current_range" && "$current_range" != "$DEFAULT_DOCKER_RANGE" ]] || return 0
|
|
||||||
|
|
||||||
if so-yaml.py get -r "$pillar_file" docker.networks.soauth.range >/dev/null 2>&1; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
suggested=$(echo "${current_range%%/*}" | awk -F'.' '{ printf "%s.%s.%s.%s", $1, $2, ($3 + 1) % 256, $4 }')
|
|
||||||
|
|
||||||
if [[ -z $UNATTENDED ]]; then
|
|
||||||
echo ""
|
|
||||||
echo "This grid uses a custom Docker range ($current_range). The authentication"
|
|
||||||
echo "services are moving to their own isolated network, which needs a second /24"
|
|
||||||
echo "that does not overlap it."
|
|
||||||
echo ""
|
|
||||||
while :; do
|
|
||||||
read -rp "Enter the network without the /24 suffix, or press Enter for ${suggested}: " input
|
|
||||||
[[ -z "$input" ]] && input="$suggested"
|
|
||||||
if valid_soauth_range "$input" "$current_range"; then
|
|
||||||
authnet="$input"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
echo "That range must be a valid IPv4 network, must not be within 172.17.0.0/24, and must not overlap ${current_range}."
|
|
||||||
done
|
|
||||||
else
|
|
||||||
if ! valid_soauth_range "$suggested" "$current_range"; then
|
|
||||||
FINAL_MESSAGE_QUEUE+=("WARNING: Unable to pick a range for the authentication network alongside $current_range. Set it manually before the next highstate:")
|
|
||||||
FINAL_MESSAGE_QUEUE+=(" - so-yaml.py add $pillar_file docker.networks.soauth.range <network>/24")
|
|
||||||
FINAL_MESSAGE_QUEUE+=(" - so-yaml.py add $pillar_file docker.networks.soauth.gateway <gateway>")
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
authnet="$suggested"
|
|
||||||
FINAL_MESSAGE_QUEUE+=("NOTE: The authentication services moved to an isolated Docker network and were assigned ${authnet}/24.")
|
|
||||||
FINAL_MESSAGE_QUEUE+=(" - If that conflicts with your environment, update docker.networks.soauth in $pillar_file and run so-checkin.")
|
|
||||||
fi
|
|
||||||
|
|
||||||
authgw=$(echo "$authnet" | awk -F'.' '{print $1,$2,$3,1}' OFS='.')
|
|
||||||
|
|
||||||
echo "Assigning the authentication network the range ${authnet}/24."
|
|
||||||
so-yaml.py add "$pillar_file" docker.networks.soauth.range "${authnet}/24" >> $SOUP_LOG 2>&1
|
|
||||||
so-yaml.py add "$pillar_file" docker.networks.soauth.gateway "$authgw" >> $SOUP_LOG 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
valid_soauth_range() {
|
|
||||||
local candidate=$1 docker_range=$2
|
|
||||||
|
|
||||||
valid_ip4 "$candidate" || return 1
|
|
||||||
[[ $candidate =~ ^172\.17\.0\. ]] && return 1
|
|
||||||
[[ "${candidate}/24" == "$docker_range" ]] && return 1
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
post_to_3.4.0() {
|
|
||||||
# Generate 9.4.8 elastic agent installers
|
|
||||||
echo "Regenerating Elastic Agent Installers"
|
|
||||||
/sbin/so-elastic-agent-gen-installers
|
|
||||||
|
|
||||||
for idx in "metrics-logstash.node-default" "metrics-logstash.stack_monitoring.node-default"; do
|
|
||||||
rollover_index "$idx"
|
|
||||||
done
|
|
||||||
|
|
||||||
add_detection_runtime_fields
|
|
||||||
|
|
||||||
remove_frozen_ca_bundles
|
|
||||||
|
|
||||||
set_postversion 3.4.0
|
|
||||||
}
|
|
||||||
|
|
||||||
remove_frozen_ca_bundles() {
|
|
||||||
rm -f /opt/so/saltstack/local/salt/elasticsearch/cacerts /opt/so/saltstack/local/salt/elasticsearch/tls-ca-bundle.pem >> $SOUP_LOG 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
add_detection_runtime_fields() {
|
|
||||||
local template=$DEFAULT_SALT_DIR/salt/elasticsearch/templates/component/so/detection-mappings.json
|
|
||||||
local runtime
|
|
||||||
|
|
||||||
runtime=$(jq -c '{runtime: .template.mappings.runtime}' "$template")
|
|
||||||
if ! so-elasticsearch-query so-detection/_mapping -XPUT -d "$runtime" --retry 3 --retry-delay 5 --fail >> $SOUP_LOG 2>&1; then
|
|
||||||
FINAL_MESSAGE_QUEUE+=("WARNING: Unable to add the Sigma search fields to the so-detection index. Searches on so_detection.sigmaKind will match nothing until they are added:")
|
|
||||||
FINAL_MESSAGE_QUEUE+=(" - sudo so-elasticsearch-query so-detection/_mapping -XPUT -d \"\$(jq -c '{runtime: .template.mappings.runtime}' $template)\"")
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
### 3.4.0 End ###
|
|
||||||
|
|
||||||
|
|
||||||
repo_sync() {
|
repo_sync() {
|
||||||
echo "Sync the local repo."
|
echo "Sync the local repo."
|
||||||
@@ -1474,10 +1445,9 @@ verify_es_version_compatibility() {
|
|||||||
["8.18.4"]="8.18.6 8.18.8 9.0.8"
|
["8.18.4"]="8.18.6 8.18.8 9.0.8"
|
||||||
["8.18.6"]="8.18.8 9.0.8"
|
["8.18.6"]="8.18.8 9.0.8"
|
||||||
["8.18.8"]="9.0.8"
|
["8.18.8"]="9.0.8"
|
||||||
["9.0.8"]="9.3.3 9.3.7 9.4.5 9.4.8"
|
["9.0.8"]="9.3.3 9.3.7 9.4.5"
|
||||||
["9.3.3"]="9.3.7 9.4.5 9.4.8"
|
["9.3.3"]="9.3.7 9.4.5"
|
||||||
["9.3.7"]="9.4.5 9.4.8"
|
["9.3.7"]="9.4.5"
|
||||||
["9.4.5"]="9.4.8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Elasticsearch MUST upgrade through these versions
|
# Elasticsearch MUST upgrade through these versions
|
||||||
@@ -2088,6 +2058,8 @@ main() {
|
|||||||
echo "Let's see if we need to update Security Onion."
|
echo "Let's see if we need to update Security Onion."
|
||||||
upgrade_check
|
upgrade_check
|
||||||
|
|
||||||
|
check_cpu_compatibility
|
||||||
|
|
||||||
upgrade_space
|
upgrade_space
|
||||||
|
|
||||||
echo "Verifying Elasticsearch version compatibility across the grid before upgrading."
|
echo "Verifying Elasticsearch version compatibility across the grid before upgrading."
|
||||||
@@ -2366,6 +2338,17 @@ fi
|
|||||||
echo "### soup has been served at $(date) ###"
|
echo "### soup has been served at $(date) ###"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SKIP_CPU_CHECK=false
|
||||||
|
declare -a SOUP_ARGS=()
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [[ "$arg" == "--skip-cpu-check" ]]; then
|
||||||
|
SKIP_CPU_CHECK=true
|
||||||
|
else
|
||||||
|
SOUP_ARGS+=("$arg")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
set -- "${SOUP_ARGS[@]}"
|
||||||
|
|
||||||
while getopts ":b:f:y" opt; do
|
while getopts ":b:f:y" opt; do
|
||||||
case ${opt} in
|
case ${opt} in
|
||||||
b )
|
b )
|
||||||
@@ -2389,7 +2372,7 @@ while getopts ":b:f:y" opt; do
|
|||||||
ISOLOC="$OPTARG"
|
ISOLOC="$OPTARG"
|
||||||
;;
|
;;
|
||||||
\? )
|
\? )
|
||||||
echo "Usage: soup [-b] [-y] [-f <iso location>]"
|
echo "Usage: soup [-b] [-y] [-f <iso location>] [--skip-cpu-check]"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
: )
|
: )
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ nginx_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://nginx/tools/sbin
|
- source: salt://nginx/tools/sbin
|
||||||
- user: root
|
- user: 939
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
#nginx_sbin_jinja:
|
#nginx_sbin_jinja:
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% from 'nginx/map.jinja' import NGINXMERGED %}
|
{% from 'nginx/map.jinja' import NGINXMERGED %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- nginx.ssl
|
- nginx.ssl
|
||||||
- nginx.config
|
- nginx.config
|
||||||
- nginx.sostatus
|
- nginx.sostatus
|
||||||
@@ -49,7 +47,6 @@ so-nginx:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.pem }}:/etc/ssl/certs/ca-certificates.crt:ro
|
|
||||||
- /opt/so/conf/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
- /opt/so/conf/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
- /opt/so/log/nginx/:/var/log/nginx:rw
|
- /opt/so/log/nginx/:/var/log/nginx:rw
|
||||||
- /opt/so/tmp/nginx/:/var/lib/nginx:rw
|
- /opt/so/tmp/nginx/:/var/lib/nginx:rw
|
||||||
@@ -92,7 +89,6 @@ so-nginx:
|
|||||||
- {{ BINDING }}
|
- {{ BINDING }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: nginxconf
|
- file: nginxconf
|
||||||
- file: nginxconfdir
|
- file: nginxconfdir
|
||||||
{% if GLOBALS.is_manager %}
|
{% if GLOBALS.is_manager %}
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ http {
|
|||||||
ssl_prefer_server_ciphers on;
|
ssl_prefer_server_ciphers on;
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
|
||||||
location ~* (^/login|^/login/.*|^/js/.*|^/css/.*|^/images/.*|^/pages/.*|^/docs/.*) {
|
location ~* (^/login/.*|^/js/.*|^/css/.*|^/images/.*|^/pages/.*|^/docs/.*) {
|
||||||
proxy_pass http://{{ GLOBALS.manager }}:9822;
|
proxy_pass http://{{ GLOBALS.manager }}:9822;
|
||||||
proxy_read_timeout 90;
|
proxy_read_timeout 90;
|
||||||
proxy_connect_timeout 90;
|
proxy_connect_timeout 90;
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
{% set BATCH = AUTOAPPLY.batch %}
|
{% set BATCH = AUTOAPPLY.batch %}
|
||||||
{% set BATCH_WAIT = AUTOAPPLY.batch_wait %}
|
{% set BATCH_WAIT = AUTOAPPLY.batch_wait %}
|
||||||
|
|
||||||
{# queue must be a top-level salt.state arg (kwarg is ignored); an int is max_queue and still fails on conflict #}
|
|
||||||
|
|
||||||
{% for action in actions %}
|
{% for action in actions %}
|
||||||
{% if action.get('highstate') %}
|
{% if action.get('highstate') %}
|
||||||
apply_highstate_{{ loop.index }}:
|
apply_highstate_{{ loop.index }}:
|
||||||
@@ -14,7 +12,8 @@ apply_highstate_{{ loop.index }}:
|
|||||||
- highstate: True
|
- highstate: True
|
||||||
- batch: {{ action.get('batch', BATCH) }}
|
- batch: {{ action.get('batch', BATCH) }}
|
||||||
- batch_wait: {{ action.get('batch_wait', BATCH_WAIT) }}
|
- batch_wait: {{ action.get('batch_wait', BATCH_WAIT) }}
|
||||||
- queue: True
|
- kwarg:
|
||||||
|
queue: 2
|
||||||
{% else %}
|
{% else %}
|
||||||
refresh_pillar_{{ loop.index }}:
|
refresh_pillar_{{ loop.index }}:
|
||||||
salt.function:
|
salt.function:
|
||||||
@@ -30,7 +29,8 @@ apply_{{ action.state | replace('.', '_') }}_{{ loop.index }}:
|
|||||||
- {{ action.state }}
|
- {{ action.state }}
|
||||||
- batch: {{ action.get('batch', BATCH) }}
|
- batch: {{ action.get('batch', BATCH) }}
|
||||||
- batch_wait: {{ action.get('batch_wait', BATCH_WAIT) }}
|
- batch_wait: {{ action.get('batch_wait', BATCH_WAIT) }}
|
||||||
- queue: True
|
- kwarg:
|
||||||
|
queue: 2
|
||||||
- require:
|
- require:
|
||||||
- salt: refresh_pillar_{{ loop.index }}
|
- salt: refresh_pillar_{{ loop.index }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -6,12 +6,10 @@
|
|||||||
{% from 'allowed_states.map.jinja' import allowed_states %}
|
{% from 'allowed_states.map.jinja' import allowed_states %}
|
||||||
{% if sls.split('.')[0] in allowed_states %}
|
{% if sls.split('.')[0] in allowed_states %}
|
||||||
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
{% from 'vars/globals.map.jinja' import GLOBALS %}
|
||||||
{% from 'ca/trust.map.jinja' import CATRUST %}
|
|
||||||
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
{% from 'docker/docker.map.jinja' import DOCKERMERGED %}
|
||||||
{% set SO_POSTGRES_USER = salt['pillar.get']('postgres:auth:users:so_postgres_user:user', 'so_postgres') %}
|
{% set SO_POSTGRES_USER = salt['pillar.get']('postgres:auth:users:so_postgres_user:user', 'so_postgres') %}
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- ca.trust
|
|
||||||
- postgres.auth
|
- postgres.auth
|
||||||
- postgres.ssl
|
- postgres.ssl
|
||||||
- postgres.config
|
- postgres.config
|
||||||
@@ -44,7 +42,6 @@ so-postgres:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- binds:
|
- binds:
|
||||||
- {{ CATRUST.dir }}
|
|
||||||
- /opt/so/log/postgres/:/log:rw
|
- /opt/so/log/postgres/:/log:rw
|
||||||
- /nsm/postgres:/var/lib/postgresql/data:rw
|
- /nsm/postgres:/var/lib/postgresql/data:rw
|
||||||
- /opt/so/conf/postgres/postgresql.conf:/conf/postgresql.conf:ro
|
- /opt/so/conf/postgres/postgresql.conf:/conf/postgresql.conf:ro
|
||||||
@@ -72,7 +69,6 @@ so-postgres:
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
- watch:
|
- watch:
|
||||||
- cmd: ca_trust_extract
|
|
||||||
- file: postgresconf
|
- file: postgresconf
|
||||||
- file: postgreshba
|
- file: postgreshba
|
||||||
- file: postgresinitdb
|
- file: postgresinitdb
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ def run():
|
|||||||
# top level so the reactor is robust to either shape.
|
# top level so the reactor is robust to either shape.
|
||||||
event = data.get('data', data) # noqa: F821 -- data provided by reactor
|
event = data.get('data', data) # noqa: F821 -- data provided by reactor
|
||||||
setting_id = event.get('setting_id', '')
|
setting_id = event.get('setting_id', '')
|
||||||
audit_id = event.get('id')
|
|
||||||
node_id = (event.get('node_id') or '').strip()
|
node_id = (event.get('node_id') or '').strip()
|
||||||
|
|
||||||
app = _app_from_setting(setting_id)
|
app = _app_from_setting(setting_id)
|
||||||
@@ -151,8 +150,8 @@ def run():
|
|||||||
if not entry:
|
if not entry:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
'push_pillar: app "%s" is not in pillar_push_map.yaml; change will be '
|
'push_pillar: app "%s" is not in pillar_push_map.yaml; change will be '
|
||||||
'picked up at the next scheduled highstate (setting_id=%s audit_id=%s)',
|
'picked up at the next scheduled highstate (setting_id=%s)',
|
||||||
app, setting_id, audit_id,
|
app, setting_id,
|
||||||
)
|
)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@@ -166,12 +165,12 @@ def run():
|
|||||||
'node_{}_{}'.format(node_id, app), actions,
|
'node_{}_{}'.format(node_id, app), actions,
|
||||||
'audit:{}@{}'.format(setting_id, node_id),
|
'audit:{}@{}'.format(setting_id, node_id),
|
||||||
)
|
)
|
||||||
LOG.info('push_pillar: per-node intent updated for %s on %s (setting_id=%s audit_id=%s)',
|
LOG.info('push_pillar: per-node intent updated for %s on %s (setting_id=%s)',
|
||||||
app, node_id, setting_id, audit_id)
|
app, node_id, setting_id)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Branch B: grid-wide app change -> use the map entry's actions as-is.
|
# Branch B: grid-wide app change -> use the map entry's actions as-is.
|
||||||
actions = list(entry) # copy to avoid mutating the cache
|
actions = list(entry) # copy to avoid mutating the cache
|
||||||
_write_intent('pillar_{}'.format(app), actions, 'audit:{}'.format(setting_id))
|
_write_intent('pillar_{}'.format(app), actions, 'audit:{}'.format(setting_id))
|
||||||
LOG.info('push_pillar: app intent updated for %s (setting_id=%s audit_id=%s)', app, setting_id, audit_id)
|
LOG.info('push_pillar: app intent updated for %s (setting_id=%s)', app, setting_id)
|
||||||
return {}
|
return {}
|
||||||
@@ -50,16 +50,16 @@ redis_sbin:
|
|||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://redis/tools/sbin
|
- source: salt://redis/tools/sbin
|
||||||
- user: root
|
- user: 939
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
|
|
||||||
redis_sbin_jinja:
|
redis_sbin_jinja:
|
||||||
file.recurse:
|
file.recurse:
|
||||||
- name: /usr/sbin
|
- name: /usr/sbin
|
||||||
- source: salt://redis/tools/sbin_jinja
|
- source: salt://redis/tools/sbin_jinja
|
||||||
- user: root
|
- user: 939
|
||||||
- group: root
|
- group: 939
|
||||||
- file_mode: 755
|
- file_mode: 755
|
||||||
- template: jinja
|
- template: jinja
|
||||||
|
|
||||||
|
|||||||
Loaded 100 of 325 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user