Secure Software Supply Chains and the Tanzu Application Platform
Table of Contents
If you are a company that makes software, then you have a software supply chain, whether you want one or not. Building software is challenging, even without thinking about where all the underlying dependencies and other software comes from, and what’s in it, never mind cataloguing and checksumming it all, and being able to replace it within a few hours.
This is all hard work, work that many companies spend thousands and thousands of engineering hours on trying to build themselves, often unsuccessfully. Other companies simply don’t have the people power–the time, the resources–to even try to implement secure pipelines.
What’s a secure software supply chain?
A “secure software supply chain” (SSSC) is…
…a fancy way of saying “we know all the components that went into building and deploying this software and trust those components.” It also includes the actual CI/CD pipeline that you trust and that’s resistant to third parties including malicious code, as we’ve seen happen in recent years. - Tanzu Blog
Here are some outcomes organizations are looking for with regards to SSSC:
- “We need to be able to deploy code to our staging and production environments reliably every time”
- “When there is a CVE for one of our applications or dependencies, we need to be able to remediate the problem within 24 hours”
- “We must ensure our software is validated during the build process and that it is built upon known secure images”
If you want to find out more about secure software supply chains, take a look at this learning path provided by VMware Tanzu.
The Tanzu Application Platform
I’ve talked about the Tanzu Application Platform (TAP) on this blog before. Suffice it to say that Kubernetes can do more than just containers, we can teach to do anything, and TAP is a way to show Kubernetes how to manage and secure applications; to turn it into more than just a “container orchestration engine”.
With TAP we use Kubernetes as a base platform that we add on to, and then turn into a full blown application platform…a modular system that understands how to deploy, manage and secure applications on its own, without having to be told what to do (unless we want to).
TAP Supply Chains
I’ve got the Tanzu Application Platform deployed into a single cluster (in this case Minikube running on my Windows workstation). It’s has a couple of software supply chains installed by default.
As you can see, I’m asking the Kubernetes API what it knows about “clustersupplychains”, i.e. TAP and its components are NATIVE to Kubernetes–we’ve taught Kubernetes how to do supply chains (and more).
$ k get clustersupplychains.carto.run
NAME READY REASON AGE
basic-image-to-url True Ready 4d23h
source-to-url True Ready 4d23h
With TAP 1.1 you get a few default supply chains, e.g. basic-image-to-url and source-to-url.
source-to-url is the easiest one to understand. This supply chain does the following:
- Watch a git repository
- When there are changes, build a new image using that code (no Dockerfile anywhere)
- Deploy the new image
This all happens from within Kubernetes, and can be across multiple clusters, each with specific duties. (In this case I just have a single cluster though.)
ClusterSupplyChains
Let’s look at the YAML that defines the ClusterSupplyChain.
NOTE: I’m trying out the “neat” plugin for kubectl here. It removes some of the extra things you see when pulling the YAML from k8s. A few other things I removed by hand.
$ k neat get -- clustersupplychain source-to-url
apiVersion: carto.run/v1alpha1
kind: ClusterSupplyChain
metadata:
name: source-to-url
spec:
params:
- default: main
name: gitops_branch
- default: supplychain
name: gitops_user_name
- default: supplychain
name: gitops_user_email
- default: supplychain@cluster.local
name: gitops_commit_message
- default: ""
name: gitops_ssh_secret
resources:
- name: source-provider
params:
- name: serviceAccount
value: default
- name: gitImplementation
value: go-git
templateRef:
kind: ClusterSourceTemplate
name: source-template
- name: deliverable
params:
- name: registry
value:
repository: tap-inner-loop-1-1-full/supply-chain
server: somerepo.example.com
templateRef:
kind: ClusterTemplate
name: deliverable-template
- name: image-builder
params:
- name: serviceAccount
value: default
- name: clusterBuilder
value: default
- name: registry
value:
repository: tap-inner-loop-1-1-full/supply-chain
server: somerepo.example.com
sources:
- name: source
resource: source-provider
templateRef:
kind: ClusterImageTemplate
name: kpack-template
- images:
- name: image
resource: image-builder
name: config-provider
params:
- name: serviceAccount
value: default
templateRef:
kind: ClusterConfigTemplate
name: convention-template
- configs:
- name: config
resource: config-provider
name: app-config
templateRef:
kind: ClusterConfigTemplate
name: config-template
- configs:
- name: config
resource: app-config
name: config-writer
params:
- name: serviceAccount
value: default
- name: registry
value:
repository: tap-inner-loop-1-1-full/supply-chain
server: somerepo.example.com
templateRef:
kind: ClusterTemplate
name: config-writer-template
selector:
apps.tanzu.vmware.com/workload-type: web
Now we can take that output and plug it into the Cartographer live editor, and it will show us a nice diagram which depicts the supply chain flow.
NOTE: Cartographer is the open source project that underlies some of TAP…it’s the k8s native component that ties all the disparate, separate functions together into a pipeline.
Here’s a larger version. The arrows mean “depends on”, as opposed to the directional flow.
If you look into the YAML, we can see the first section under resources is source-provider
.
SNIP!
resources:
- name: source-provider
params:
- name: serviceAccount
value: default
- name: gitImplementation
value: go-git
templateRef:
kind: ClusterSourceTemplate
name: source-template
SNIP!
The kind “ClusterSourceTemplate” exists in the cluster.
$ k get clustersourcetemplates.carto.run
NAME AGE
delivery-source-template 4d23h
source-scanner-template 4d23h
source-template 4d23h
testing-pipeline 4d23h
Above we can see there is one called “source-template”.
$ k neat get -- clustersourcetemplates.carto.run source-template -oyaml
apiVersion: carto.run/v1alpha1
kind: ClusterSourceTemplate
metadata:
name: source-template
spec:
params:
- default: default
name: serviceAccount
- default: go-git
name: gitImplementation
revisionPath: .status.artifact.revision
urlPath: .status.artifact.url
ytt: |
#@ load("@ytt:data", "data")
#@ def merge_labels(fixed_values):
#@ labels = {}
#@ if hasattr(data.values.workload.metadata, "labels"):
#@ labels.update(data.values.workload.metadata.labels)
#@ end
#@ labels.update(fixed_values)
#@ return labels
#@ end
#@ def param(key):
#@ if not key in data.values.params:
#@ return None
#@ end
#@ return data.values.params[key]
#@ end
---
#@ if hasattr(data.values.workload.spec.source, "git"):
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
name: #@ data.values.workload.metadata.name
labels: #@ merge_labels({ "app.kubernetes.io/component": "source" })
spec:
interval: 1m0s
url: #@ data.values.workload.spec.source.git.url
ref: #@ data.values.workload.spec.source.git.ref
gitImplementation: #@ data.values.params.gitImplementation
ignore: |
!.git
#@ if/end param("gitops_ssh_secret"):
secretRef:
name: #@ param("gitops_ssh_secret")
#@ end
#@ if hasattr(data.values.workload.spec.source, "image"):
apiVersion: source.apps.tanzu.vmware.com/v1alpha1
kind: ImageRepository
metadata:
name: #@ data.values.workload.metadata.name
labels: #@ merge_labels({ "app.kubernetes.io/component": "source" })
spec:
serviceAccount: #@ data.values.params.serviceAccount
interval: 1m0s
image: #@ data.values.workload.spec.source.image
#@ end
A lot of the above YAML is a “template” built with Carvel’s YAML templating tool, ytt, which may look a little unusual to those who haven’t seen ytt before.
From this section of the YAML, it’s somewhat obvious that this is a “if/then” template, and IF the source comes from GIT, then use “kind: GitRepository”. (And if it’s an IMAGE then use ImageRepository.)
SNIP!
#@ if hasattr(data.values.workload.spec.source, "git"):
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
SNIP!
The demo app, tanzu-java-web-app, is using a git repository, as defined in its YAML manifest / k8s object. Note the “source.git” section.
$ k neat get -- workloads.carto.run tanzu-java-web-app -oyaml
apiVersion: carto.run/v1alpha1
kind: Workload
SNIP!
source:
git:
ref:
branch: main
url: https://github.com/sample-accelerators/tanzu-java-web-app
So there must be a manifest for that…yep.
$ k get gitrepositories.source.toolkit.fluxcd.io
NAME URL READY STATUS
AGE
tanzu-java-web-app https://github.com/sample-accelerators/tanzu-java-web-app True Fetched revision: main/f5cf96ef23f3fddba94616112dfad882882aabe4 4d23h
Note the above is “…fluxcd.io”. TAP is using parts of the open source flux project, in this case specifically the flux capability to get code from a git repository.
We can continue this k8s sleuthing to follow the entire software supply chain. So far we’ve just looked at how source code is retrieved.
An important point is that this is all programmable, so much so that we can simply use the Cartographer live editor and paste in the YAML definition and it can easily produce an image.
This is also what is visualized in the TAP web interface.
Conclusion
Here we’ve sleuthed through one stage of one of the supply chains, and looked at how we can visualize the chains with Cartographer’s live editor and the TAP GUI. In the next post we’ll create a custom supply chain.