Cosign и Jenkins
Понимаю, что это мало кому надо (или наоборот кому-то надо, но найти никак).
Я тут поднимал свой Registry для отгрузки docker images. Ничего суперсекретного, но есть просто некоторые вещи, которые мне давно было стыдно лить в паблик.
Я установил Harbor, настроил там зеркало кое-куда, собрал свой ansible-runner для быстрых заливок ключей на VPS. Заглянул в Harbor и увидел красный кружок рядом с образом и внутри красный крестик.
Наверно кому-то будет смешно, но я не знал, что images можно и иногда нужно подписывать. Я себя осознал как перфекциониста и потратил полдня на то, чтобы красные кружок с крестиком превратились в зеленый кружок с галочкой.
Идеи нет, только путь. К тому же собираю я все это дело у себя в K8s кластере.
Получился вот такой Pipeline для сборки образа внутри кубера, подписывания его с помощью cosign и отправки в свой registry:
#!/usr/bin/env groovy
def label = "k8s-${UUID.randomUUID().toString()}"
def home = "/home/jenkins"
def workspace = "${home}/workspace/ansible_image_build"
def workdir = "${workspace}/src/ansible_image_build"
def dockerImage = "ansible-runner"
def registryDomain = "registry.example.com"
def dockerRegistry = "registry.example.com/runners"
def version = "0.0.${env.BUILD_NUMBER}"
def imageDigest = ""
podTemplate(
label: label,
volumes: [
emptyDirVolume(mountPath: "/var/lib/containers/storage", memory: false),
emptyDirVolume(mountPath: "/.docker", memory: false)
],
containers: [
containerTemplate(
name: 'podman',
image: 'quay.io/containers/podman:v4.8.1',
ttyEnabled: true,
command: 'cat',
privileged: true,
envVars: [
secretEnvVar(key: 'DOCKERHUB_USER', secretName: 'registry-local', secretKey: 'access_username'),
secretEnvVar(key: 'DOCKERHUB_PASS', secretName: 'registry-local', secretKey: 'access_token')
],
),
containerTemplate(
name: 'cosign',
image: 'ghcr.io/sigstore/cosign/cosign:v2.1.1',
ttyEnabled: true,
privileged: true,
runAsUser: "1000",
runAsGroup: "1000",
command: 'cat',
envVars: [
secretEnvVar(key: 'COSIGN_KEY', secretName: 'cosign', secretKey: 'cosign.key'),
secretEnvVar(key: 'COSIGN_PASSWORD', secretName: 'cosign', secretKey: 'cosign.password'),
secretEnvVar(key: 'DOCKERHUB_USER', secretName: 'registry-local', secretKey: 'access_username'),
secretEnvVar(key: 'DOCKERHUB_PASS', secretName: 'registry-local', secretKey: 'access_token')
]
)
]) {
node(label) {
stage('Get Dockerfile') {
checkout scm: [$class: 'GitSCM',
branches: [[name: '*/main']],
userRemoteConfigs: [[url: 'http://gitea-http.gitea:3000/aladex/ansible-docker.git']]]
}
stage('Build and Push Image') {
container('podman') {
script {
sh "podman login -u \${DOCKERHUB_USER} -p \${DOCKERHUB_PASS} ${dockerRegistry}"
sh "podman build -t ${dockerImage} -f Dockerfile ."
sh "podman tag ${dockerImage} ${dockerRegistry}/${dockerImage}:latest"
sh "podman tag ${dockerImage} ${dockerRegistry}/${dockerImage}:${version}"
}
}
}
stage('Push Image') {
container('podman') {
script {
sh "podman push ${dockerRegistry}/${dockerImage}:latest"
sh "podman push ${dockerRegistry}/${dockerImage}:${version}"
imageDigest = sh(script: "podman inspect --format='{{.Digest}}' ${dockerRegistry}/${dockerImage}:${version}", returnStdout: true).trim()
}
}
}
stage('Sign Image') {
container('cosign') {
sh "set -x" // Включить режим отладки
sh "echo 'Running cosign version'"
sh "cosign version || echo 'Failed to run cosign version'"
sh "echo 'Logging in to ${registryDomain}'"
sh "cosign login ${registryDomain} --username=\$DOCKERHUB_USER --password=\$DOCKERHUB_PASS || echo 'Login failed'"
sh "echo 'Signning the hashed image'"
sh "cosign sign -y --key env://COSIGN_KEY ${dockerRegistry}/${dockerImage}:latest || echo 'Failed to sign hashed image'"
sh "cosign sign -y --key env://COSIGN_KEY ${dockerRegistry}/${dockerImage}:${version} || echo 'Failed to sign hashed image'"
sh "cosign sign -y --key env://COSIGN_KEY ${dockerRegistry}/${dockerImage}@${imageDigest} || echo 'Failed to sign hashed image'"
}
}
}
}
Не идеально, но вот основные моменты:
- Podman делает login в наш docker registry
- Далее берем хэш-сумму для подписи в cosign
- Логинимся в наш registry уже с помощью cosign
- Получаем на image и подписываем его
Неправильно, что мы подписываем конкретные версии нашего image. На это ругается cosign и best practice тут - использовать только хэш:
sh "cosign sign -y --key env://COSIGN_KEY ${dockerRegistry}/${dockerImage}@${imageDigest} || echo 'Failed to sign hashed image'"
И при заливке этого дела в OpenSource проектах или в Dockerhub я бы так и сделал. Но Harbor просит подпись для всех версий, поэтому я проигнорировал предупреждения и подписал latest, номер версии и непосредственно хэш.
И еще один важный момент. Я монтирую при создании пода папку /.docker. Туда записывает креды cosign. Без монтирования прав нет. Точнее права пропадают, если не запускать его под UID=1000. А если не запустить его под этим пользователем, то Jenkins не сможет записать лог и билд повиснет на использовании контейнера с cosign.
С таким набором можно спокойно билдить все у себя в кластере, не отдавая на откуп в Github или Gitlab. Особенно, если у вас поднят какой-то простой репозиторий в Gogs или Gitea, у которых есть вебхуки, но нет собственного CI.