Skip to content

Commit

Permalink
wip: add nessus e2e test
Browse files Browse the repository at this point in the history
  • Loading branch information
sfowl committed Nov 15, 2024
1 parent 5a08e6a commit 64cb1db
Show file tree
Hide file tree
Showing 9 changed files with 436 additions and 158 deletions.
76 changes: 75 additions & 1 deletion .tekton/integration-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@ spec:
- name: SNAPSHOT
value: $(params.SNAPSHOT)

- name: provision-eaas-space-nessus
runAfter:
- parse-metadata
taskRef:
resolver: git
params:
- name: url
value: https://github.com/konflux-ci/build-definitions.git
- name: revision
value: main
- name: pathInRepo
value: task/provision-env-with-ephemeral-namespace/0.1/provision-env-with-ephemeral-namespace.yaml
params:
- name: KONFLUXNAMESPACE
value: $(context.pipelineRun.namespace)
- name: PIPELINERUN_NAME
value: $(context.pipelineRun.name)
- name: PIPELINERUN_UID
value: $(context.pipelineRun.uid)

- name: provision-eaas-space
runAfter:
- parse-metadata
Expand Down Expand Up @@ -131,5 +151,59 @@ spec:
yum install -y python3.12
python3.12 -m ensurepip
pip3 install -r requirements.txt -r requirements-dev.txt
pytest -s e2e-tests --json-report --json-report-summary --json-report-file $(results.TEST_RESULTS.path)
pytest -s e2e-tests/test_integration.py --json-report --json-report-summary --json-report-file $(results.TEST_RESULTS.path)
cat $(results.TEST_RESULTS.path)
- name: run-e2e-tests
runAfter:
- provision-eaas-space-nessus
taskSpec:
volumes:
- name: credentials
emptyDir: {}
results:
- name: TEST_RESULTS
description: e2e test results
steps:

# XXX not supported to use workspaces in integration tests:
# * https://issues.redhat.com/browse/STONEINTG-895
- name: clone-repository
image: quay.io/konflux-ci/git-clone:latest
script: |
git config --global --add safe.directory /workspace
git clone "$(tasks.parse-metadata.results.source-git-url)" /workspace
pushd /workspace
git checkout "$(tasks.parse-metadata.results.source-git-revision)"
- name: test
image: registry.redhat.io/openshift4/ose-cli:latest
env:
- name: KUBECONFIG
value: /tmp/kubeconfig
- name: KUBECONFIG_VALUE
valueFrom:
secretKeyRef:
name: $(tasks.provision-eaas-space-nessus.results.secretRef)
key: kubeconfig
- name: RAPIDAST_CLEANUP
value: "false" # namespace will be cleaned up automatically
- name: RAPIDAST_IMAGE
value: $(tasks.parse-metadata.results.component-container-image)
- name: RAPIDAST_SERVICEACCOUNT
value: namespace-manager # created by provision-env-with-ephemeral-namespace
workingDir: /workspace
volumeMounts:
- name: credentials
mountPath: /credentials
script: |
#!/bin/bash -ex
echo "$KUBECONFIG_VALUE" > "$KUBECONFIG"
oc whoami
yum install -y python3.12
python3.12 -m ensurepip
pip3 install -r requirements.txt -r requirements-dev.txt
pytest -s e2e-tests/test_nessus.py --json-report --json-report-summary --json-report-file $(results.TEST_RESULTS.path)
cat $(results.TEST_RESULTS.path)
160 changes: 160 additions & 0 deletions e2e-tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import os
import shutil
import tempfile
import time

import certifi
from kubernetes import client, config, utils, watch
from kubernetes.client.rest import ApiException

NAMESPACE = os.getenv("RAPIDAST_NAMESPACE", "") # e.g. rapidast--pipeline
SERVICEACCOUNT = os.getenv("RAPIDAST_SERVICEACCOUNT", "pipeline") # name of ServiceAccount used in rapidast pod
RAPIDAST_IMAGE = os.getenv("RAPIDAST_IMAGE", "quay.io/redhatproductsecurity/rapidast:development")
# delete resources created by tests
RAPIDAST_CLEANUP = os.getenv("RAPIDAST_CLEANUP", "True").lower() in ("true", "1", "t", "y", "yes")

MANIFESTS = "e2e-tests/manifests"


# monkeypatch certifi so that internal CAs are trusted
def where():
return os.getenv("REQUESTS_CA_BUNDLE", "/etc/pki/tls/certs/ca-bundle.crt")


certifi.where = where


def wait_until_ready(**kwargs):
corev1 = client.CoreV1Api()
w = watch.Watch()
timeout = kwargs.pop("timeout", 120)

start_time = time.time()

while time.time() - start_time < timeout:
try:
pods = corev1.list_namespaced_pod(namespace=NAMESPACE, **kwargs)
if len(pods.items) != 1:
raise RuntimeError(f"Unexpected number of pods {len(pods.items)} matching: {kwargs}")
pod = pods.items[0]

# Check if pod is ready by looking at conditions
if pod.status.conditions:
for condition in pod.status.conditions:
if condition.type == "Ready":
print(f"{pod.metadata.name} Ready={condition.status}")
if condition.status == "True":
return True

time.sleep(2)
except client.ApiException as e:
print(f"Error checking pod status: {e}")
return False


# simulates: $ oc logs -f <pod> | tee <file>
def tee_log(pod_name: str, filename: str):
corev1 = client.CoreV1Api()
w = watch.Watch()
with open(filename, "w", encoding="utf-8") as f:
for e in w.stream(corev1.read_namespaced_pod_log, name=pod_name, namespace=NAMESPACE):
if not isinstance(e, str):
continue # Watch.stream() can yield non-string types
f.write(e + "\n")
print(e)


def render_manifests(input_dir, output_dir):
shutil.copytree(input_dir, output_dir, dirs_exist_ok=True)
print(f"rendering manifests in {output_dir}")
print(f"using serviceaccount {SERVICEACCOUNT}")
# XXX should probably replace this with something like kustomize
for filepath in os.scandir(output_dir):
with open(filepath, "r", encoding="utf-8") as f:
contents = f.read()
contents = contents.replace("${IMAGE}", RAPIDAST_IMAGE)
contents = contents.replace("${SERVICEACCOUNT}", SERVICEACCOUNT)
with open(filepath, "w", encoding="utf-8") as f:
f.write(contents)


def setup_namespace():
global NAMESPACE # pylint: disable=W0603
# only try to create a namespace if env is set
if NAMESPACE == "":
NAMESPACE = get_current_namespace()
else:
create_namespace(NAMESPACE)
print(f"using namespace '{NAMESPACE}'")


def get_current_namespace() -> str:
try:
# Load the kubeconfig
config.load_config()

# Get the kube config object
_, active_context = config.list_kube_config_contexts()

# Return the namespace from current context
if active_context and "namespace" in active_context["context"]:
return active_context["context"]["namespace"]
return "default"

except config.config_exception.ConfigException:
# If running inside a pod
try:
with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r", encoding="utf-8") as f:
return f.read().strip()
except FileNotFoundError:
return "default"


def create_namespace(namespace_name: str):
config.load_config()
corev1 = client.CoreV1Api()
try:
corev1.read_namespace(namespace_name)
print(f"namespace {namespace_name} already exists")
except ApiException as e:
if e.status == 404:
print(f"creating namespace {namespace_name}")
namespace = client.V1Namespace(metadata=client.V1ObjectMeta(name=namespace_name))
corev1.create_namespace(namespace)
else:
raise e
except Exception as e: # pylint: disable=W0718
print(f"error reading namespace {namespace_name}: {e}")


def cleanup():
if RAPIDAST_CLEANUP:
os.system(f"kubectl delete -f {MANIFESTS}/")
# XXX oobtukbe does not clean up after itself
os.system("kubectl delete Task/vulnerable")


def new_kclient():
config.load_config()
return client.ApiClient()


class TestBase:
@classmethod
def setup_class(cls):
cls.tempdir = tempfile.mkdtemp()
cls.kclient = new_kclient()
render_manifests(MANIFESTS, cls.tempdir)
print(f"testing with image: {RAPIDAST_IMAGE}")
setup_namespace()
cleanup()

@classmethod
def teardown_class(cls):
# TODO teardown should really occur after each test, so the the
# resource count does not grown until quota reached
cleanup()

def create_from_yaml(self, path: str):
# simple wrapper to reduce repetition
utils.create_from_yaml(self.kclient, path, namespace=NAMESPACE, verbose=True)
47 changes: 47 additions & 0 deletions e2e-tests/manifests/nessus-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: nessus
labels:
app: nessus
spec:
replicas: 1
selector:
matchLabels:
app: nessus
template:
metadata:
labels:
app: nessus
spec:
imagePullSecrets:
- name: sfowler-nessus-pull-secret
containers:
- name: nessus
command:
- /opt/nessus/sbin/nessus-service
- --no-root
env:
- name: AUTO_UPDATE
value: "no"
image: quay.io/sfowler/nessus@sha256:5881d6928e52d6c536634aeba0bbb7d5aac2b53e77c17f725e4e5aff0054f772
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8834
readinessProbe:
exec:
command:
- /bin/bash
- -c
- |
#!/bin/bash
# curl -ks https://0.0.0.0:8834/server/status | python3 -c 'import sys, json; json.load(sys.stdin)["code"] == 200 or sys.exit(1)'
curl -ks https://0.0.0.0:8834/server/status | python3 -c 'import sys, json; json.load(sys.stdin)["detailed_status"]["login_status"] == "allow" or sys.exit(1)'
initialDelaySeconds: 20
periodSeconds: 10
failureThreshold: 32
resources:
limits:
cpu: 2
memory: 4Gi
19 changes: 19 additions & 0 deletions e2e-tests/manifests/nessus-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: nessus
name: nessus
spec:
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
- port: 8834
protocol: TCP
targetPort: 8834
selector:
app: nessus
sessionAffinity: None
type: ClusterIP
51 changes: 51 additions & 0 deletions e2e-tests/manifests/rapidast-nessus-configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
apiVersion: v1
data:
config.yaml: |+
config:
# WARNING: `configVersion` indicates the schema version of the config file.
# This value tells RapiDAST what schema should be used to read this configuration.
# Therefore you should only change it if you update the configuration to a newer schema
configVersion: 5
# all the results of all scanners will be stored under that location
# base_results_dir: "./results"
# `application` contains data related to the application, not to the scans.
application:
shortName: "nessus-test-1.0"
# url: "<Mandatory. root URL of the application>" # XXX unused for nessus
# `general` is a section that will be applied to all scanners.
# Any scanner can override a value by creating an entry of the same name in their own configuration
general:
# container:
# type: podman
# remove `authentication` entirely for unauthenticated connection
authentication:
type: "oauth2_rtoken"
parameters:
client_id: "cloud-services"
token_endpoint: "<token retrieval URL>"
# rtoken_from_var: "RTOKEN" # referring to a env defined in general.environ.envFile
#preauth: false # set to true to pregenerate a token, and stick to it (no refresh)
# `scanners' is a section that configures scanning options
scanners:
nessus_foobar:
server:
# url: https://10.0.108.143:8834/ # URL of Nessus instance
url: https://nessus:8834/ # URL of Nessus instance
username_from_var: NESSUS_USER # Nessus credentials
password_from_var: NESSUS_PASSWORD
scan:
name: nessus-test # name of new scan to create
folder: nessus-tests # name of folder in to contain scan
policy: "discovery" # policy used for scan
timeout: 600 # timeout limit in seconds to complete scan
targets:
- 127.0.0.1
kind: ConfigMap
metadata:
name: rapidast-nessus
Loading

0 comments on commit 64cb1db

Please sign in to comment.