mirror of
https://github.com/splunk/security_content
synced 2026-06-08 17:32:49 +00:00
rebase with ssa end to end testing
This commit is contained in:
@@ -1,7 +1,17 @@
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
K8S_PLAYGROUND_ROLE: k8s:v1/app-play1/namespace/threat-research/role/deployer
|
||||
K8S_STAGING_ROLE: k8s:v1/app-stage1/namespace/tr-st-deploy/role/deployer
|
||||
DOCKER_ROLE: docker:v1/registry/prefix/threat-research
|
||||
K8_DEPLOYER_CONTAINER: docker.repo.splunkdev.net/threat-research/k8-deployer
|
||||
SMOKETEST_RUNNER: docker.repo.splunkdev.net/threat-research/smoketest-runner
|
||||
SRCBRANCH: $CI_COMMIT_REF_NAME
|
||||
|
||||
stages:
|
||||
- publish_smoketest_runner
|
||||
- publish_deployer
|
||||
- smoketest_staging
|
||||
- smoketest_play
|
||||
- ssa-validate
|
||||
|
||||
validate:
|
||||
@@ -14,3 +24,57 @@ validate:
|
||||
- python3 bin/ssa_validate.py --skip-error detections/*/*
|
||||
- python3 bin/ssa_test.py --skip-error tests/*/*
|
||||
- python3 bin/testing_coverage.py --type streaming --min-coverage 1.0
|
||||
|
||||
publish_deployer:
|
||||
stage: publish_deployer
|
||||
image: docker.repo.splunkdev.net/ci-cd/ci-container:alpine-3.11
|
||||
before_script:
|
||||
- apk add --update docker
|
||||
script:
|
||||
- eval $(go-go vault -a ${DOCKER_ROLE})
|
||||
- cd bin/ssa-end-to-end-testing/k8s-deployer
|
||||
- docker build . -t ${K8_DEPLOYER_CONTAINER}:${CI_COMMIT_SHORT_SHA}
|
||||
- docker push ${K8_DEPLOYER_CONTAINER}:${CI_COMMIT_SHORT_SHA}
|
||||
|
||||
publish_smoketest_runner:
|
||||
stage: publish_smoketest_runner
|
||||
image: docker.repo.splunkdev.net/ci-cd/ci-container:alpine-3.11
|
||||
before_script:
|
||||
- apk add --update docker
|
||||
script:
|
||||
- eval $(go-go vault -a ${DOCKER_ROLE})
|
||||
- docker build bin/ssa-end-to-end-testing/smoke-test-runner -t ${SMOKETEST_RUNNER}:${CI_COMMIT_SHORT_SHA} --build-arg SRCBRANCH=$CI_COMMIT_REF_NAME
|
||||
- docker push ${SMOKETEST_RUNNER}:${CI_COMMIT_SHORT_SHA}
|
||||
|
||||
smoketest_play:
|
||||
stage: smoketest_play
|
||||
dependencies:
|
||||
- publish_smoketest_runner
|
||||
- publish_deployer
|
||||
image: ${K8_DEPLOYER_CONTAINER}:${CI_COMMIT_SHORT_SHA}
|
||||
allow_failure: true
|
||||
script:
|
||||
- eval $(go-go vault -a ${K8S_PLAYGROUND_ROLE})
|
||||
- eval $(go-go vault -a ${DOCKER_ROLE})
|
||||
- cd /deployer/k8s
|
||||
- ./deployer.sh
|
||||
- ./check_deploy_status.sh
|
||||
variables:
|
||||
SCSENV: app_play1
|
||||
SMOKETEST_RUNNER_IMAGE: ${SMOKETEST_RUNNER}:${CI_COMMIT_SHORT_SHA}
|
||||
|
||||
smoketest_staging:
|
||||
stage: smoketest_staging
|
||||
dependencies:
|
||||
- publish_smoketest_runner
|
||||
- publish_deployer
|
||||
image: ${K8_DEPLOYER_CONTAINER}:${CI_COMMIT_SHORT_SHA}
|
||||
script:
|
||||
- eval $(go-go vault -a ${K8S_STAGING_ROLE})
|
||||
- eval $(go-go vault -a ${DOCKER_ROLE})
|
||||
- cd /deployer/k8s
|
||||
- ./deployer.sh
|
||||
- ./check_deploy_status.sh
|
||||
variables:
|
||||
SCSENV: app_staging1
|
||||
SMOKETEST_RUNNER_IMAGE: ${SMOKETEST_RUNNER}:${CI_COMMIT_SHORT_SHA}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
ansible==2.10.7
|
||||
ansible==3.0.0
|
||||
ansible-runner==1.4.7
|
||||
apipkg==1.5
|
||||
aspy.yaml==1.3.0
|
||||
@@ -6,22 +6,22 @@ atomicwrites==1.4.0
|
||||
attackcti==0.3.4.3
|
||||
attrs==20.3.0
|
||||
azure-common==1.1.26
|
||||
azure-core==1.10.0
|
||||
azure-core==1.11.0
|
||||
azure-identity==1.5.0
|
||||
azure-mgmt-compute==18.2.0
|
||||
azure-mgmt-core==1.2.2
|
||||
azure-mgmt-network==17.1.0
|
||||
azure-mgmt-resource==15.0.0
|
||||
azure-mgmt-resource==16.0.0
|
||||
bcrypt==3.2.0
|
||||
boto3==1.16.61
|
||||
botocore==1.19.63
|
||||
boto3==1.16.62
|
||||
botocore==1.20.0
|
||||
certifi==2020.12.5
|
||||
cffi==1.14.5
|
||||
cfgv==2.0.1
|
||||
chardet==4.0.0
|
||||
configparser==5.0.2
|
||||
contextlib2==0.6.0.post1
|
||||
cryptography==3.3.2
|
||||
cryptography==3.4.4
|
||||
Deprecated==1.2.11
|
||||
dnspython==2.1.0
|
||||
docutils==0.16
|
||||
@@ -36,7 +36,7 @@ jmespath==0.10.0
|
||||
lockfile==0.12.2
|
||||
MarkupSafe==1.1.1
|
||||
mock==4.0.3
|
||||
more-itertools==8.6.0
|
||||
more-itertools==8.7.0
|
||||
mysql-connector-python==8.0.23
|
||||
nodeenv==1.3.4
|
||||
ntlm-auth==1.5.0
|
||||
@@ -47,7 +47,7 @@ path==15.1.2
|
||||
path.py==12.5.0
|
||||
pexpect==4.8.0
|
||||
pluggy==0.13.1
|
||||
pre-commit==2.9.3
|
||||
pre-commit==2.10.0
|
||||
protobuf==3.15.1
|
||||
psutil==5.8.0
|
||||
ptyprocess==0.7.0
|
||||
|
||||
@@ -145,11 +145,11 @@ search = {{ baseline.search }}
|
||||
|
||||
{% for response_task in response_tasks %}
|
||||
{% if response_task.search is defined %}
|
||||
[{{ response_task.type | default('ESCU') }} - {{ response_task.name }} - Response Task]
|
||||
[ESCU - {{ response_task.name }} - Response Task]
|
||||
action.escu = 0
|
||||
action.escu.enabled = 1
|
||||
action.escu.search_type = investigative
|
||||
action.escu.full_search_name = {{ response_task.type | default('ESCU') }} - {{ response_task.name }} - Response Task
|
||||
action.escu.full_search_name = ESCU - {{ response_task.name }} - Response Task
|
||||
description = {{ response_task.description }}
|
||||
action.escu.creation_date = {{ response_task.date }}
|
||||
action.escu.modification_date = {{ response_task.date }}
|
||||
|
||||
@@ -34,4 +34,14 @@ def build_humvee(path):
|
||||
log(logging.DEBUG, "Humvee details", detail=latest_humvee_object)
|
||||
urllib.request.urlretrieve(latest_humvee_object['downloadUri'], humvee_path)
|
||||
else:
|
||||
log(logging.DEBUG, "Already latest checksum %s" % humvee_md5, detail=latest_humvee_object)
|
||||
log(logging.DEBUG, "Already latest checksum %s" % humvee_md5, detail=latest_humvee_object)
|
||||
|
||||
|
||||
#def convert_to_ssa(detection):
|
||||
# '''
|
||||
# curl -H 'Content-type: text/yaml' -H 'Authorization: Bearer TOKEN'
|
||||
# https://app-admin.playground.scp.splunk.com/secanalytics/ssa-tenant-management/v1alpha1/admin/detection-spl/research2
|
||||
# --data-binary @detections/endpoint/ssa___first_time_seen_cmd_line.yml
|
||||
# @param detection:
|
||||
# @return:
|
||||
# '''
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
FROM docker.repo.splunkdev.net/ci-cd/ci-container:python-3.7-buster
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y python3-dev git python-dev unzip python3-pip
|
||||
RUN apt-get install -y python-gitdb
|
||||
|
||||
WORKDIR /app
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
ENTRYPOINT ["python3", "detection_service.py"]
|
||||
CMD ["-tfn", "T1003_002"]
|
||||
@@ -0,0 +1,47 @@
|
||||
# Running tests
|
||||
|
||||
You can run these tests using `pytest`. The required parameters a DSP `token`, the DSP `environment`, and the DSP `tenant`.
|
||||
|
||||
## Run all tests in `playground`
|
||||
|
||||
```angular2
|
||||
pytest --env playground --tenant research2 --token <YOUR_DSP_TOKEN>
|
||||
```
|
||||
|
||||
## Run all tests in `staging`
|
||||
|
||||
```angular2
|
||||
pytest --env staging --tenant research --token <YOUR_DSP_TOKEN>
|
||||
```
|
||||
|
||||
## Run specific tests
|
||||
|
||||
If you add the argument `-k` to pytest, then it can filter tests to run the tests specified.
|
||||
|
||||
```angular2
|
||||
pytest --env playground --tenant research2 --token <YOUR_DSP_TOKEN> -k test_data_ingestion_preview
|
||||
```
|
||||
|
||||
It also can run tests by category if naming conditions are maintained.
|
||||
|
||||
```angular2
|
||||
pytest --env playground --tenant research2 --token <YOUR_DSP_TOKEN> -k test_data_ingestion
|
||||
```
|
||||
|
||||
Will run tests `test_data_ingestion_preview` and `test_data_ingestion_index`.
|
||||
|
||||
## Best Practices
|
||||
|
||||
Save your token to an environment variable. Go to *playground* console and copy your token. Then..
|
||||
|
||||
```angular2
|
||||
export SCLOUD_TOKEN_PLAY=$(pbpaste)
|
||||
```
|
||||
|
||||
Go to your *staging* console and copy your token. Then..
|
||||
|
||||
```angular2
|
||||
export SCLOUD_TOKEN_STAGING=$(pbpaste)
|
||||
```
|
||||
|
||||
Now you can easily run tests on both playground, and staging.
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--token", action="store", default="scs token")
|
||||
parser.addoption("--env", action="store", default="scs environment")
|
||||
parser.addoption("--tenant", action="store", default="environment's tenant")
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
# This is called for every test. Only get/set command line arguments
|
||||
# if the argument is specified in the list of test "fixturenames".
|
||||
option_value = metafunc.config.option.token
|
||||
if 'token' in metafunc.fixturenames and option_value is not None:
|
||||
metafunc.parametrize("token", [option_value])
|
||||
env = metafunc.config.option.env
|
||||
if 'tenant' in metafunc.fixturenames and env is not None:
|
||||
metafunc.parametrize("env", [env])
|
||||
tenant = metafunc.config.option.tenant
|
||||
if 'tenant' in metafunc.fixturenames and tenant is not None:
|
||||
metafunc.parametrize("tenant", [tenant])
|
||||
@@ -0,0 +1,30 @@
|
||||
FROM docker.repo.splunkdev.net/ci-cd/ci-container:debian-buster
|
||||
|
||||
ARG SRCBRANCH=develop
|
||||
ARG SCSENV=app_play1
|
||||
|
||||
ARG KUBECTL_VERSION=v1.10.9
|
||||
|
||||
ENV QBEC_VERSION 0.12.1
|
||||
ENV QBEC_SHA 6fbec2c33ff15c603585a22c100aa839461680cf775a1f90336078bf79c6a42f
|
||||
|
||||
ENV SRCBRANCH=${SRCBRANCH}
|
||||
ENV SCSENV=${SCSENV}
|
||||
|
||||
RUN mkdir /deployer && \
|
||||
apt-get update && \
|
||||
apt-get install -y bash tar curl && \
|
||||
# Install kubetctl
|
||||
curl -L https://dl.k8s.io/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl && \
|
||||
chmod +x /usr/local/bin/kubectl && \
|
||||
# Install qbec
|
||||
curl -L -O https://github.com/splunk/qbec/releases/download/v${QBEC_VERSION}/qbec-linux-amd64.tar.gz && \
|
||||
echo "${QBEC_SHA} qbec-linux-amd64.tar.gz" | sha256sum -c && \
|
||||
tar -xzf qbec-linux-amd64.tar.gz -C /usr/local/bin qbec && rm -rf qbec-linux-amd64.tar.gz
|
||||
|
||||
COPY k8s /deployer/k8s
|
||||
|
||||
RUN chmod +x /deployer/k8s/deployer.sh
|
||||
RUN chmod +x /deployer/k8s/check_deploy_status.sh
|
||||
|
||||
WORKDIR /deployer/k8s
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o pipefail
|
||||
set -o nounset
|
||||
|
||||
set -o errexit
|
||||
#This script is used to check the status of the smoketest k8s job: the tests pass if {.status.succeeded} returns 1.
|
||||
# Then retrieve the job logs once it's completed.
|
||||
smoketest_k8s_job=$(kubectl get jobs -o json | jq -r --arg JOBID "${CI_JOB_ID}" '.items[] | select(.kind=="Job") | select(.metadata.labels.ciJobId==$JOBID) | .metadata.name')
|
||||
echo >&2 "Create smoketest k8s job: $smoketest_k8s_job"
|
||||
smoketest_k8s_pod=$(kubectl get pods -o custom-columns=:metadata.name | grep ${smoketest_k8s_job})
|
||||
echo >&2 "Create smoketest k8s pod: $smoketest_k8s_pod"
|
||||
echo >&2 "Checking if smoketest starts running..."
|
||||
set +o errexit
|
||||
|
||||
#Wait for job ready to run as background process, returns 1 if smoketest job still in Pod Initializing stage.
|
||||
#Will exit if the pod is not up after 5 minutes(max_retries = 30)
|
||||
job_running=1
|
||||
counter=1
|
||||
MAX_RETRIES=30
|
||||
|
||||
while [[ $job_running -ne 0 ]] && [[ $counter -le $MAX_RETRIES ]]; do
|
||||
kubectl wait --for=condition=ready pod/${smoketest_k8s_pod} --timeout=10s
|
||||
job_running=$?
|
||||
echo >&2 "Checking if smoketest job starts running (return 0 if the job is ready to run):" $job_running
|
||||
((counter++))
|
||||
if [[ $counter -eq $MAX_RETRIES ]]; then
|
||||
kubectl get pods
|
||||
echo "Smoke test pod is not up after 5 minutes. Will exit."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
set -o errexit
|
||||
kubectl logs -f ${smoketest_k8s_pod}
|
||||
echo >&2 "Complete retrieving smoketest job logs."
|
||||
|
||||
|
||||
#Add more loggings to capture more info for k8s error that happens sporadically
|
||||
set +o errexit
|
||||
echo >&2 "Printing command: kubectl get job/${smoketest_k8s_job}"
|
||||
echo >&2 "$(kubectl get job/${smoketest_k8s_job})"
|
||||
echo >&2 "$(kubectl get job/${smoketest_k8s_job} -o jsonpath='{.status}')"
|
||||
|
||||
echo >&2 "Printing command with extra space: kubectl get job/${smoketest_k8s_job} "
|
||||
echo >&2 "$(kubectl get job/${smoketest_k8s_job} )"
|
||||
echo >&2 "$(kubectl get job/${smoketest_k8s_job} -o jsonpath='{.status}')"
|
||||
|
||||
echo >&2 "Printing command kubectl get jobs ${smoketest_k8s_job} -o jsonpath='{.status}'"
|
||||
echo >&2 "$(kubectl get jobs ${smoketest_k8s_job} -o jsonpath='{.status}')"
|
||||
echo >&2 "*****End of logging for k8s error debugging *****"
|
||||
set -o errexit
|
||||
|
||||
|
||||
SUCCESS=$(kubectl get job/${smoketest_k8s_job} -o jsonpath='{.status.succeeded}')
|
||||
if [[ $SUCCESS -ne 1 ]]; then
|
||||
echo "Smoke test failed. Please refer to the test logs."
|
||||
exit 1
|
||||
fi
|
||||
echo "All smoke tests passed!"
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
local params = import '../params.libsonnet';
|
||||
|
||||
[
|
||||
{
|
||||
apiVersion: 'v1',
|
||||
kind: 'ServiceAccount',
|
||||
metadata: {
|
||||
name: params.components.serviceAccountName,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,73 @@
|
||||
|
||||
local params = import '../params.libsonnet';
|
||||
|
||||
local job = {
|
||||
apiVersion: 'batch/v1',
|
||||
kind: 'Job',
|
||||
metadata: {
|
||||
generateName: 'ssa-smoketest-',
|
||||
labels: {
|
||||
name: 'ssa-smoketest',
|
||||
uploaderLabel: std.extVar('CI_COMMIT_SHORT_SHA'),
|
||||
ciJobId: std.extVar('CI_JOB_ID'),
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
activeDeadlineSeconds: 600,
|
||||
ttlSecondsAfterFinished: 100,
|
||||
template: {
|
||||
metadata: {
|
||||
annotations: {
|
||||
'splunk8s.io.vault/init-container': 'true',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
serviceAccountName: params.components.serviceAccountName,
|
||||
restartPolicy: 'Never',
|
||||
containers: [
|
||||
{
|
||||
name: 'ssa-smoke-test',
|
||||
image: std.extVar('SMOKETEST_RUNNER_IMAGE'),
|
||||
imagePullPolicy: 'Always',
|
||||
env: [
|
||||
{
|
||||
"name": 'SRCBRANCH',
|
||||
"value": std.extVar('SRCBRANCH')
|
||||
},
|
||||
{
|
||||
"name": 'SMOKETEST_VAULT_READ_PATH',
|
||||
"value": params.components.vaultReadPath
|
||||
},
|
||||
{
|
||||
"name": 'DSP_ENV',
|
||||
"value": params.components.dspEnv
|
||||
},
|
||||
{
|
||||
"name": 'TENANT',
|
||||
"value": params.components.tenant
|
||||
},
|
||||
{
|
||||
"name": 'ENV',
|
||||
"value": std.extVar('qbec.io/env')
|
||||
},
|
||||
],
|
||||
resources: {
|
||||
limits: {
|
||||
cpu: '1',
|
||||
memory: '1000Mi'
|
||||
},
|
||||
requests: {
|
||||
cpu: '800m',
|
||||
memory: '750Mi'
|
||||
},
|
||||
},
|
||||
command: ['/bin/bash', '-c'],
|
||||
args: ['./run_ssa_smoketest_helper.sh'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
job
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
qbec --yes apply $SCSENV --vm:ext-str SRCBRANCH --vm:ext-str CI_COMMIT_SHORT_SHA --vm:ext-str CI_JOB_ID --vm:ext-str SMOKETEST_RUNNER_IMAGE -c service-account -c smoketest --wait-timeout "1m"
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
// this file has the baseline default parameters
|
||||
{
|
||||
components: {
|
||||
serviceAccountName: "sa-tr"
|
||||
},
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
|
||||
// this file has the param overrides for the default environment
|
||||
local base = import './base.libsonnet';
|
||||
|
||||
base {
|
||||
components +: {
|
||||
serviceAccountName: "sa-tr-playground",
|
||||
vaultReadPath: 'scpauth-app-play1/token/threat-research-test.app-play1',
|
||||
tenant: 'research2',
|
||||
dspEnv: 'playground',
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
// this file has the param overrides for the default environment
|
||||
local base = import './base.libsonnet';
|
||||
|
||||
base {
|
||||
components +: {
|
||||
serviceAccountName: "sa-tr-staging",
|
||||
vaultReadPath: 'scpauth-app-stage1/token/threat-research-test.app-stage1',
|
||||
tenant: 'research',
|
||||
dspEnv: 'staging',
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
// this file returns the params for the current qbec environment
|
||||
// you need to add an entry here every time you add a new environment.
|
||||
|
||||
local env = std.extVar('qbec.io/env');
|
||||
local paramsMap = {
|
||||
_: import './environments/base.libsonnet',
|
||||
app_play1: import './environments/smoke-test-playground.libsonnet',
|
||||
app_staging1: import './environments/smoke-test-staging.libsonnet',
|
||||
};
|
||||
|
||||
if std.objectHas(paramsMap, env) then paramsMap[env] else error 'environment ' + env + ' not defined in ' + std.thisFile
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: qbec.io/v1alpha1
|
||||
kind: App
|
||||
metadata:
|
||||
name: security-content-stream-smoke-test
|
||||
spec:
|
||||
environments:
|
||||
app_play1:
|
||||
defaultNamespace: threat-research
|
||||
server: https://internal.api.app-play1.dev.us-west-2.splunk8s.io
|
||||
app_staging1:
|
||||
defaultNamespace: tr-st-deploy
|
||||
server: https://internal.api.app-stage1.stage.us-west-2.splunk8s.io
|
||||
vars:
|
||||
external:
|
||||
- name: SCBRANCH
|
||||
default: develop
|
||||
secret: false
|
||||
- name: SMOKE_TEST
|
||||
default: test
|
||||
secret: false
|
||||
@@ -0,0 +1,41 @@
|
||||
03/12/2021 01:19:11 AM
|
||||
LogName=Security
|
||||
SourceName=Microsoft Windows security auditing.
|
||||
EventCode=4688
|
||||
EventType=0
|
||||
Type=Information
|
||||
ComputerName=win-dc-5236904.attackrange.local
|
||||
TaskCategory=Process Creation
|
||||
OpCode=Info
|
||||
RecordNumber=540110
|
||||
Keywords=Audit Success
|
||||
Message=A new process has been created.
|
||||
|
||||
Creator Subject:
|
||||
Security ID: ATTACKRANGE\SuperUser01
|
||||
Account Name: SuperUser01
|
||||
Account Domain: ATTACKRANGE
|
||||
Logon ID: 0x1DB69D
|
||||
|
||||
Target Subject:
|
||||
Security ID: NULL SID
|
||||
Account Name: -
|
||||
Account Domain: -
|
||||
Logon ID: 0x0
|
||||
|
||||
Process Information:
|
||||
New Process ID: 0xd4c
|
||||
New Process Name: C:\Windows\system32\cmd.exe
|
||||
Token Elevation Type: %%1936
|
||||
Mandatory Label: Mandatory Label\High Mandatory Level
|
||||
Creator Process ID: 0xc88
|
||||
Creator Process Name: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
|
||||
Process Command Line: "C:\Windows\system32\cmd.exe" /c "reg save HKLM\sam %temp%\sam & reg save HKLM\system %temp%\system & reg save HKLM\Security %temp%\security"
|
||||
|
||||
Token Elevation Type indicates the type of token that was assigned to the new process in accordance with User Account Control policy.
|
||||
|
||||
Type 3 is a full token with no privileges removed or groups disabled. A full token is only used if User Account Control is disabled or if the user is the built-in Administrator account or a service account.
|
||||
|
||||
Type 2 is an elevated token with no privileges removed or groups disabled. An elevated token is used when User Account Control is enabled and the user chooses to start the program using Run as administrator. An elevated token is also used when an application is configured to always require administrative privilege or to always require maximum privilege, and the user is a member of the Administrators group.
|
||||
|
||||
Type 3 is a limited token with administrative privileges removed and administrative groups disabled. The limited token is used when User Account Control is enabled, the application does not require administrative privilege, and the user does not choose to start the program using Run as administrator.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,52 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from pytz import timezone
|
||||
import fileinput
|
||||
import os
|
||||
import re
|
||||
import io
|
||||
|
||||
class DataManipulation:
|
||||
|
||||
def manipulate_timestamp(self, file_path, sourcetype, source):
|
||||
|
||||
if source == 'WinEventLog:Security':
|
||||
return self.manipulate_timestamp_windows_event_log_raw(file_path)
|
||||
|
||||
|
||||
def manipulate_timestamp_windows_event_log_raw(self, file_path):
|
||||
path = file_path
|
||||
|
||||
f = io.open(path, "r", encoding="utf-8")
|
||||
pst = timezone('US/Pacific')
|
||||
self.now = datetime.now(pst)
|
||||
self.now = self.now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
self.now = datetime.strptime(self.now,"%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
|
||||
# read raw logs
|
||||
regex = r'\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2} [AP]M'
|
||||
data = f.read()
|
||||
lst_matches = re.findall(regex, data)
|
||||
if len(lst_matches) > 0:
|
||||
latest_event = datetime.strptime(lst_matches[-1],"%m/%d/%Y %I:%M:%S %p")
|
||||
self.difference = self.now - latest_event
|
||||
f.close()
|
||||
|
||||
result = re.sub(regex, self.replacement_function, data)
|
||||
|
||||
with io.open(path + ".swp", "w+", encoding='utf8') as f:
|
||||
f.write(result)
|
||||
return path + ".swp"
|
||||
else:
|
||||
f.close()
|
||||
return path
|
||||
|
||||
|
||||
def replacement_function(self, match):
|
||||
try:
|
||||
event_time = datetime.strptime(match.group(),"%m/%d/%Y %I:%M:%S %p")
|
||||
new_time = self.difference + event_time
|
||||
return new_time.strftime("%m/%d/%Y %I:%M:%S %p")
|
||||
except Exception as e:
|
||||
return match.group()
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
import git
|
||||
import os
|
||||
import logging
|
||||
from os import path
|
||||
import sys
|
||||
|
||||
|
||||
# Logger
|
||||
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SECURITY_CONTENT_URL = f"https://github.com/splunk/security_content"
|
||||
|
||||
|
||||
class GithubService:
|
||||
|
||||
def __init__(self, security_content_branch):
|
||||
self.security_content_branch = security_content_branch
|
||||
self.security_content_repo_obj = self.clone_project(SECURITY_CONTENT_URL, f"security_content", f"develop")
|
||||
self.security_content_repo_obj.git.checkout(security_content_branch)
|
||||
|
||||
def clone_project(self, url, project, branch):
|
||||
LOGGER.info(f"Clone Security Content Project")
|
||||
repo_obj = git.Repo.clone_from(url, project, branch=branch)
|
||||
return repo_obj
|
||||
|
||||
|
||||
def get_changed_test_files_ssa(self):
|
||||
branch1 = self.security_content_branch
|
||||
branch2 = 'develop'
|
||||
g = git.Git('security_content')
|
||||
differ = g.diff('--name-only', branch1, branch2)
|
||||
changed_files = differ.splitlines()
|
||||
|
||||
changed_ssa_test_files = []
|
||||
|
||||
#tests = self.read_security_content_test_files()
|
||||
|
||||
for file_path in changed_files:
|
||||
# added or changed test files
|
||||
if file_path.startswith('tests'):
|
||||
if os.path.basename(file_path).startswith('ssa'):
|
||||
if file_path not in changed_ssa_test_files:
|
||||
changed_ssa_test_files.append(file_path)
|
||||
|
||||
# changed detections
|
||||
if file_path.startswith('detections'):
|
||||
if os.path.basename(file_path).startswith('ssa'):
|
||||
file_path_new = os.path.splitext(file_path)[0].replace('detections', 'tests') + '.test.yml'
|
||||
if file_path_new not in changed_ssa_test_files:
|
||||
changed_ssa_test_files.append(file_path_new)
|
||||
|
||||
return changed_ssa_test_files
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import sys
|
||||
import yaml
|
||||
import time
|
||||
import os
|
||||
import requests
|
||||
import shutil
|
||||
|
||||
from .data_manipulation import DataManipulation
|
||||
|
||||
|
||||
def load_file(file_path):
|
||||
with open(file_path, 'r', encoding="utf-8") as stream:
|
||||
try:
|
||||
file = list(yaml.safe_load_all(stream))[0]
|
||||
except yaml.YAMLError as exc:
|
||||
sys.exit("ERROR: reading {0}".format(file_path))
|
||||
return file
|
||||
|
||||
|
||||
def prepare_test(file_path):
|
||||
|
||||
# read test file and return as object
|
||||
test_obj = load_file('security_content/' + file_path)
|
||||
detection_obj = load_file('security_content/detections/' + test_obj['tests'][0]['file'])
|
||||
|
||||
# download attack data
|
||||
epoch_time = str(int(time.time()))
|
||||
folder_name = "attack_data_" + epoch_time
|
||||
os.mkdir(folder_name)
|
||||
|
||||
for test in test_obj['tests']:
|
||||
for attack_data in test['attack_data']:
|
||||
url = attack_data['data']
|
||||
r = requests.get(url, allow_redirects=True)
|
||||
attack_data_file_path = folder_name + '/' + attack_data['file_name']
|
||||
open(folder_name + '/' + attack_data['file_name'], 'wb').write(r.content)
|
||||
|
||||
# Update timestamps before replay
|
||||
if 'update_timestamp' in attack_data:
|
||||
if attack_data['update_timestamp'] == True:
|
||||
data_manipulation = DataManipulation()
|
||||
data_manipulation.manipulate_timestamp(folder_name + '/' + attack_data['file_name'], attack_data['sourcetype'], attack_data['source'])
|
||||
|
||||
dict_test = {
|
||||
"test_obj": test_obj,
|
||||
"detection_obj": detection_obj,
|
||||
"attack_data_file_path": attack_data_file_path
|
||||
}
|
||||
|
||||
return dict_test, folder_name
|
||||
|
||||
|
||||
def remove_security_content():
|
||||
shutil.rmtree('security_content')
|
||||
|
||||
def remove_attack_data(file_path):
|
||||
# remove attack data
|
||||
shutil.rmtree(file_path)
|
||||
@@ -0,0 +1,20 @@
|
||||
| from read_ssa_enriched_events()
|
||||
| eval timestamp=parse_long(ucast(map_get(input_event, "_time"), "string", null))
|
||||
| eval process_name=lower(ucast(map_get(input_event, "process_name"), "string", null)),
|
||||
cmd_line=ucast(map_get(input_event, "process"), "string", null),
|
||||
dest_user_id=ucast(map_get(input_event, "dest_user_id"), "string", null),
|
||||
dest_device_id=ucast(map_get(input_event, "dest_device_id"), "string", null)
|
||||
| where process_name="cmd.exe" OR process_name="reg.exe"
|
||||
| where cmd_line != null AND
|
||||
match_regex(cmd_line, /(?i)save\s+/)=true AND
|
||||
( match_regex(cmd_line, /(?i)HKLM\\Security/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKLM\\SAM/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKLM\\System/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKEY_LOCAL_MACHINE\\Security/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKEY_LOCAL_MACHINE\\SAM/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKEY_LOCAL_MACHINE\\System/)=true
|
||||
)
|
||||
| eval start_time = timestamp,
|
||||
end_time = timestamp,
|
||||
entities = mvappend(dest_device_id, dest_user_id),
|
||||
body = "TBD";
|
||||
@@ -0,0 +1,20 @@
|
||||
| from read_ssa_enriched_events()
|
||||
| eval timestamp=parse_long(ucast(map_get(input_event, "_time"), "string", null))
|
||||
| eval process_name=lower(ucast(map_get(input_event, "process_name"), "string", null)),
|
||||
cmd_line=ucast(map_get(input_event, "process"), "string", null),
|
||||
dest_user_id=ucast(map_get(input_event, "dest_user_id"), "string", null),
|
||||
dest_device_id=ucast(map_get(input_event, "dest_device_id"), "string", null)
|
||||
| where process_name="cmd.exe" OR process_name="reg.exe"
|
||||
| where cmd_line != null AND
|
||||
match_regex(cmd_line, /(?i)save\s+/)=true AND
|
||||
( match_regex(cmd_line, /(?i)HKLM\\Security/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKLM\\SAM/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKLM\\System/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKEY_LOCAL_MACHINE\\Security/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKEY_LOCAL_MACHINE\\SAM/)=true OR
|
||||
match_regex(cmd_line, /(?i)HKEY_LOCAL_MACHINE\\System/)=true
|
||||
)
|
||||
| eval start_time = timestamp,
|
||||
end_time = timestamp,
|
||||
entities = mvappend(dest_device_id, dest_user_id),
|
||||
body = "TBD";
|
||||
@@ -0,0 +1 @@
|
||||
| from read_splunk_firehose();
|
||||
@@ -0,0 +1 @@
|
||||
| from read_splunk_firehose();
|
||||
@@ -0,0 +1 @@
|
||||
| from read_ssa_enriched_events();
|
||||
@@ -0,0 +1,504 @@
|
||||
"""
|
||||
helper functions to use the Streams Service API (v3beta1) to perform create, read and delete operations on
|
||||
data pipeline.
|
||||
API doc: https://dev.splunk.com/enterprise/reference/api/streams/v3beta1
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
from http import HTTPStatus
|
||||
#from constants import ML_MODEL_CONNECTOR_UUID
|
||||
from modules.utils import request_headers
|
||||
|
||||
|
||||
# Logger
|
||||
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TENANT_PLAYGROUND = f"research2"
|
||||
TENANT_STAGING = f"research"
|
||||
BASE_URL_PLAYGROUND = f"https://api.playground.scp.splunk.com/"
|
||||
BASE_URL_STAGING = f"https://api.staging.scp.splunk.com/"
|
||||
|
||||
# Streaming Pipelines REST endpoints
|
||||
CONNECTIONS_ENDPOINT = f"streams/v3beta1/connections"
|
||||
PIPELINES_ENDPOINT = f"streams/v3beta1/pipelines"
|
||||
PIPELINES_COMPILE_ENDPOINT = f"streams/v3beta1/pipelines/compile"
|
||||
PIPELINES_VALIDATE_ENDPOINT = f"streams/v3beta1/pipelines/validate"
|
||||
PIPELINES_REGISTRY_ENDPOINT = f"streams/v3beta1/pipelines/registry"
|
||||
PREVIEW_SESSION_ENDPOINT = f"streams/v3beta1/preview-session"
|
||||
PREVIEW_DATA_ENDPOINT = f"streams/v3beta1/preview-data"
|
||||
INGEST_ENDPOINT = f"ingest/v1beta2/events"
|
||||
SUBMIT_SEARCH_ENDPOINT = f"search/v2beta1/jobs"
|
||||
DATASETS_ENDPOINT = f"catalog/v2beta1/datasets"
|
||||
|
||||
|
||||
class DSPApi:
|
||||
|
||||
def __init__(self, env, tenant, header_token):
|
||||
self.env = env
|
||||
self.tenant = tenant
|
||||
self.header_token = header_token
|
||||
|
||||
|
||||
def return_api_endpoint(self, endpoint):
|
||||
if self.env == 'playground':
|
||||
return f"{BASE_URL_PLAYGROUND}{TENANT_PLAYGROUND}/{endpoint}"
|
||||
else:
|
||||
return f"{BASE_URL_STAGING}{TENANT_STAGING}/{endpoint}"
|
||||
|
||||
|
||||
def compile_spl(self, spl):
|
||||
"""
|
||||
Compile SPL text to a UPL JSON
|
||||
|
||||
Parameters
|
||||
----------
|
||||
spl: str
|
||||
the SPL representation of a pipeline or function parameter to be compiled
|
||||
|
||||
Returns
|
||||
-------
|
||||
upl:
|
||||
JSON representation of the compiled AST
|
||||
"""
|
||||
data = {"spl": spl}
|
||||
LOGGER.debug(f"Compiling SPL into UPL")
|
||||
#LOGGER.info(f"{spl}")
|
||||
response = requests.post(self.return_api_endpoint(PIPELINES_COMPILE_ENDPOINT), json=data, headers=request_headers(self.header_token))
|
||||
upl = response.json()
|
||||
#LOGGER.info(f"POST compile response_body is: {upl}")
|
||||
LOGGER.info(f"Successfully compile spl to upl")
|
||||
return upl, response
|
||||
|
||||
|
||||
def validate_upl(self, upl):
|
||||
"""
|
||||
Validate whether the JSON representation of a pipeline is valid
|
||||
|
||||
Parameters
|
||||
----------
|
||||
upl: JSON
|
||||
JSON representation of the compiled AST of a pipeline
|
||||
|
||||
Returns
|
||||
-------
|
||||
response_body: JSON
|
||||
returns whether or not the pipeline id valid. If valid, the response body returns 'success'
|
||||
"""
|
||||
|
||||
headers = {"Content-Type": "application/json", "Authorization": self.header_token}
|
||||
data = {"upl": upl}
|
||||
response = requests.post(self.return_api_endpoint(PIPELINES_VALIDATE_ENDPOINT), json=data, headers=headers)
|
||||
response_body = response.json()
|
||||
if response.status_code == HTTPStatus.OK:
|
||||
LOGGER.info(f"UPL is validated.")
|
||||
return upl, response_body
|
||||
else:
|
||||
LOGGER.error(f"UPL validation failed: {response_body}")
|
||||
return response_body
|
||||
|
||||
|
||||
def create_pipeline(self, upl):
|
||||
"""
|
||||
POST pipelines endpoint to create a pipeline based on the valid upl
|
||||
|
||||
Parameters
|
||||
----------
|
||||
upl: JSON
|
||||
JSON representation of the validated pipeline details
|
||||
|
||||
Returns
|
||||
-------
|
||||
pipline_id: UUID
|
||||
id of the created pipeline
|
||||
|
||||
"""
|
||||
|
||||
headers = {"Content-Type": "application/json", "Authorization": self.header_token}
|
||||
set_test_id = uuid.uuid4().hex
|
||||
data = {
|
||||
"name": f"ssa_smoke_test_pipeline_helper_{set_test_id}",
|
||||
"description": "ssa_test_pipeline_description",
|
||||
"bypassValidation": "true",
|
||||
"data": upl
|
||||
}
|
||||
response = requests.post(self.return_api_endpoint(PIPELINES_ENDPOINT), json=data, headers=headers)
|
||||
response_body = response.json()
|
||||
if response.status_code == HTTPStatus.CREATED:
|
||||
pipeline_id = response_body.get("id")
|
||||
#LOGGER.info(f"Pipeline {pipeline_id} successfully created")
|
||||
return pipeline_id
|
||||
|
||||
|
||||
def create_pipeline_from_spl(self, spl):
|
||||
"""
|
||||
helper function to compile and validate from spl text, then create the pipeline
|
||||
|
||||
"""
|
||||
upl, _ = self.compile_spl(spl)
|
||||
validated_upl, _ = self.validate_upl(upl)
|
||||
pipeline_id = self.create_pipeline(validated_upl)
|
||||
LOGGER.info(f"pipeline id created is: {pipeline_id}")
|
||||
return pipeline_id
|
||||
|
||||
|
||||
def activate_pipeline(self, pipeline_id):
|
||||
"""
|
||||
POST pipelines/activate endpoint to activate an existing pipeline
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pipeline_id: str
|
||||
pipeline UUID to activate
|
||||
|
||||
Returns
|
||||
-------
|
||||
response_body: response JSON
|
||||
response body that contains pipeline status ACTIVATED
|
||||
"""
|
||||
|
||||
assert(pipeline_id is not None), "Must specify a 'pipeline_id'"
|
||||
|
||||
headers = {"Content-Type": "application/json", "Authorization": self.header_token}
|
||||
pipelines_activate_endpoint = self.return_api_endpoint(PIPELINES_ENDPOINT) + "/" + pipeline_id + "/activate"
|
||||
|
||||
data = {
|
||||
"activateLatestVersion": "true",
|
||||
"allowNonRestoredState": "true",
|
||||
"skipRestoreState": "true"
|
||||
}
|
||||
|
||||
response = requests.post(pipelines_activate_endpoint, json=data, headers=headers)
|
||||
response_body = response.json()
|
||||
|
||||
if response.status_code == HTTPStatus.OK:
|
||||
pipeline_id = response_body.get("id")
|
||||
LOGGER.info(f"Pipeline {pipeline_id} successfully created")
|
||||
return response_body
|
||||
else:
|
||||
LOGGER.error(f"Failed to activate pipeline {pipeline_id}")
|
||||
return
|
||||
|
||||
|
||||
def deactivate_pipeline(self, pipeline_id):
|
||||
"""
|
||||
POST pipelines/deactivate endpoint to deactivate an existing pipeline
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pipeline_id:
|
||||
pipeline UUID to deactivate
|
||||
|
||||
Returns
|
||||
-------
|
||||
response_body: response JSON
|
||||
response body that contains the status of the pipeline after deactivation
|
||||
"""
|
||||
|
||||
assert(pipeline_id is not None), "Must specify a 'pipeline_id'"
|
||||
|
||||
headers = {"Content-Type": "application/json", "Authorization": self.header_token}
|
||||
pipelines_deactivate_endpoint = self.return_api_endpoint(PIPELINES_ENDPOINT) + "/" + pipeline_id + "/deactivate"
|
||||
|
||||
data = {
|
||||
"skipSavepoint": "true"
|
||||
}
|
||||
response = requests.post(pipelines_deactivate_endpoint, json=data, headers=headers)
|
||||
response_body = response.json()
|
||||
return response, response_body
|
||||
|
||||
|
||||
def delete_pipeline(self, pipeline_id):
|
||||
"""
|
||||
Delete an existing pipeline using its pipeline UUID
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pipeline_id:
|
||||
pipeline UUID of an existing pipeline
|
||||
|
||||
Returns
|
||||
-------
|
||||
response status code
|
||||
"""
|
||||
|
||||
assert(pipeline_id is not None), "Must specify a 'pipeline_id'"
|
||||
|
||||
headers = {"Content-Type": "application/json", "Authorization": self.header_token}
|
||||
delete_pipeline_endpoint = self.return_api_endpoint(PIPELINES_ENDPOINT) + "/" + pipeline_id
|
||||
response = requests.delete(delete_pipeline_endpoint, headers=headers)
|
||||
LOGGER.info(f"DELETE pipeline response status code is: {response.status_code}")
|
||||
return response
|
||||
|
||||
|
||||
def pipeline_status(self, pipeline_id):
|
||||
"""
|
||||
Returns the statues of an existing pipeline
|
||||
Parameters
|
||||
----------
|
||||
pipeline_id:
|
||||
pipeline UUID of an existing pipeline.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pipeline_status: str
|
||||
pipeline status can be CREATED, ACTIVATED, FINISHED, RESTARTING, FAILED.
|
||||
"""
|
||||
assert(pipeline_id is not None), "Must specify a 'pipeline_id'"
|
||||
|
||||
headers = {"Content-Type": "application/json", "Authorization": self.header_token}
|
||||
pipelines_status_endpoint = self.return_api_endpoint(PIPELINES_ENDPOINT) + "/" + pipeline_id
|
||||
|
||||
response = requests.get(pipelines_status_endpoint, headers=headers)
|
||||
response_body = response.json()
|
||||
if response.status_code == HTTPStatus.OK:
|
||||
pipeline_status = response_body.get("status")
|
||||
return pipeline_status
|
||||
else:
|
||||
LOGGER.error(f"Fail to get current status of pipeline pipeline {pipeline_id}")
|
||||
return
|
||||
|
||||
|
||||
def get_preview_id(self, upl):
|
||||
"""
|
||||
POST preview-session endpoint to create a preview session for a pipeline
|
||||
|
||||
Parameters
|
||||
----------
|
||||
upl: JSON
|
||||
JSON representation of the pipeline details
|
||||
|
||||
Returns
|
||||
-------
|
||||
preview_id: str
|
||||
preview session id
|
||||
"""
|
||||
|
||||
headers = {"Content-Type": "application/json", "Authorization": self.header_token}
|
||||
data = {
|
||||
"upl": upl
|
||||
}
|
||||
response = requests.post(self.return_api_endpoint(PREVIEW_SESSION_ENDPOINT), json=data, headers=headers)
|
||||
response_body = response.json()
|
||||
# The preview sessions was started successfully
|
||||
if response.status_code == HTTPStatus.CREATED:
|
||||
preview_id = response_body.get("previewId")
|
||||
assert(preview_id is not None), "Must return a 'preview_id'"
|
||||
return preview_id
|
||||
|
||||
|
||||
def get_preview_data(self, preview_id):
|
||||
"""
|
||||
POST preview-data endpoint to get the preview data for a preview session
|
||||
|
||||
Parameters
|
||||
----------
|
||||
preview_id: str
|
||||
preview session id
|
||||
|
||||
Returns
|
||||
-------
|
||||
response_body: response JSON
|
||||
the response would contain current number of records, preview data
|
||||
"""
|
||||
|
||||
assert(preview_id is not None), "Must specify a 'preview_id'"
|
||||
|
||||
headers = {"Content-Type": "application/json", "Authorization": self.header_token}
|
||||
preview_data_endpoint = self.return_api_endpoint(PREVIEW_DATA_ENDPOINT) + "/" + str(preview_id)
|
||||
|
||||
response = requests.get(preview_data_endpoint, headers=headers)
|
||||
response_body = response.json()
|
||||
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
LOGGER.error(f"Failed to preview data from the pipeline. Please check if the operator has been properly "
|
||||
f"uploaded to DSP.")
|
||||
return
|
||||
else:
|
||||
return response, response_body
|
||||
|
||||
|
||||
def stop_preview_session(self, preview_id):
|
||||
"""
|
||||
Delete an existing pipeline using its pipeline UUID
|
||||
|
||||
Parameters
|
||||
----------
|
||||
preview_id: str
|
||||
preview session id
|
||||
|
||||
Returns
|
||||
-------
|
||||
Boolean: True if preview session is stopped.
|
||||
"""
|
||||
|
||||
assert(preview_id is not None), "Must specify a 'preview_id'"
|
||||
headers = {"Content-Type": "application/json", "Authorization": self.header_token}
|
||||
stop_preview_session_endpoint = self.return_api_endpoint(PREVIEW_SESSION_ENDPOINT) + "/" + str(preview_id)
|
||||
response = requests.delete(stop_preview_session_endpoint, headers=headers)
|
||||
LOGGER.info(f"DELETE/preview-session response status code is: {response.status_code}")
|
||||
return response
|
||||
|
||||
|
||||
def get_preview_id_from_spl(self, spl):
|
||||
|
||||
"""
|
||||
helper function to compile and validate from spl text, then create the pipeline preview session
|
||||
|
||||
"""
|
||||
|
||||
upl, _ = self.compile_spl(spl)
|
||||
validated_upl, _ = self.validate_upl(upl)
|
||||
preview_id = self.get_preview_id(validated_upl)
|
||||
LOGGER.info(f"preview id created is: {preview_id}")
|
||||
return preview_id
|
||||
|
||||
|
||||
def ingest_data(self, data):
|
||||
"""
|
||||
Send events
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data: str
|
||||
datafile
|
||||
|
||||
Returns
|
||||
-------
|
||||
response
|
||||
response body in JSON format
|
||||
"""
|
||||
data = [{
|
||||
"body": data,
|
||||
"sourcetype": "WinEventLog"
|
||||
}]
|
||||
response = requests.post(self.return_api_endpoint(INGEST_ENDPOINT), json=data, headers=request_headers(self.header_token))
|
||||
return response.json()
|
||||
|
||||
|
||||
def submit_search_job(self, module, query):
|
||||
"""
|
||||
Submit Search job
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module: str
|
||||
module where this query will be run on (default: `mc`)
|
||||
query: str
|
||||
SPL of the search query
|
||||
|
||||
Returns
|
||||
-------
|
||||
sid
|
||||
sid to search job
|
||||
"""
|
||||
data = {
|
||||
"query": query,
|
||||
"module": module
|
||||
}
|
||||
LOGGER.info(f"Submit Search Job")
|
||||
response = requests.post(self.return_api_endpoint(SUBMIT_SEARCH_ENDPOINT), json=data, headers=request_headers(self.header_token))
|
||||
if response.status_code != HTTPStatus.CREATED:
|
||||
LOGGER.error(f"Submit search job failed.")
|
||||
return None
|
||||
else:
|
||||
response_body = response.json()
|
||||
return response_body.get("sid")
|
||||
|
||||
|
||||
def check_search_job_finished(self, sid):
|
||||
"""
|
||||
Check if a search job finished
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sid: str
|
||||
ID of the search. Returned value from `submit_search_job`
|
||||
|
||||
Returns
|
||||
-------
|
||||
response
|
||||
boolean true or false
|
||||
"""
|
||||
LOGGER.info(f"Check Search job results")
|
||||
results_check_search_job = self.return_api_endpoint(SUBMIT_SEARCH_ENDPOINT) + "/" + sid
|
||||
response = requests.get(results_check_search_job, headers=request_headers(self.header_token))
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
LOGGER.error(f"Failed to get status of search job")
|
||||
return None
|
||||
else:
|
||||
response_json = response.json()
|
||||
LOGGER.info(f"Check if search is finished.")
|
||||
if response_json.get("status") == "done":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_search_job_results(self, sid):
|
||||
"""
|
||||
Get Search Job Results
|
||||
|
||||
Parameters
|
||||
----------
|
||||
sid: str
|
||||
ID of the search. Returned value from `submit_search_job`
|
||||
|
||||
Returns
|
||||
-------
|
||||
results
|
||||
results of search
|
||||
"""
|
||||
|
||||
LOGGER.info(f"Get Search job results")
|
||||
results_search_job_endpoint = self.return_api_endpoint(SUBMIT_SEARCH_ENDPOINT) + "/" + sid + "/results"
|
||||
response = requests.get(results_search_job_endpoint, headers=request_headers(self.header_token))
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
LOGGER.error(f"Failed to get search results")
|
||||
return None
|
||||
else:
|
||||
response_body = response.json()
|
||||
return response_body.get("results")
|
||||
|
||||
|
||||
def create_temp_index(self, module):
|
||||
"""
|
||||
Creates an index under module
|
||||
|
||||
Parameters
|
||||
@param module: str
|
||||
module under this index will be created
|
||||
@return:
|
||||
index object dictionary
|
||||
"""
|
||||
index_name = f"temp_st_{uuid.uuid1()}".replace("-", "_")
|
||||
data = {
|
||||
"module": module,
|
||||
"name": index_name,
|
||||
"kind": "index",
|
||||
"disabled": False
|
||||
}
|
||||
response = requests.post(self.return_api_endpoint(DATASETS_ENDPOINT), headers=request_headers(self.header_token), json=data)
|
||||
LOGGER.info(f"Create Temp Index {index_name}")
|
||||
return response.json()
|
||||
|
||||
|
||||
def delete_temp_index(self, index_id):
|
||||
"""
|
||||
Deletes an index
|
||||
|
||||
@param index_id:
|
||||
Index ID
|
||||
@return:
|
||||
response status code from API
|
||||
"""
|
||||
LOGGER.info(f"Delete Temp Index")
|
||||
datasets_endpoint_api = self.return_api_endpoint(DATASETS_ENDPOINT)
|
||||
delete_url = f"{datasets_endpoint_api}/{index_id}"
|
||||
response = requests.delete(delete_url, headers=request_headers(self.header_token))
|
||||
return response.status_code
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import sys
|
||||
|
||||
from http import HTTPStatus
|
||||
from modules.streams_service_api_helper import DSPApi
|
||||
from modules.utils import manipulate_spl, read_spl, read_data
|
||||
|
||||
|
||||
# Logger
|
||||
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# MACROS
|
||||
SLEEP_TIME_CREATE_INDEX = 10
|
||||
SLEEP_TIME_ACTIVATE_PIPELINE = 10
|
||||
SLEEP_TIME_SEND_DATA = 30
|
||||
WAIT_CYCLE = 20
|
||||
MAX_EXECUTION_TIME_LIMIT = 1200 # per detection test
|
||||
|
||||
TEST_DATASET = 'windows-security_small.txt'
|
||||
|
||||
class SSADetectionTesting:
|
||||
|
||||
def __init__(self, env, tenant, header_token):
|
||||
self.execution_passed = True
|
||||
self.max_execution_time = MAX_EXECUTION_TIME_LIMIT
|
||||
self.env = env
|
||||
self.tenant = tenant
|
||||
self.header_token = f"Bearer {header_token}"
|
||||
self.api = DSPApi(env, tenant, self.header_token)
|
||||
self.test_results = {}
|
||||
|
||||
def test_dsp_pipeline(self):
|
||||
|
||||
file_path_data = os.path.join(os.path.dirname(__file__), 'data', TEST_DATASET)
|
||||
file_path_spl = os.path.join(os.path.dirname(__file__), 'spl')
|
||||
|
||||
test_spls = ['troubleshoot.spl', 'detection.spl']
|
||||
test_names = [
|
||||
"SSA Index Test Minimal",
|
||||
"SSA Index Detection Testing Example"
|
||||
]
|
||||
|
||||
test_results = []
|
||||
for i in range(0,len(test_spls)):
|
||||
self.max_execution_time = MAX_EXECUTION_TIME_LIMIT
|
||||
test_result = self.ssa_detection_test(read_spl(file_path_spl, test_spls[i]), file_path_data, test_names[i])
|
||||
test_results.append(test_result.copy())
|
||||
|
||||
passed = True
|
||||
|
||||
LOGGER.info('-----------------------------------')
|
||||
LOGGER.info('-------- test DSP Pipeline --------')
|
||||
LOGGER.info('-----------------------------------')
|
||||
for test_result in test_results:
|
||||
LOGGER.info(test_result['msg'])
|
||||
passed = passed and test_result['result']
|
||||
LOGGER.info('-----------------------------------')
|
||||
|
||||
return passed
|
||||
|
||||
|
||||
def test_ssa_detections(self, test_obj):
|
||||
|
||||
self.max_execution_time = MAX_EXECUTION_TIME_LIMIT
|
||||
file_path_attack_data = os.path.join(os.path.dirname(__file__), "../", test_obj["attack_data_file_path"])
|
||||
|
||||
test_results = self.ssa_detection_test(test_obj["detection_obj"]["search"], file_path_attack_data, "SSA Smoke Test " + test_obj["test_obj"]["name"])
|
||||
|
||||
return test_results
|
||||
|
||||
|
||||
## Helper Functions ##
|
||||
|
||||
def update_execution_time(self, time_frame):
|
||||
self.max_execution_time = self.max_execution_time - WAIT_CYCLE
|
||||
if self.max_execution_time < 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def wait_time(self, time_in_s):
|
||||
time.sleep(time_in_s)
|
||||
return self.update_execution_time(time_in_s)
|
||||
|
||||
def check_result(self, condition, error_message):
|
||||
try:
|
||||
assert condition
|
||||
except:
|
||||
self.execution_passed = False
|
||||
LOGGER.error(error_message)
|
||||
|
||||
def write_test_results(self, test_name):
|
||||
if not self.execution_passed:
|
||||
msg = f"Detection test failed for {test_name}"
|
||||
LOGGER.error(msg)
|
||||
self.test_results["msg"] = msg
|
||||
self.test_results["result"] = False
|
||||
else:
|
||||
msg = f"Detection test successful for {test_name}"
|
||||
LOGGER.info(msg)
|
||||
self.test_results["msg"] = msg
|
||||
|
||||
|
||||
def ssa_detection_test_init(self):
|
||||
self.test_results["result"] = True
|
||||
self.test_results["msg"] = ""
|
||||
self.results_index = self.api.create_temp_index("mc")
|
||||
|
||||
|
||||
def ssa_detection_test_main(self, spl, source, test_name):
|
||||
self.execution_passed = True
|
||||
|
||||
self.wait_time(SLEEP_TIME_CREATE_INDEX)
|
||||
|
||||
spl = manipulate_spl(self.api.env, spl, self.results_index)
|
||||
self.check_result(spl is not None, "fail to manipulate spl file")
|
||||
|
||||
pipeline_id = self.api.create_pipeline_from_spl(spl)
|
||||
self.check_result(pipeline_id is not None, "failed to create a pipeline")
|
||||
|
||||
_pipeline_status = self.api.pipeline_status(pipeline_id)
|
||||
self.check_result(_pipeline_status=="CREATED", f"Current status of pipeline {pipeline_id} should be CREATED")
|
||||
|
||||
response_body = self.api.activate_pipeline(pipeline_id)
|
||||
self.check_result(response_body.get("activated")==pipeline_id, f"pipeline {pipeline_id} should be successfully activate.")
|
||||
|
||||
self.wait_time(SLEEP_TIME_ACTIVATE_PIPELINE)
|
||||
|
||||
data = read_data(source)
|
||||
LOGGER.info("Sending (%d) events" % (len(data)))
|
||||
|
||||
if len(data) == 0:
|
||||
LOGGER.warning("No events to send, skip to next test.")
|
||||
return self.test_results
|
||||
|
||||
for d in data:
|
||||
response_body = self.api.ingest_data(d)
|
||||
|
||||
self.wait_time(SLEEP_TIME_SEND_DATA)
|
||||
|
||||
search_results = False
|
||||
max_execution_time_reached = False
|
||||
|
||||
while not (search_results or max_execution_time_reached):
|
||||
max_execution_time_reached = self.wait_time(WAIT_CYCLE)
|
||||
query = f"from indexes('{self.results_index['name']}') | search source!=\"Search Catalog\" "
|
||||
sid = self.api.submit_search_job(self.results_index['module'], query)
|
||||
self.check_result(sid is not None, f"Failed to create a Search Job")
|
||||
|
||||
job_finished = False
|
||||
while not job_finished:
|
||||
self.wait_time(WAIT_CYCLE)
|
||||
result = self.api.check_search_job_finished(sid)
|
||||
job_finished = result
|
||||
|
||||
results = self.api.get_search_job_results(sid)
|
||||
search_results = (len(results) > 0)
|
||||
if not search_results:
|
||||
LOGGER.info(f"Search didn't return any results. Retrying in {WAIT_CYCLE}s, max execution time left {self.max_execution_time}s")
|
||||
|
||||
self.check_result(len(results) > 0, "Search job didn't return any results")
|
||||
|
||||
response, response_body = self.api.deactivate_pipeline(pipeline_id)
|
||||
self.check_result(response.status_code == HTTPStatus.OK, f"The pipeline {pipeline_id} fails to deactivated.")
|
||||
|
||||
response = self.api.delete_pipeline(pipeline_id)
|
||||
self.check_result(response.status_code == HTTPStatus.NO_CONTENT, f"Fail to delete pipeline {pipeline_id}.")
|
||||
|
||||
self.write_test_results(test_name)
|
||||
|
||||
return self.test_results
|
||||
|
||||
|
||||
def ssa_detection_test_teardown(self):
|
||||
pass
|
||||
self.api.delete_temp_index(self.results_index["id"])
|
||||
|
||||
|
||||
def ssa_detection_test(self, spl, source, test_name):
|
||||
self.ssa_detection_test_init()
|
||||
test_result = self.ssa_detection_test_main(spl, source, test_name)
|
||||
self.ssa_detection_test_teardown()
|
||||
return test_result
|
||||
|
||||
|
||||
|
||||
# only for troubleshooting
|
||||
# def ssa_detection_in_dsp_with_preview_session(self, spl, source, test_name):
|
||||
|
||||
# self.execution_passed = True
|
||||
|
||||
# spl = manipulate_spl(self.api.env, spl)
|
||||
# self.check_result(spl is not None, "fail to read dummy spl file")
|
||||
|
||||
# preview_id = self.api.get_preview_id_from_spl(spl)
|
||||
# self.check_result(preview_id is not None, "failed to create a preview session %s" % spl)
|
||||
|
||||
# time.sleep(SLEEP_TIME_SHORT)
|
||||
|
||||
# data = read_data(source)
|
||||
# response_body = self.api.ingest_data(data)
|
||||
|
||||
# time.sleep(SLEEP_TIME_LONG)
|
||||
|
||||
# response, response_body = self.api.get_preview_data(preview_id)
|
||||
# self.check_result(response_body.get("currentNumberOfRecords") > 0, "Missing records in preview session.")
|
||||
|
||||
# response = self.api.stop_preview_session(preview_id)
|
||||
|
||||
# self.write_test_results(test_name)
|
||||
@@ -0,0 +1,126 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import fileinput
|
||||
import re
|
||||
|
||||
from .data_manipulation import DataManipulation
|
||||
|
||||
# Logger
|
||||
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Macros
|
||||
PULSAR_SOURCE_CONNECTION_ID_PLAYGROUND = f"29fb61f1-9342-48f5-9793-1afa008c377b"
|
||||
PULSAR_SOURCE_TOPIC_PLAYGROUND = f"persistent://ssa/egress/decorated-events-research2"
|
||||
PULSAR_SOURCE_CONNECTION_ID_STAGING = f"fd92bf9f-5d40-4c2e-bb75-bf0c3fc13980"
|
||||
PULSAR_SOURCE_TOPIC_STAGING = f"persistent://ssa/egress/decorated-events-research"
|
||||
|
||||
READ_SSA_ENRICHED_EVENTS = f"| from read_ssa_enriched_events()"
|
||||
READ_SSA_ENRICHED_EVENTS_EXPANDED = (
|
||||
f"| from pulsar(\"__PULSAR_SOURCE_CONNECTION_ID__\", \"__PULSAR_SOURCE_TOPIC__\")"
|
||||
f"| eval input_event=deserialize_json_object(value)"
|
||||
f"| select input_event"
|
||||
f"| eval _datamodels=ucast(map_get(input_event, \"_datamodels\"), \"collection<string>\", [])"
|
||||
f",body={{}}"
|
||||
)
|
||||
|
||||
# not used in the moment
|
||||
# PULSAR_SINK_CONNECTION_ID = f"29fb61f1-9342-48f5-9793-1afa008c377b"
|
||||
# PULSAR_SINK_TOPIC = f"persistent://ssa/ingress/detection-events-research2"
|
||||
WRITE_SSA_DETECTED_EVENTS = f"| into write_ssa_detected_events();"
|
||||
|
||||
# ## dummy values ##
|
||||
# DETECTION_TYPE = f"anomaly"
|
||||
# DETECTION_ID = f"93fbec4e-0375-440c-8db3-4508eca470c4"
|
||||
# DETECTION_VERSION = f"1"
|
||||
# RISK_SEVERITY = f"low"
|
||||
# ## dummy values ##
|
||||
|
||||
# WRITE_SSA_DETECTED_EVENTS_EXPANDED = (
|
||||
# f"| eval create_time=time()"
|
||||
# f", type=\"{DETECTION_TYPE}\""
|
||||
# f", detection_id=\"{DETECTION_ID}\""
|
||||
# f", detection_version=\"{DETECTION_VERSION}\""
|
||||
# f", risk_severity=\"{RISK_SEVERITY}\""
|
||||
# f"| select create_time"
|
||||
# f", start_time"
|
||||
# f", end_time"
|
||||
# f", type"
|
||||
# f", detection_id"
|
||||
# f", detection_version"
|
||||
# f", risk_severity"
|
||||
# f", entities"
|
||||
# f", body"
|
||||
# f"| eval id=concat(\"sha256:\", base64_encode(sha256(serialize_json())))"
|
||||
# f"| select id, serialize_json() AS value"
|
||||
# f"| into pulsar(\"{PULSAR_SINK_CONNECTION_ID}\", \"{PULSAR_SINK_TOPIC}\", id, value);"
|
||||
# )
|
||||
|
||||
|
||||
def fetch_token(file):
|
||||
with open(file) as f:
|
||||
data = json.load(f)
|
||||
try:
|
||||
token = data["data"]["token"]
|
||||
|
||||
except RuntimeError as e:
|
||||
LOGGER.error(f"JSON is invalid, no IAC token found in Vault JSON output: {e}")
|
||||
return token
|
||||
|
||||
|
||||
def request_headers(header_token):
|
||||
headers = {"Content-Type": "application/json", "Authorization": header_token}
|
||||
return headers
|
||||
|
||||
|
||||
def manipulate_spl(env, spl, results_index):
|
||||
spl = replace_ssa_macros(env, spl)
|
||||
if results_index is not None:
|
||||
# When an index is defined for a test, it writes the output of this pipeline to this index.
|
||||
# original_pipeline; => original_pipeline | into index("module", "index");
|
||||
module = results_index["module"]
|
||||
index = results_index["name"]
|
||||
spl = spl[:spl.rindex(";")] + f" | into index(\"{module}\", \"{index}\");"
|
||||
LOGGER.info(f"spl: {spl}")
|
||||
return spl
|
||||
|
||||
|
||||
def read_spl(file_path, file_name):
|
||||
full_path = os.path.join(file_path, file_name)
|
||||
spl = open(full_path, "r").read()
|
||||
return spl
|
||||
|
||||
|
||||
def replace_ssa_macros(env, spl):
|
||||
pulsar_source_connection_id, pulsar_source_topic = return_macros(env)
|
||||
macro_expanded = READ_SSA_ENRICHED_EVENTS_EXPANDED.replace("__PULSAR_SOURCE_CONNECTION_ID__", pulsar_source_connection_id)
|
||||
macro_expanded = macro_expanded.replace("__PULSAR_SOURCE_TOPIC__", pulsar_source_topic)
|
||||
spl = spl.replace(READ_SSA_ENRICHED_EVENTS, macro_expanded)
|
||||
spl = spl.replace(WRITE_SSA_DETECTED_EVENTS, ";")
|
||||
#spl = spl.replace("\n", " ")
|
||||
return spl
|
||||
|
||||
|
||||
def read_data(file_name):
|
||||
file_path = os.path.join(os.path.dirname(__file__), 'data', file_name)
|
||||
data_manipulation = DataManipulation()
|
||||
modified_file = data_manipulation.manipulate_timestamp(file_path, 'xmlwineventlog', 'WinEventLog:Security')
|
||||
data = []
|
||||
event = ""
|
||||
date_rex = r'\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2} [AP]M'
|
||||
for line in fileinput.input(files=modified_file):
|
||||
if event != "" and re.match(date_rex, line):
|
||||
data.append(event)
|
||||
event = line
|
||||
else:
|
||||
event = event + line
|
||||
return data
|
||||
|
||||
|
||||
def return_macros(env):
|
||||
if env == "playground":
|
||||
return PULSAR_SOURCE_CONNECTION_ID_PLAYGROUND, PULSAR_SOURCE_TOPIC_PLAYGROUND
|
||||
else:
|
||||
return PULSAR_SOURCE_CONNECTION_ID_STAGING, PULSAR_SOURCE_TOPIC_STAGING
|
||||
@@ -0,0 +1,23 @@
|
||||
attrs==20.3.0
|
||||
certifi==2020.12.5
|
||||
chardet==4.0.0
|
||||
gitdb==4.0.5
|
||||
GitPython==3.1.14
|
||||
idna==2.10
|
||||
iniconfig==1.1.1
|
||||
install==1.3.4
|
||||
packaging==20.9
|
||||
pluggy==0.13.1
|
||||
py==1.10.0
|
||||
pyparsing==2.4.7
|
||||
pytest==6.2.2
|
||||
pytest-check==1.0.1
|
||||
pytest-expect==1.1.0
|
||||
pytz==2021.1
|
||||
PyYAML==5.4.1
|
||||
requests==2.25.1
|
||||
six==1.15.0
|
||||
smmap==3.0.5
|
||||
toml==0.10.2
|
||||
u-msgpack-python==2.7.1
|
||||
urllib3==1.26.3
|
||||
@@ -0,0 +1,77 @@
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from modules.github_service import GithubService
|
||||
from modules.test_ssa_detections import SSADetectionTesting
|
||||
from modules.security_content_handler import prepare_test, remove_attack_data, remove_security_content
|
||||
|
||||
|
||||
# Logger
|
||||
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
parser = argparse.ArgumentParser(description="SSA detection smoke test")
|
||||
parser.add_argument("-t", "--token", required=True,
|
||||
help="specify the scloud token")
|
||||
parser.add_argument("-e", "--env", required=True,
|
||||
help="specify the environment")
|
||||
parser.add_argument("-s", "--tenant", required=True,
|
||||
help="specify the tenant in the environment")
|
||||
parser.add_argument("-b", "--branch", required=True,
|
||||
help="specify the security content branch")
|
||||
|
||||
args = parser.parse_args()
|
||||
token = args.token
|
||||
env = args.env
|
||||
tenant = args.tenant
|
||||
branch = args.branch
|
||||
|
||||
# Retrieve Security Content
|
||||
github_service = GithubService(branch)
|
||||
test_files_ssa = github_service.get_changed_test_files_ssa()
|
||||
LOGGER.info('changed/added GitHub files:')
|
||||
for test_file in test_files_ssa:
|
||||
LOGGER.info(test_file)
|
||||
|
||||
if len(test_files_ssa)==0:
|
||||
LOGGER.info('Nothing to test for SSA smoke test.')
|
||||
sys.exit(0)
|
||||
|
||||
# test DSP and SSA pipeline
|
||||
ssa_detection_testing = SSADetectionTesting(env, tenant, token)
|
||||
test_result_passed = ssa_detection_testing.test_dsp_pipeline()
|
||||
|
||||
if not test_result_passed:
|
||||
sys.exit(1)
|
||||
|
||||
# # test SSA detections
|
||||
test_results = []
|
||||
test_passed = True
|
||||
for test_file in test_files_ssa:
|
||||
test_obj, attack_data_folder = prepare_test(test_file)
|
||||
test_result = ssa_detection_testing.test_ssa_detections(test_obj)
|
||||
test_results.append(test_result)
|
||||
remove_attack_data(attack_data_folder)
|
||||
|
||||
LOGGER.info('-----------------------------------')
|
||||
LOGGER.info('------- test SSA detections -------')
|
||||
LOGGER.info('-----------------------------------')
|
||||
for test_result in test_results:
|
||||
test_passed = test_passed and test_result['result']
|
||||
LOGGER.info(test_result['msg'])
|
||||
LOGGER.info('-----------------------------------')
|
||||
|
||||
remove_security_content()
|
||||
exit_code = not test_passed
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
export VAULT_TOKEN=`cat /vault/.vault-token`
|
||||
|
||||
SCLOUD_TOKEN=$(vault read -format json $SMOKETEST_VAULT_READ_PATH tenant=$TENANT | jq -r '.data.token')
|
||||
|
||||
virtualenv -p python3 smoketest && source smoketest/bin/activate && pip3 install -r requirements.txt
|
||||
|
||||
python run_ssa_smoketest.py -t $SCLOUD_TOKEN -e $DSP_ENV -s $TENANT -b $SRCBRANCH
|
||||
@@ -0,0 +1,18 @@
|
||||
# Base image that support pod to communicate with SCS Vault to fetch IAC token as well to support python3 pytest framework
|
||||
FROM docker.repo.splunkdev.net/ci-cd/ci-container:python-3.7-buster
|
||||
|
||||
ARG SRCBRANCH=develop
|
||||
|
||||
RUN mkdir smoketest && \
|
||||
/usr/local/bin/python -m pip install --upgrade pip && \
|
||||
pip install pytest && pip install requests && pip install virtualenv
|
||||
|
||||
RUN git clone https://github.com/splunk/security_content.git -b ${SRCBRANCH}
|
||||
|
||||
RUN chmod +x security_content/bin/ssa-end-to-end-testing/run_ssa_smoketest_helper.sh
|
||||
|
||||
RUN curl -L https://github.com/splunk/splunk-cloud-sdk-go/releases/download/v1.11.1/scloud_v7.1.0_linux_amd64.tar.gz | tar -xz -C /usr/bin
|
||||
|
||||
COPY --from=docker.repo.splunkdev.net/kub/debug-tools:latest /usr/local/bin/vault /usr/local/bin/vault
|
||||
|
||||
WORKDIR /security_content/bin/ssa-end-to-end-testing
|
||||
+2
-2
@@ -20,10 +20,10 @@ importlib-resources==5.1.2
|
||||
Jinja2==2.11.3
|
||||
jsonschema==3.2.0
|
||||
MarkupSafe==1.1.1
|
||||
more-itertools==8.6.0
|
||||
more-itertools==8.7.0
|
||||
nodeenv==1.5.0
|
||||
pathlib2==2.3.5
|
||||
pre-commit==2.9.3
|
||||
pre-commit==2.10.0
|
||||
prompt-toolkit==1.0.14
|
||||
Pygments==2.8.0
|
||||
PyInquirer==1.0.3
|
||||
|
||||
Reference in New Issue
Block a user