Skip to content

Commit e19d665

Browse files
authored
Merge pull request #90 from splunk/pydantic2
We're happy to announce the merge for contentctl 4.0! ![image](https://github.com/splunk/contentctl/assets/87383215/5152e9d2-3ce9-4570-b429-150dcadd3bcc)
2 parents b14038b + dee4853 commit e19d665

File tree

141 files changed

+6069
-5542
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

141 files changed

+6069
-5542
lines changed

.github/workflows/testEndToEnd.yml

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,22 @@ jobs:
1111
strategy:
1212
fail-fast: false
1313
matrix:
14-
python_version: ["3.9", "3.10", "3.11"]
15-
operating_system: ["ubuntu-20.04", "ubuntu-22.04"]
14+
python_version: ["3.11", "3.12"]
15+
operating_system: ["ubuntu-20.04", "ubuntu-22.04", "macos-latest", "macos-14"]
1616
#operating_system: ["ubuntu-20.04", "ubuntu-22.04", "macos-latest"]
1717

1818

1919
runs-on: ${{ matrix.operating_system }}
2020
steps:
21-
- name: Install Docker for macOS
22-
run: |
23-
brew install docker
24-
#import magic fails on macos runner
25-
brew install libmagic
26-
colima start
27-
# Mapping below is required to get the Python docker library working
28-
sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock
29-
if: matrix.operating_system == 'macos-latest'
21+
#- name: Install Docker for macOS
22+
# run: |
23+
# brew install docker
24+
# # import magic fails on macos runner
25+
# brew install libmagic colima
26+
# colima start
27+
# # Mapping below is required to get the Python docker library working
28+
# sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock
29+
# if: matrix.operating_system == 'macos-latest'
3030

3131
#Checkout the current branch
3232
- name: Checkout repo
@@ -51,7 +51,12 @@ jobs:
5151
- name: Run contentctl init
5252
run: |
5353
cd my_splunk_content_pack
54-
poetry run contentctl init
54+
poetry run contentctl init
55+
56+
- name: Clone the AtomicRedTeam Repo
57+
run: |
58+
cd my_splunk_content_pack
59+
git clone --depth 1 https://github.com/redcanaryco/atomic-red-team
5560
5661
- name: Run contentctl validate
5762
run: |
@@ -65,9 +70,10 @@ jobs:
6570
6671
#Do not pause on a failed detection
6772
- name: Run contentctl test
73+
if: startsWith(matrix.operating_system, 'ubuntu')
6874
run: |
6975
cd my_splunk_content_pack
70-
poetry run contentctl test --unattended
76+
poetry run contentctl test --disable-tqdm --post-test-behavior never_pause
7177
7278
- uses: actions/upload-artifact@v4
7379
with:

.vscode/launch.json

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,68 @@
11
{
2-
// Use IntelliSense to learn about possible attributes.
3-
// Hover to view descriptions of existing attributes.
4-
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5-
"version": "0.2.0",
6-
"configurations": [
7-
8-
{
9-
"name": "contentctl test",
10-
"type": "python",
11-
"request": "launch",
12-
"program": "${workspaceFolder}/splunk_contentctl/contentctl.py",
13-
"cwd": "${workspaceFolder}/splunk_contentctl",
14-
"console": "integratedTerminal",
15-
"justMyCode": true,
16-
"args": ["-p", "tmp", "test"]
17-
}
18-
]
2+
"configurations": [
3+
{
4+
"name": "contentctl init",
5+
"type": "debugpy",
6+
"request": "launch",
7+
"program": "${workspaceFolder}/.venv/bin/contentctl",
8+
"cwd": "${workspaceFolder}/../ddd/",
9+
"args": ["init"]
10+
},
11+
{
12+
"name": "contentctl validate",
13+
"type": "debugpy",
14+
"request": "launch",
15+
"program": "${workspaceFolder}/.venv/bin/contentctl",
16+
"cwd": "${workspaceFolder}/../",
17+
"args": ["validate"]
18+
},
19+
{
20+
"name": "contentctl validate enrich",
21+
"type": "debugpy",
22+
"request": "launch",
23+
"program": "${workspaceFolder}/.venv/bin/contentctl",
24+
"cwd": "${workspaceFolder}/../",
25+
"args": ["validate", "--enrichments"]
26+
},
27+
{
28+
"name": "contentctl build",
29+
"type": "debugpy",
30+
"request": "launch",
31+
"program": "${workspaceFolder}/.venv/bin/contentctl",
32+
"cwd": "${workspaceFolder}/../",
33+
"args": ["build"]
34+
},
35+
{
36+
"name": "contentctl build enrich",
37+
"type": "debugpy",
38+
"request": "launch",
39+
"program": "${workspaceFolder}/.venv/bin/contentctl",
40+
"cwd": "${workspaceFolder}/../",
41+
"args": ["build", "--enrichments"]
42+
},
43+
{
44+
"name": "contentctl test",
45+
"type": "debugpy",
46+
"request": "launch",
47+
"program": "${workspaceFolder}/.venv/bin/contentctl",
48+
"cwd": "${workspaceFolder}/../",
49+
"args": ["test"]
50+
},
51+
{
52+
"name": "contentctl --help",
53+
"type": "debugpy",
54+
"request": "launch",
55+
"program": "${workspaceFolder}/.venv/bin/contentctl",
56+
"cwd": "${workspaceFolder}/../",
57+
"args": ["--help"]
58+
},
59+
{
60+
"name": "contentctl test detection",
61+
"type": "debugpy",
62+
"request": "launch",
63+
"program": "${workspaceFolder}/.venv/bin/contentctl",
64+
"cwd": "${workspaceFolder}/../",
65+
"args": ["test", "mode:selected", "--mode.files", "detections/endpoint/3cx_supply_chain_attack_network_indicators.yml"]
66+
}
67+
]
1968
}

.vscode/settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
22
"python.terminal.activateEnvironment": true,
33
"python.envFile": "${workspaceFolder}/.env",
4-
"python.testing.cwd": "${workspaceFolder}"
4+
"python.testing.cwd": "${workspaceFolder}",
5+
"python.languageServer": "Pylance",
6+
"python.analysis.typeCheckingMode": "strict"
7+
8+
59
}

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,28 @@ contentctl test's default mode allows it to quickly test all content with requir
178178
6. **docs** - Create documentation as Markdown
179179
7. **reporting** - Create different reporting files such as a Mitre ATT&CK overlay
180180

181+
# Shell tab-complete
181182

183+
Leveraging the tab completion featureset of the CLI library we're using, you can generate tab completions for `contentctl` automatically, for zsh, bash, and tcsh. For additional details, you can view the docs for the library [here.](https://brentyi.github.io/tyro/tab_completion/)
184+
185+
### Zsh
186+
If you already have a location for your ZSH tab completions, you only need to run the generation line and can skip the folder creation, configuring the rest to fit with your shell config.
187+
188+
```zsh
189+
mkdir -p ~/.zfunc
190+
contentctl --tyro-write-completion zsh ~/.zfunc/_contentctl
191+
echo "fpath+=~/.zfunc" >> ~/.zshrc
192+
echo "autoload -Uz compinit && compinit" >> ~/.zshrc
193+
source ~/.zshrc
194+
```
195+
196+
### Bash
197+
198+
```bash
199+
completion_dir=${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion}/completions/
200+
mkdir -p $completion_dir
201+
contentctl --tyro-write-completion bash ${completion_dir}/_contentctl
202+
```
182203

183204
# Acronyms
184205
| Acronym | Meaning| Description |

contentctl/actions/build.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import sys
2+
import shutil
3+
import os
4+
5+
from dataclasses import dataclass
6+
7+
from contentctl.objects.enums import SecurityContentProduct, SecurityContentType
8+
from contentctl.input.director import Director, DirectorOutputDto
9+
from contentctl.output.conf_output import ConfOutput
10+
from contentctl.output.conf_writer import ConfWriter
11+
from contentctl.output.ba_yml_output import BAYmlOutput
12+
from contentctl.output.api_json_output import ApiJsonOutput
13+
import pathlib
14+
import json
15+
import datetime
16+
from typing import Union
17+
18+
from contentctl.objects.config import build
19+
20+
@dataclass(frozen=True)
21+
class BuildInputDto:
22+
director_output_dto: DirectorOutputDto
23+
config:build
24+
25+
26+
class Build:
27+
28+
29+
30+
def execute(self, input_dto: BuildInputDto) -> DirectorOutputDto:
31+
if input_dto.config.build_app:
32+
updated_conf_files:set[pathlib.Path] = set()
33+
conf_output = ConfOutput(input_dto.config)
34+
updated_conf_files.update(conf_output.writeHeaders())
35+
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.detections, SecurityContentType.detections))
36+
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.stories, SecurityContentType.stories))
37+
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.baselines, SecurityContentType.baselines))
38+
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.investigations, SecurityContentType.investigations))
39+
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.lookups, SecurityContentType.lookups))
40+
updated_conf_files.update(conf_output.writeObjects(input_dto.director_output_dto.macros, SecurityContentType.macros))
41+
updated_conf_files.update(conf_output.writeAppConf())
42+
43+
#Ensure that the conf file we just generated/update is syntactically valid
44+
for conf_file in updated_conf_files:
45+
ConfWriter.validateConfFile(conf_file)
46+
47+
conf_output.packageApp()
48+
49+
print(f"Build of '{input_dto.config.app.title}' APP successful to {input_dto.config.getPackageFilePath()}")
50+
51+
52+
if input_dto.config.build_api:
53+
shutil.rmtree(input_dto.config.getAPIPath(), ignore_errors=True)
54+
input_dto.config.getAPIPath().mkdir(parents=True)
55+
api_json_output = ApiJsonOutput()
56+
for output_objects, output_type in [(input_dto.director_output_dto.detections, SecurityContentType.detections),
57+
(input_dto.director_output_dto.stories, SecurityContentType.stories),
58+
(input_dto.director_output_dto.baselines, SecurityContentType.baselines),
59+
(input_dto.director_output_dto.investigations, SecurityContentType.investigations),
60+
(input_dto.director_output_dto.lookups, SecurityContentType.lookups),
61+
(input_dto.director_output_dto.macros, SecurityContentType.macros),
62+
(input_dto.director_output_dto.deployments, SecurityContentType.deployments)]:
63+
api_json_output.writeObjects(output_objects, input_dto.config.getAPIPath(), input_dto.config.app.label, output_type )
64+
65+
66+
67+
#create version file for sse api
68+
version_file = input_dto.config.getAPIPath()/"version.json"
69+
utc_time = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0,tzinfo=None).isoformat()
70+
version_dict = {"version":{"name":f"v{input_dto.config.app.version}","published_at": f"{utc_time}Z" }}
71+
with open(version_file,"w") as version_f:
72+
json.dump(version_dict,version_f)
73+
74+
print(f"Build of '{input_dto.config.app.title}' API successful to {input_dto.config.getAPIPath()}")
75+
76+
if input_dto.config.build_ssa:
77+
78+
srs_path = input_dto.config.getSSAPath() / 'srs'
79+
complex_path = input_dto.config.getSSAPath() / 'complex'
80+
shutil.rmtree(srs_path, ignore_errors=True)
81+
shutil.rmtree(complex_path, ignore_errors=True)
82+
srs_path.mkdir(parents=True)
83+
complex_path.mkdir(parents=True)
84+
ba_yml_output = BAYmlOutput()
85+
ba_yml_output.writeObjects(input_dto.director_output_dto.ssa_detections, str(input_dto.config.getSSAPath()))
86+
87+
print(f"Build of 'SSA' successful to {input_dto.config.getSSAPath()}")
88+
89+
return input_dto.director_output_dto

0 commit comments

Comments
 (0)