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