Skip to content
Merged
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
29 changes: 29 additions & 0 deletions .github/actions/build-flavor/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ inputs:
Token for the container registry. A composite action has no secrets
context, so this has to be passed in by the calling workflow.
required: true
repo_client_crt:
description: PEM contents of the client certificate for the gated pgEdge repositories.
required: true
repo_client_key:
description: PEM contents of the matching private key.
required: true
dry_run:
description: When true, resolve and print the build without pushing
required: false
Expand Down Expand Up @@ -61,6 +67,23 @@ runs:
go-version: '1.25'
cache-dependency-path: tests/go.sum

# Written to files because bake passes secrets by path. BuildKit mounts
# them into the RUNs that install packages and never writes them to a
# layer, so the published image carries no credentials.
- name: Write the repository client certificate
shell: bash
env:
REPO_CLIENT_CRT_PEM: ${{ inputs.repo_client_crt }}
REPO_CLIENT_KEY_PEM: ${{ inputs.repo_client_key }}
run: |
set -o errexit -o pipefail -o nounset
: "${REPO_CLIENT_CRT_PEM:?is empty or unset}"
: "${REPO_CLIENT_KEY_PEM:?is empty or unset}"
install -d -m 0700 "${RUNNER_TEMP}/pgedge-certs"
install -m 0600 /dev/null "${RUNNER_TEMP}/pgedge-certs/client.key"
printf '%s\n' "$REPO_CLIENT_CRT_PEM" > "${RUNNER_TEMP}/pgedge-certs/client.crt"
printf '%s\n' "$REPO_CLIENT_KEY_PEM" > "${RUNNER_TEMP}/pgedge-certs/client.key"

- name: Build and push by digest
id: build
shell: bash
Expand All @@ -79,6 +102,10 @@ runs:

args=(
--file pgedge.docker-bake.hcl
# The certificate lives outside the bake file's directory, so bake
# needs the entitlement to read it. Scoped to the directory the step
# above creates rather than all of RUNNER_TEMP.
--allow "fs.read=${RUNNER_TEMP}/pgedge-certs"
--set "default.platform=linux/${arch}"
--set "default.tags="
--metadata-file metadata.json
Expand All @@ -99,6 +126,8 @@ runs:
"POSTGRES_MAJOR_VERSION=$(jq -r '.postgres_major' <<< "$CELL")"
"TARGET=${target}"
"TAG=${REPO}"
"REPO_CLIENT_CRT=${RUNNER_TEMP}/pgedge-certs/client.crt"
"REPO_CLIENT_KEY=${RUNNER_TEMP}/pgedge-certs/client.key"
)
while IFS= read -r kv; do
envs+=("$kv")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/build_images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ jobs:
cell: ${{ toJSON(matrix.cell) }}
repo: ${{ inputs.pgedge_image_repo }}
registry_token: ${{ secrets.GITHUB_TOKEN }}
repo_client_crt: ${{ secrets.PGEDGE_REPO_CLIENT_CRT }}
repo_client_key: ${{ secrets.PGEDGE_REPO_CLIENT_KEY }}
dry_run: ${{ inputs.pgedge_image_dry_run }}
no_cache: ${{ inputs.pgedge_image_no_cache }}

Expand Down Expand Up @@ -190,6 +192,8 @@ jobs:
cell: ${{ toJSON(matrix.cell) }}
repo: ${{ inputs.pgedge_image_repo }}
registry_token: ${{ secrets.GITHUB_TOKEN }}
repo_client_crt: ${{ secrets.PGEDGE_REPO_CLIENT_CRT }}
repo_client_key: ${{ secrets.PGEDGE_REPO_CLIENT_KEY }}
dry_run: ${{ inputs.pgedge_image_dry_run }}
no_cache: ${{ inputs.pgedge_image_no_cache }}

Expand Down Expand Up @@ -246,6 +250,8 @@ jobs:
cell: ${{ toJSON(matrix.cell) }}
repo: ${{ inputs.pgedge_image_repo }}
registry_token: ${{ secrets.GITHUB_TOKEN }}
repo_client_crt: ${{ secrets.PGEDGE_REPO_CLIENT_CRT }}
repo_client_key: ${{ secrets.PGEDGE_REPO_CLIENT_KEY }}
dry_run: ${{ inputs.pgedge_image_dry_run }}
no_cache: ${{ inputs.pgedge_image_no_cache }}

Expand Down Expand Up @@ -307,6 +313,8 @@ jobs:
cell: ${{ toJSON(matrix.cell) }}
repo: ${{ inputs.pgedge_image_repo }}
registry_token: ${{ secrets.GITHUB_TOKEN }}
repo_client_crt: ${{ secrets.PGEDGE_REPO_CLIENT_CRT }}
repo_client_key: ${{ secrets.PGEDGE_REPO_CLIENT_KEY }}
dry_run: ${{ inputs.pgedge_image_dry_run }}
no_cache: ${{ inputs.pgedge_image_no_cache }}

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ tests/tests
# Scratch written by the image build (bake metadata, per-arch digests)
digests/
metadata.json

# Default paths for the gated-repository client certificate in
# pgedge.docker-bake.hcl. Never commit these.
client.crt
client.key
100 changes: 95 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ useradd -u ${POSTGRES_USER_ID} -m postgres -s /bin/bash
dnf install -y epel-release dnf
dnf config-manager --set-enabled crb
dnf update -y --allowerasing
dnf install -y https://dnf.pgedge.com/reporpm/pgedge-release-latest.noarch.rpm
# downloads.pgedge.com, not dnf.pgedge.com: the gated repository cannot serve
# the package that configures access to it.
dnf install -y https://downloads.pgedge.com/reporpm/pgedge-release-latest.noarch.rpm
if [[ -n "${PACKAGE_RELEASE_CHANNEL}" ]]; then
sed -i "s|release|${PACKAGE_RELEASE_CHANNEL}|g" /etc/yum.repos.d/pgedge.repo
fi
Expand All @@ -51,13 +53,35 @@ ARG POSTGRES_MAJOR_VERSION

COPY packagelists/${TARGETARCH}/${POSTGRES_PACKAGE_LIST_FILE} /usr/share/pgedge/packages.txt

RUN <<EOF
RUN --mount=type=secret,id=repo_client_crt \
--mount=type=secret,id=repo_client_key \
<<EOF
#!/usr/bin/env bash

set -o errexit
set -o pipefail
set -o nounset

# The pgEdge repositories authenticate clients with mutual TLS. BuildKit mounts
# the secrets for this RUN alone and never writes them to a layer; the copies
# pgedge-subscription-manager installs are removed, and the repositories
# disabled again, before this layer is committed. So the image carries no
# credentials and no repository it cannot reach.
trap 'rm -f /etc/pki/pgedge/client.crt /etc/pki/pgedge/client.key; dnf config-manager --set-disabled "pgedge*" >/dev/null 2>&1 || true' EXIT

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.

Non-blocking suggestion: we could add an option to the pgedge-subscription-manager tool to avoid copying the certificates to a new location, such as --no-copy. That would be slightly less risky.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Okay. I'll open a separate ticket for it.


# A chained stage starts FROM a published parent built before the repositories
# were gated: its pgedge-release predates pgedge-subscription-manager and its
# repository definition is enabled, pointing at a host it can no longer reach.
# Disable those definitions and replace the package first, so neither this
# install nor the channel rewrite below depends on when the parent was built.
dnf config-manager --set-disabled "pgedge*" >/dev/null 2>&1 || true
command -v pgedge-subscription-manager >/dev/null 2>&1 \
|| dnf install -y --disablerepo="pgedge*" \
https://downloads.pgedge.com/reporpm/pgedge-release-latest.noarch.rpm


pgedge-subscription-manager install /run/secrets/repo_client_crt /run/secrets/repo_client_key --force

xargs dnf install -y < /usr/share/pgedge/packages.txt
# Patch any OS packages (including transitive dependencies pulled in above)
# to the latest available errata so the image ships with security fixes.
Expand Down Expand Up @@ -119,13 +143,33 @@ USER root

COPY packagelists/${TARGETARCH}/${PACKAGE_LIST_FILE} /usr/share/pgedge/packages.txt

RUN <<EOF
RUN --mount=type=secret,id=repo_client_crt \
--mount=type=secret,id=repo_client_key \
<<EOF
#!/usr/bin/env bash

set -o errexit
set -o pipefail
set -o nounset

# The pgEdge repositories authenticate clients with mutual TLS. BuildKit mounts
# the secrets for this RUN alone and never writes them to a layer; the copies
# pgedge-subscription-manager installs are removed, and the repositories
# disabled again, before this layer is committed. So the image carries no
# credentials and no repository it cannot reach.
trap 'rm -f /etc/pki/pgedge/client.crt /etc/pki/pgedge/client.key; dnf config-manager --set-disabled "pgedge*" >/dev/null 2>&1 || true' EXIT

# A chained stage starts FROM a published parent built before the repositories
# were gated: its pgedge-release predates pgedge-subscription-manager and its
# repository definition is enabled, pointing at a host it can no longer reach.
# Disable those definitions and replace the package first, so neither this
# install nor the channel rewrite below depends on when the parent was built.
dnf config-manager --set-disabled "pgedge*" >/dev/null 2>&1 || true
command -v pgedge-subscription-manager >/dev/null 2>&1 \
|| dnf install -y --disablerepo="pgedge*" \
https://downloads.pgedge.com/reporpm/pgedge-release-latest.noarch.rpm


# The repo config is inherited from the parent image, which may have been built
# from a different channel, so re-point it here rather than relying on the base
# stage. Idempotent: once rewritten there is no "release" left to match.
Expand All @@ -135,6 +179,8 @@ fi

# A delta: re-pinning a package the parent's "dnf update -y" has moved past
# would be a downgrade, which dnf refuses.
pgedge-subscription-manager install /run/secrets/repo_client_crt /run/secrets/repo_client_key --force
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

xargs dnf install -y < /usr/share/pgedge/packages.txt
dnf update -y
dnf clean all
Expand All @@ -161,13 +207,33 @@ USER root

COPY packagelists/${TARGETARCH}/${STANDARD_PACKAGE_LIST_FILE} /usr/share/pgedge/packages.txt

RUN <<EOF
RUN --mount=type=secret,id=repo_client_crt \
--mount=type=secret,id=repo_client_key \
<<EOF
#!/usr/bin/env bash

set -o errexit
set -o pipefail
set -o nounset

# The pgEdge repositories authenticate clients with mutual TLS. BuildKit mounts
# the secrets for this RUN alone and never writes them to a layer; the copies
# pgedge-subscription-manager installs are removed, and the repositories
# disabled again, before this layer is committed. So the image carries no
# credentials and no repository it cannot reach.
trap 'rm -f /etc/pki/pgedge/client.crt /etc/pki/pgedge/client.key; dnf config-manager --set-disabled "pgedge*" >/dev/null 2>&1 || true' EXIT

# A chained stage starts FROM a published parent built before the repositories
# were gated: its pgedge-release predates pgedge-subscription-manager and its
# repository definition is enabled, pointing at a host it can no longer reach.
# Disable those definitions and replace the package first, so neither this
# install nor the channel rewrite below depends on when the parent was built.
dnf config-manager --set-disabled "pgedge*" >/dev/null 2>&1 || true
command -v pgedge-subscription-manager >/dev/null 2>&1 \
|| dnf install -y --disablerepo="pgedge*" \
https://downloads.pgedge.com/reporpm/pgedge-release-latest.noarch.rpm


# The repo config is inherited from the parent image, which may have been built
# from a different channel, so re-point it here rather than relying on the base
# stage. Idempotent: once rewritten there is no "release" left to match.
Expand All @@ -177,6 +243,8 @@ fi

# A delta: re-pinning a package the parent's "dnf update -y" has moved past
# would be a downgrade, which dnf refuses.
pgedge-subscription-manager install /run/secrets/repo_client_crt /run/secrets/repo_client_key --force

xargs dnf install -y < /usr/share/pgedge/packages.txt
# Patch any OS packages (including transitive dependencies pulled in above)
# to the latest available errata so the image ships with security fixes.
Expand Down Expand Up @@ -265,13 +333,33 @@ USER root

COPY packagelists/${TARGETARCH}/${COLDFRONT_PACKAGE_LIST_FILE} /usr/share/pgedge/coldfront-packages.txt

RUN <<CFEOF
RUN --mount=type=secret,id=repo_client_crt \
--mount=type=secret,id=repo_client_key \
<<CFEOF
#!/usr/bin/env bash

set -o errexit
set -o pipefail
set -o nounset

# The pgEdge repositories authenticate clients with mutual TLS. BuildKit mounts
# the secrets for this RUN alone and never writes them to a layer; the copies
# pgedge-subscription-manager installs are removed, and the repositories
# disabled again, before this layer is committed. So the image carries no
# credentials and no repository it cannot reach.
trap 'rm -f /etc/pki/pgedge/client.crt /etc/pki/pgedge/client.key; dnf config-manager --set-disabled "pgedge*" >/dev/null 2>&1 || true' EXIT

# A chained stage starts FROM a published parent built before the repositories
# were gated: its pgedge-release predates pgedge-subscription-manager and its
# repository definition is enabled, pointing at a host it can no longer reach.
# Disable those definitions and replace the package first, so neither this
# install nor the channel rewrite below depends on when the parent was built.
dnf config-manager --set-disabled "pgedge*" >/dev/null 2>&1 || true
command -v pgedge-subscription-manager >/dev/null 2>&1 \
|| dnf install -y --disablerepo="pgedge*" \
https://downloads.pgedge.com/reporpm/pgedge-release-latest.noarch.rpm


# The repo config is inherited from the parent image, which may have been built
# from a different channel, so re-point it here rather than relying on the base
# stage. Idempotent: once rewritten there is no "release" left to match.
Expand All @@ -281,6 +369,8 @@ fi

# Dependencies are dnf's to resolve. No second "dnf update -y": that would move
# ColdFront past its pinned NVR.
pgedge-subscription-manager install /run/secrets/repo_client_crt /run/secrets/repo_client_key --force

xargs dnf install -y --setopt=install_weak_deps=False < /usr/share/pgedge/coldfront-packages.txt
dnf clean all

Expand Down
18 changes: 18 additions & 0 deletions pgedge.docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ variable "TAG" {
default = "pgedge"
}

variable "REPO_CLIENT_CRT" {
type = string
default = "client.crt"
}

variable "REPO_CLIENT_KEY" {
type = string
default = "client.key"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

target "default" {
pull = true
target = TARGET
Expand All @@ -78,6 +88,14 @@ target "default" {
STANDARD_IMAGE = STANDARD_IMAGE
POSTGRES_MAJOR_VERSION = POSTGRES_MAJOR_VERSION
}
// The pgEdge repositories authenticate clients with mutual TLS. BuildKit
// mounts these for the RUNs that install packages and never writes them to
// a layer. Point them at your own files with REPO_CLIENT_CRT and
// REPO_CLIENT_KEY.
secret = [
"id=repo_client_crt,src=${REPO_CLIENT_CRT}",
"id=repo_client_key,src=${REPO_CLIENT_KEY}",
]
platforms = [
"linux/amd64",
"linux/arm64",
Expand Down
Loading