Vendor constructs and composes their application incl a BoB
π§
Step 1 : Template the ApplicationProfile
Generic highlevel components
Header:
Executables: Paths and arguments of executables that are expected to run.
Network Connections: Expected network connections (IP addresses, DNS names, ports, protocols).
File Access: Expected file access patterns (paths, read/write).
System Calls: Expected system calls.
Capabilities: Expected Linux capabilities.
Image information: Image ID, Image Tag.
For individual implementations, as we use kubescape, we can add
Rules: Exceptions and Allowed Containers
1A) For Kubernetes: Labels and Annotations
Assuming the bob will be deployed to K8s, labels and annotations are key. (Theoretically this idea transfers to plain Linux, however the installation mechanisms will be different)
Ideal Header
apiVersion: kind: BillOfBehavior metadata: annotations: labels: name: namespace:
<!-- ## 2) Building the BoB including a test
Lets take our ApplicationProfile and create a very simply bob
### Here and Back Again
A bob's tale: -->
Well, rather minimalistic, but a sketch how to extract the `values` and substitute them back in, this is using `Helm` syntax
```yaml
==> bob.values <==
namespace=default
name=webapp
clustername=honeycluster (inherited from kubescape config)
templatehash=d87cdd796
==> bob_helm.yaml <==
apiVersion: spdx.softwarecomposition.kubescape.io/v1beta1
kind: ApplicationProfile
metadata:
annotations:
kubescape.io/completion: complete
kubescape.io/instance-id: apiVersion-apps/v1/namespace-{{ .Release.Namespace }}/kind-ReplicaSet/name-{{ include "mywebapp.fullname" . }}-{{ .Values.bob.templateHash }}
kubescape.io/status: completed
kubescape.io/wlid: wlid://cluster-{{ .Values.bob.clusterName }}/namespace-{{ .Release.Namespace }}/deployment-{{ include "mywebapp.fullname" . }}
labels:
kubescape.io/instance-template-hash: {{ .Values.bob.templateHash | quote }}
kubescape.io/ignore: {{ .Values.bob.ignore | quote }}
kubescape.io/workload-api-group: apps
kubescape.io/workload-api-version: v1
kubescape.io/workload-kind: Deployment
kubescape.io/workload-name: {{ include "mywebapp.fullname" . }}
kubescape.io/workload-namespace: {{ .Release.Namespace }}
name: replicaset-{{ include "mywebapp.fullname" . }}-{{ .Values.bob.templateHash }}
namespace: {{ .Release.Namespace }}
==> bob_original.yaml <==
apiVersion: spdx.softwarecomposition.kubescape.io/v1beta1
kind: ApplicationProfile
metadata:
annotations:
kubescape.io/completion: complete
kubescape.io/instance-id: apiVersion-apps/v1/namespace-default/kind-ApplicationProfile/name-webapp-d87cdd796
kubescape.io/resource-size: '245'
kubescape.io/status: completed
kubescape.io/wlid: wlid://cluster-honeycluster/namespace-default/deployment-webapp
creationTimestamp: '2025-05-12T12:45:42Z'
In general: the body of the profile must be composable
You as the vendor know how you are packaging or releasing your artefact. In the case of a Helm
Chart, you
choose if you use ReplicaSet
DemonSet
or StatefulSet
and what else you bundle into it.
So, lets look first at parametrizing some things, then at composing them:
Example: Network directly allowed in bob.yaml
endpoints:
- direction: inbound
endpoint: :8080/ping.php
headers:
Host:
- localhost:8080
internal: false
methods:
- GET
becomes, e.g.
- direction: inbound
endpoint: :8080/ping.php
headers:
Host:
- {{ include "mywebapp.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}
- localhost:8080
internal: false
methods:
- GET
whether localhost is sufficiently general remains TBD, it seems to be.
The Test
Lets start simple and add to our deployment.yaml a second deployment that executes our curl given that the port is different, this might already trigger an alert.
==> deployment.yaml <==
...
app: testapp
kubescape.io/ignore: "true"
spec:
containers:
- name: curl-container
image: curlimages/curl:latest
command: ["/bin/sh", "-c"]
args:
- |
set -ex
SERVICE="{{ include "mywebapp.fullname" . }}"
NAMESPACE="{{ .Release.Namespace }}"
PORT="{{ .Values.service.port }}"
TARGET_IP="{{ .Values.bob.targetIp }}"
URL="${SERVICE}.${NAMESPACE}.svc.cluster.local:${PORT}/ping.php?ip=${TARGET_IP}"
RESPONSE=$(curl -s "$URL")
echo "$RESPONSE"
echo "$RESPONSE" | grep -q "Ping results for ${TARGET_IP}"
...
Level up your Server Side game β Join 11,000 engineers who receive insightful learning materials straight to their inbox