Documentation / concourse
Concourse
Installs Concourse, for continuous integration and delivery: pipelines of resources, jobs and tasks, run in containers on workers.
The chart provides:
- Concourse 8.3, for amd64 and arm64, with its database in a PostgreSQL of
your own, such as a release of the
postgresqlchart, reached over TLS with the server’s certificate checked; - web nodes that meet the
restrictedPod Security Standard, with a read-only root filesystem; - workers in a release and a namespace of their own, privileged, as Concourse requires;
- keys, local users and the database credentials taken from Secrets you create, which the chart never creates, changes or prints;
- NetworkPolicies that admit workers to the TSA only from their own namespace, and admit nothing to the workers;
- Concourse’s image from the Mamluk registry, pinned by digest.
Workers and Pod Security
A worker must be privileged. It runs each build step in a container it creates itself, with a container runtime inside its Pod, and that needs privilege. Upstream’s own chart sets it on every worker, with no setting to turn it off. The worker container is the only privileged container in this chart: it uses no host path, no host network and no service account token, and a clean-up container before it runs as root without privilege.
A privileged worker can do anything its node can. A pipeline step
marked privileged, and any step on a compromised worker, has the access of
root on the node. Install workers in a namespace that holds nothing else, on
nodes you are content to give to builds, and let only those you trust
change pipelines.
For that reason the chart installs in two releases: the web nodes, in the
namespace of your choice under restricted, and the workers, in a namespace
of their own, which must allow the privileged Pod Security Standard. The
chart refuses to install both in one release, and refuses workers in
default or any kube- namespace.
Why the workers have a namespace of their own
Kubernetes applies these controls to a namespace as a whole, so keeping the workers apart confines the privilege to them:
- Pod Security. A namespace’s level applies to every Pod in it. In a
shared namespace the web nodes would lose
restricted; apart, only the workers’ namespace allows privileged Pods. - Admission policies. The Compliance and Assurance Convoy exempts whole namespaces, from every policy at once. With the workers alone in theirs, the exemption covers nothing else, and the web nodes stay under every policy.
- Who can run privileged Pods. Anyone able to create Pods in the workers’ namespace can run privileged containers. A namespace holding only the workers lets you grant that to as few people as possible, apart from those who operate the web nodes.
- Network. The web nodes admit connections to the TSA only from the workers’ namespace.
A namespace does not contain a privileged container: a worker can reach the
node it runs on. To keep builds away from other workloads, run the workers
on nodes of their own, with nodeSelector and tolerations in the
workers’ release.
The cost is two releases instead of one, the keys in two Secrets, and, if you run the Convoy, the exemption described below.
Admission policies
Any admission policy forbidding privileged containers will flag the
workers, including the Compliance and Assurance Convoy’s no-privileged.
That is by design: Concourse cannot run builds otherwise.
The Convoy does not exempt the workers’ namespace by default, since
Concourse is optional, and on a cluster without it a namespace of that name
would be open to anyone able to create it. If you run the Convoy and install
workers, exempt their namespace yourself. The Convoy then applies no policy
at all in that namespace, not only no-privileged, so keep it to the
workers and limit who can create Pods in it.
The Convoy exempts a namespace by its name, which it matches on the
kubernetes.io/metadata.name label. Kubernetes sets that label to the
namespace’s name and lets nobody change it, so the exemption cannot be
moved to another namespace by relabelling one. Do not exempt namespaces by
a label of your own: anyone able to edit a namespace could set it.
Do this before installing the workers: a policy set to Deny would
otherwise refuse the install. Set this in the values of the compliance
chart. The list replaces the default as a whole, so it repeats the default,
which may be longer in a later version of that chart: check it against
helm show values, and add any namespaces of your own.
policies:
exemptNamespaces:
- kube-system
- kube-node-lease
- kube-public
- tigera-operator
- calico-system
- calico-apiserver
- longhorn-system
- cert-manager
- kong
- kuma-system
- logging
- bahriya-system
- kyverno
- trivy-system
- ci-workers
Record the exemption, and the reason above, wherever your regime asks you to account for exceptions. An exempted namespace is not a passed control: the workers remain privileged.
Prerequisites
- Kubernetes 1.33 or later.
- A namespace for the web nodes, and one for the workers alone, allowed the
privilegedPod Security Standard. - A pull secret for
1x.axnamed1x-axin both, created from an image token as described in the access documentation. -
A PostgreSQL database owned by the user Concourse connects as. A release of the
postgresqlchart in the web nodes’ namespace:helm install pg tersane/postgresql --namespace ci --create-namespace \ --set bootstrap.database=concourse --set bootstrap.owner=concourse -
Keys, generated with Concourse’s own image, which needs nothing but a scratch directory:
mkdir keys docker run --rm -v "$PWD/keys:/keys" --entrypoint /usr/local/concourse/bin/concourse \ 1x.ax/tersane/core/concourse/concourse:<tag> generate-key -t rsa -f /keys/session_signing_key # the same with: -t ssh -f /keys/tsa_host_key, and -t ssh -f /keys/worker_keyThen the web nodes’ Secret, and the workers’:
kubectl --namespace ci create secret generic concourse-web-keys \ --from-file=session_signing_key=keys/session_signing_key \ --from-file=tsa_host_key=keys/tsa_host_key \ --from-file=authorized_worker_keys=keys/worker_key.pub kubectl --namespace ci-workers create secret generic concourse-worker-keys \ --from-file=worker_key=keys/worker_key \ --from-file=tsa_host_key.pub=keys/tsa_host_key.pub -
Local users, in a Secret: a comma-separated list of
name:password, where the password may be a bcrypt hash.kubectl --namespace ci create secret generic concourse-users \ --from-file=local_users=./local-users
Installation
The web nodes:
helm install concourse tersane/concourse --namespace ci \
--set web.externalURL=https://ci.example.com \
--set webKeys.existingSecret=concourse-web-keys \
--set auth.existingSecret=concourse-users --set 'auth.mainTeam={admin}' \
--set database.host=pg-postgresql-rw \
--set database.existingSecret=pg-postgresql-app \
--set database.caSecret=pg-postgresql-ca \
--set 'networkPolicy.workerNamespaces={ci-workers}' \
--set ingress.className=<class> --set ingress.tlsSecret=<certificate Secret> \
--version <chart version>
The workers, after exempting their namespace if you run the Convoy:
kubectl create namespace ci-workers
kubectl label namespace ci-workers pod-security.kubernetes.io/enforce=privileged
helm install concourse-workers tersane/concourse --namespace ci-workers \
--set web.enabled=false --set worker.enabled=true \
--set worker.tsaHost=concourse-tsa.ci.svc.cluster.local:2222 \
--set worker.keys.existingSecret=concourse-worker-keys \
--set networkPolicy.webNamespace=ci \
--version <chart version>
The web nodes migrate the database before they serve, in an init container.
Sign in at the external URL, or with fly:
fly --target ci login --concourse-url https://ci.example.com --username admin
Database
database.sslmode is verify-full by default: the server’s certificate
must be signed by the authority in database.caSecret and name
database.host. require encrypts without checking the certificate.
Workers
Each worker keeps its containers and caches on a volume of
worker.workDir.size. A worker that stops is retired: it finishes the
builds it is running, within worker.terminationGracePeriodSeconds, and
leaves; its volume is emptied before it registers again.
Network policy
The web nodes admit the user interface and API from
networkPolicy.ingressFrom, any source when empty, and the TSA only from the
namespaces in networkPolicy.workerNamespaces, matched by name.
The workers admit nothing: the web nodes reach them back through the SSH
connection each worker opens to the TSA. Their egress is DNS, the TSA, and
networkPolicy.workerExternalEgress, every address that is not private by
default, which is what builds fetch from: registries, Git hosts and package
mirrors. An air-gapped installation narrows it to its own mirrors, and adds
any address inside the cluster that builds use with
networkPolicy.extraEgress.
DNS is reached at the Pods labelled k8s-app: kube-dns in kube-system, on
port 53, which is where most distributions run CoreDNS. Where the cluster’s
DNS servers run in another namespace, carry other labels or listen on another
port, set networkPolicy.dns to match them, or the workers cannot resolve
names.
Uninstalling
helm uninstall concourse-workers --namespace ci-workers
helm uninstall concourse --namespace ci
The workers’ volumes are kept with the namespace; delete their claims to free them. The database is yours, and is untouched.
Parameters
Web
| Parameter | Description | Default |
|---|---|---|
web.enabled |
Install the web nodes | true |
web.replicas |
Web nodes | 1 |
web.externalURL |
The address users and fly reach Concourse at; required |
"" |
web.clusterName |
Shown on the dashboard | "" |
web.resources |
Resources of each web node | 200m and 512Mi requested, 2 CPUs and 2Gi limits |
web.service.type / web.service.port |
The web Service | ClusterIP / 8080 |
web.tsa.port / web.tsa.service.type / .nodePort |
The TSA and its Service | 2222 / ClusterIP / "" |
webKeys.existingSecret |
Secret with session_signing_key, tsa_host_key, authorized_worker_keys; required |
"" |
auth.existingSecret / auth.key |
Secret with the local users; required | "" / local_users |
auth.mainTeam |
Local users who own the main team; required | [] |
database.host / .port / .name |
The PostgreSQL server and database; host required | "" / 5432 / concourse |
database.existingSecret / .usernameKey / .passwordKey |
The Secret with the user and password; required | "" / username / password |
database.sslmode |
disable, require, verify-ca or verify-full |
verify-full |
database.caSecret / database.caKey |
The server’s certificate authority | "" / ca.crt |
ingress.enabled / .className / .annotations / .tlsSecret |
The Ingress | true / "" / {} / "" |
Workers
| Parameter | Description | Default |
|---|---|---|
worker.enabled |
Install workers | false |
worker.replicas |
Workers | 2 |
worker.tsaHost |
host:port of the web release’s TSA; required | "" |
worker.keys.existingSecret |
Secret with worker_key and tsa_host_key.pub; required |
"" |
worker.workDir.size / .storageClass |
Each worker’s volume | 50Gi / the default class |
worker.baggageclaimDriver |
overlay or naive |
overlay |
worker.resources |
Resources of each worker | 500m and 1Gi requested, 4 CPUs and 8Gi limits |
worker.terminationGracePeriodSeconds |
Time to finish builds when stopped | 3600 |
Common
| Parameter | Description | Default |
|---|---|---|
image.repository / image.tag |
The image | 1x.ax/tersane/core/concourse/concourse / the release’s |
imagePullPolicy / imagePullSecrets |
Pulling | IfNotPresent / [{name: 1x-ax}] |
logLevel |
debug, info, error or fatal |
info |
networkPolicy.enabled |
The NetworkPolicies | true |
networkPolicy.ingressFrom |
Sources of the user interface and API; empty admits any | [] |
networkPolicy.workerNamespaces |
Namespaces whose workers may reach the TSA; required on the web release | [] |
networkPolicy.webNamespace |
The web release’s namespace; required on the worker release | "" |
networkPolicy.workerExternalEgress |
Where builds may connect outside the cluster | every address that is not private |
networkPolicy.extraEgress |
Further egress rules for the workers | [] |
networkPolicy.dns.namespace / .podLabels / .ports |
The cluster’s DNS servers, which the workers reach | kube-system / {k8s-app: kube-dns} / [53] |
podSecurityContext / securityContext |
The web nodes’ security contexts | user 10000, restricted |
nodeSelector / tolerations |
Scheduling | {} / [] |