-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
436 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.