Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .github/workflows/release-keyvault-cert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Download certificate from keyvault and publish it as release

on:
workflow_dispatch:
inputs:
branch:
description: 'branch to build'
type: string
required: false
default: 'main'
schedule:
# Each day at 5am UTC
- cron: '0 5 * * *'

jobs:
create-cert-release:
runs-on: ubuntu-24.04
permissions:
# allow the action to create a release
contents: write
id-token: write
steps:
- name: Check out the bakery repo
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch != '' && inputs.branch || 'main' }}
path: bakery
- name: build
id: build
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
bakery: ${{ github.repository }}

KEYVAULT_CERT_NAME: ${{ secrets.KEYVAULT_CERT_NAME }}
AZURE_KEYVAULT_URL: https://${{ secrets.KEYVAULT_NAME }}.vault.azure.net/

AZURE_FEDERATED_TOKEN_FILE: /tmp/azure-oidc-token
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_LOG_LEVEL: verbose
AZURE_KEYVAULT_PKCS11_DEBUG: 1
PKCS11_MODULE_PATH: /usr/lib64/pkcs11/azure-keyvault-pkcs11.so
run: |
cd bakery
./flatcar-container-wrap.sh \
'cert_name="${bakery//\//-}.crt" && \
curl -sSL \
-H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=api://AzureADTokenExchange" \
| jq -r '.value' | sudo tee "$AZURE_FEDERATED_TOKEN_FILE" && \
p11-kit export-object "pkcs11:token=$KEYVAULT_CERT_NAME;type=cert" | sudo tee "$cert_name"'
- name: create a new release
uses: softprops/action-gh-release@v2
with:
make_latest: true
tag_name: signing_cert
body: |
The public key used for verification of sysexts produced and signed
in sysext-bakery. You should place this key to one of the verity.d
directories (e.g. /etc/verity.d/) to trust this key on your system.
files: |
bakery/*.crt
18 changes: 17 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
permissions:
# allow the action to create a release
contents: write
id-token: write
steps:
- name: Set up qemu / binmft misc for cross-platform builds
uses: docker/setup-qemu-action@v3
Expand All @@ -78,15 +79,30 @@ jobs:
jq \
squashfs-tools \
xz-utils \
erofs-utils
erofs-utils \
skopeo

- name: build
id: build
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
KEYVAULT_CERT_NAME: ${{ secrets.KEYVAULT_CERT_NAME }}
AZURE_KEYVAULT_URL: https://${{ secrets.KEYVAULT_NAME }}.vault.azure.net/

AZURE_FEDERATED_TOKEN_FILE: /tmp/azure-oidc-token
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_LOG_LEVEL: verbose
AZURE_KEYVAULT_PKCS11_DEBUG: 1
PKCS11_MODULE_PATH: /usr/lib64/pkcs11/azure-keyvault-pkcs11.so
run: |
pushd bakery
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
./release.sh ${{ matrix.release }}
./flatcar-container-wrap.sh \
"sudo -E ./sign_sysext_keyvault.sh ${{ matrix.release }} ${{ secrets.KEYVAULT_CERT_NAME }}"

- name: create a new release
uses: softprops/action-gh-release@v2
Expand Down
18 changes: 18 additions & 0 deletions flatcar-container-wrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

FLATCAR_SDK_CONTAINER="ghcr.io/flatcar/flatcar-sdk-all"
LATEST_FLATCAR_SDK_VERSION=$(skopeo list-tags docker://${FLATCAR_SDK_CONTAINER} | \
jq -r '.Tags[]' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n1)
EXCLUDED_VARS="^(DIRSTACK|HOME|HOSTNAME|LOGNAME|MAIL|OLDPWD|PATH|PWD|USER|USERNAME|XDG_DATA_DIRS|BASH.*|EPOCH|RANDOM)$"

ARGS=("--rm" "--volume" "/tmp:/tmp" "--volume" "$PWD:/app" --entrypoint "" --workdir "/app" "--privileged" "--user" "sdk")
# Extra "sh -c" is needed to only export the exported variables
for VARNAME in $(bash -c "compgen -v | grep -vE \"$EXCLUDED_VARS\""); do
set +u
VAL="${!VARNAME}"
set -u
ARGS+=("--env" "${VARNAME}=${VAL}")
done

docker run "${ARGS[@]}" "${FLATCAR_SDK_CONTAINER}:$LATEST_FLATCAR_SDK_VERSION" \
bash -c "sudo ./setup_scripts_repo.sh && $*"
166 changes: 166 additions & 0 deletions lib/setup_azure_keyvault.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#!/usr/bin/env bash
set -euo pipefail

libroot="$(dirname "$(readlink -f "$0")")"

if [[ $# -eq 0 || $# -ge 3 ]]; then
echo "Usage: $0 <RG name> [<AZURE_SUBSCRIPTION_ID>]"
echo "Note: AZURE_SUBSCRIPTION_ID can also be specified via environment variable"
exit 1
fi

set +u
AZURE_SUBSCRIPTION_ID="${AZURE_SUBSCRIPTION_ID:-$2}"
set -u
RG_NAME="$1"
LOCATION="westeurope"
KV_NAME="sysext-bakery"
CERT_NAME="sysext-bakery-signing"
IDENTITY_NAME="sysext-bakery-github-actions"
FED_CRED_NAME="gh-main"
GITHUB_REPO=$(source "${libroot}/libbakery.sh"; echo "$bakery") # owner/repo
GITHUB_BRANCH="main"

echo "Setting subscription..."
az account set --subscription "$AZURE_SUBSCRIPTION_ID"
TENANT_ID="$(az account show --query tenantId -o tsv)"

echo "Creating resource group $RG_NAME in $LOCATION..."
az group create -n "$RG_NAME" -l "$LOCATION" -o none

# ============
# KEY VAULT
# ============
echo "Creating Key Vault $KV_NAME..."
# Enable RBAC authorization and purge protection (soft-delete is on by default)
#
az keyvault show -n "$KV_NAME" -g "$RG_NAME" -o none 2>/dev/null || \
az keyvault create \
--name "$KV_NAME" \
--resource-group "$RG_NAME" \
--location "$LOCATION" \
--sku standard \
--enable-purge-protection true \
--enable-rbac-authorization true \
-o none
KV_ID="$(az keyvault show -n "$KV_NAME" -g "$RG_NAME" --query id -o tsv)"

# 2) Get your Azure AD object id (the caller shown in the error)
# This works when you're logged in as a user (not a service principal)
USER_OID=$(az ad signed-in-user show --query id -o tsv)
USER_NAME=$(az ad signed-in-user show --query userPrincipalName -o tsv)
RG_ID=$(az group show -n "$RG_NAME" --query id -o tsv)

# 3) Grant yourself a data-plane role that includes certificate create
# Pick ONE of the two; Administrator is broader than Certificates Officer.
for role in "Key Vault Administrator" "Owner"; do
echo "Granting $role role in RG $RG_NAME to $USER_NAME"
az role assignment create \
--assignee-object-id "$USER_OID" \
--assignee-principal-type User \
--role "$role" \
--scope "$RG_ID" \
-o none
done

echo "Waiting 30 seconds for the permissions to apply. If the script fails, please wait and run it again."
sleep 30

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any chance to poll for the completion instead of defining a wait time that may be exceeded?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just fixed it. There seems to be no way to poll if the rights are actually effective other than trying to use them. So, instead I've just added retry loop when actually using them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not pushed yet, or?


# ============
# CERTIFICATE
# ============
echo "Creating self-signed certificate $CERT_NAME in $KV_NAME (default policy)..."
DEFAULT_POLICY="$(az keyvault certificate get-default-policy)"
az keyvault certificate show --vault-name "$KV_NAME" -n "$CERT_NAME" -o none 2>/dev/null || \
az keyvault certificate create \
--vault-name "$KV_NAME" \
--name "$CERT_NAME" \
--policy "$DEFAULT_POLICY" \
-o none

# ===============================
# USER-ASSIGNED MANAGED IDENTITY
# ===============================
echo "Creating user-assigned managed identity $IDENTITY_NAME..."
az identity show --name "$IDENTITY_NAME" --resource-group "$RG_NAME" -o none 2>/dev/null || \
az identity create \
--name "$IDENTITY_NAME" \
--resource-group "$RG_NAME" \
--location "$LOCATION" \
-o none

IDENTITY_JSON="$(az identity show -n "$IDENTITY_NAME" -g "$RG_NAME")"
IDENTITY_CLIENT_ID="$(echo "$IDENTITY_JSON" | jq -r .clientId)"
IDENTITY_PRINCIPAL_ID="$(echo "$IDENTITY_JSON" | jq -r .principalId)"
# IDENTITY_ID="$(echo "$IDENTITY_JSON" | jq -r .id)"

echo "Identity clientId: $IDENTITY_CLIENT_ID"
echo "Identity principalId: $IDENTITY_PRINCIPAL_ID"

# =====================================
# FEDERATED CREDENTIAL FOR GITHUB OIDC
# =====================================
echo "Creating federated credential $FED_CRED_NAME on identity $IDENTITY_NAME for repo $GITHUB_REPO branch ${GITHUB_BRANCH}..."
ISSUER="https://token.actions.githubusercontent.com"
AUDIENCE="api://AzureADTokenExchange"
SUBJECT="repo:${GITHUB_REPO}:ref:refs/heads/${GITHUB_BRANCH}"

az identity federated-credential show --name "$FED_CRED_NAME" --identity-name "$IDENTITY_NAME" --resource-group "$RG_NAME" -o none > /dev/null 2>&1 || \
az identity federated-credential create \
--name "$FED_CRED_NAME" \
--identity-name "$IDENTITY_NAME" \
--resource-group "$RG_NAME" \
--issuer "$ISSUER" \
--subject "$SUBJECT" \
--audiences "$AUDIENCE" \
-o none

# ==================================
# ROLE ASSIGNMENTS (Key Vault RBAC)
# ==================================

IDENTITY_NAME="sysext-bakery-github-actions"
PRINCIPAL_ID=$(az identity show -n "$IDENTITY_NAME" -g "$RG_NAME" --query principalId -o tsv)
for role in "Key Vault Crypto User" "Key Vault Secrets User" "Key Vault Reader"; do
echo "Granting $role role in RG $RG_NAME to $IDENTITY_NAME"
az role assignment create \
--assignee-object-id "$PRINCIPAL_ID" \
--assignee-principal-type ServicePrincipal \
--role "$role" \
--scope "$RG_ID" \
-o none
done

# az role assignment create \
# --assignee-object-id "$PRINCIPAL_ID" \
# --assignee-principal-type ServicePrincipal \
# --role "Reader" \
# --scope "$KV_ID"

echo "All done."
echo "---------------------------------------------"
echo "Key Vault: $KV_NAME"
echo "Certificate: $CERT_NAME"
echo "Managed Identity: $IDENTITY_NAME"
echo "Identity clientId: $IDENTITY_CLIENT_ID"
echo "Tenant ID: $TENANT_ID"
echo "Subscription ID: $AZURE_SUBSCRIPTION_ID"
echo "---------------------------------------------"
echo ""
echo "Please set these GitHub secrets on repo ${GITHUB_REPO}:"
echo ""
echo "---------------------------------------------"
echo "AZURE_CLIENT_ID : $IDENTITY_CLIENT_ID"
echo "AZURE_SUBSCRIPTION_ID : $AZURE_SUBSCRIPTION_ID"
echo "AZURE_TENANT_ID : $TENANT_ID"
echo "KEYVAULT_CERT_NAME : $CERT_NAME"
echo "KEYVAULT_NAME : $KV_NAME"
echo ""
echo "You can do it like this:"
echo ""
echo "echo '$IDENTITY_CLIENT_ID' | gh secret set --repo '$GITHUB_REPO' AZURE_CLIENT_ID"
echo "echo '$AZURE_SUBSCRIPTION_ID' | gh secret set --repo '$GITHUB_REPO' AZURE_SUBSCRIPTION_ID"
echo "echo '$TENANT_ID' | gh secret set --repo '$GITHUB_REPO' AZURE_TENANT_ID"
echo "echo '$CERT_NAME' | gh secret set --repo '$GITHUB_REPO' KEYVAULT_CERT_NAME"
echo "echo '$KV_NAME' | gh secret set --repo '$GITHUB_REPO' KEYVAULT_NAME"
echo "---------------------------------------------"
5 changes: 5 additions & 0 deletions repart.d/wrap-fs-in-ddi/10-root.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[Partition]
Type=root
Verity=data
VerityMatchKey=root
CopyBlocks=$FS_IMAGE
5 changes: 5 additions & 0 deletions repart.d/wrap-fs-in-ddi/20-verity.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[Partition]
Type=root-verity
Verity=hash
VerityMatchKey=root
Minimize=best
4 changes: 4 additions & 0 deletions repart.d/wrap-fs-in-ddi/30-signature.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[Partition]
Type=root-verity-sig
Verity=signature
VerityMatchKey=root
15 changes: 15 additions & 0 deletions setup_scripts_repo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

# clone scripts with a custom branch and set up symlinks,
# where the run_sdk_container normally mounts stuff
git -C /tmp clone --depth=1 https://github.com/flatcar/scripts.git -b danzatt/sign-sysexts-nopatch
sudo rm -rf /mnt/host/source
sudo mkdir -p /tmp/scripts/__build__/images
sudo ln -s /tmp/scripts/sdk_container /mnt/host/source
sudo ln -s /tmp/scripts/__build__/images /mnt/host/source/src/build
sudo ln -s /tmp/scripts/ /mnt/host/source/src/scripts

# reinstall packages changed in the branch
sudo emerge systemd azure-keyvault-pkcs11
sudo touch /etc/pkcs11/pkcs11.conf
echo "module: /usr/lib64/pkcs11/azure-keyvault-pkcs11.so" | sudo tee /etc/pkcs11/modules/azure-keyvault-pkcs11.module
Loading