[v17] Sign managed updates Linux tarballs with cosign (#69536)

This commit is contained in:
Angel Dionisio
2026-08-12 18:33:29 +00:00
committed by GitHub
parent c4762309dc
commit 489cb39048
9 changed files with 791 additions and 88 deletions
+14 -2
View File
@@ -367,6 +367,12 @@ ifeq ("$(GITHUB_REPOSITORY_OWNER)","gravitational")
# This is done here to prevent any changes to the (BUI)LDFLAGS passed to the other binaries
TELEPORT_LDFLAGS ?= -ldflags '$(GO_LDFLAGS) -X github.com/gravitational/teleport/lib/modules.teleportBuildType=community'
TOOLS_LDFLAGS ?= -ldflags '$(GO_LDFLAGS) $(KUBECTL_SETVERSION) -X github.com/gravitational/teleport/lib/modules.teleportBuildType=community'
TELEPORT_UPDATE_ARTIFACT_SIGNATURE_PUBLIC_KEY_B64 ?=
TELEPORT_UPDATE_ARTIFACT_SIGNATURE_BACKUP_PUBLIC_KEY_B64 ?=
TELEPORT_UPDATE_ARTIFACT_SIGNATURE_ADDITIONAL_PUBLIC_KEY_B64 ?=
TELEPORT_UPDATE_ARTIFACT_SIGNATURE_ADDITIONAL_BACKUP_PUBLIC_KEY_B64 ?=
TELEPORT_UPDATE_DEV_BUILD ?=
TELEPORT_UPDATE_LDFLAGS ?= -ldflags '$(GO_LDFLAGS) $(KUBECTL_SETVERSION) -X github.com/gravitational/teleport/lib/modules.teleportBuildType=community -X main.artifactSignaturePublicKeyB64=$(TELEPORT_UPDATE_ARTIFACT_SIGNATURE_PUBLIC_KEY_B64) -X main.artifactSignatureBackupPublicKeyB64=$(TELEPORT_UPDATE_ARTIFACT_SIGNATURE_BACKUP_PUBLIC_KEY_B64) -X main.artifactSignatureAdditionalPublicKeyB64=$(TELEPORT_UPDATE_ARTIFACT_SIGNATURE_ADDITIONAL_PUBLIC_KEY_B64) -X main.artifactSignatureAdditionalBackupPublicKeyB64=$(TELEPORT_UPDATE_ARTIFACT_SIGNATURE_ADDITIONAL_BACKUP_PUBLIC_KEY_B64)'
endif
# By making these 3 targets below (tsh, tctl and teleport) PHONY we are solving
@@ -406,7 +412,11 @@ $(BUILDDIR)/tbot:
.PHONY: $(BUILDDIR)/teleport-update
$(BUILDDIR)/teleport-update:
GOOS=$(OS) GOARCH=$(ARCH) CGO_ENABLED=0 go build -o $(BUILDDIR)/teleport-update $(BUILDFLAGS_TELEPORT_UPDATE) ./tool/teleport-update
@if [[ (-z "$(TELEPORT_UPDATE_ARTIFACT_SIGNATURE_PUBLIC_KEY_B64)" || -z "$(TELEPORT_UPDATE_ARTIFACT_SIGNATURE_BACKUP_PUBLIC_KEY_B64)") && "$(TELEPORT_UPDATE_DEV_BUILD)" != "1" ]]; then \
echo "TELEPORT_UPDATE_ARTIFACT_SIGNATURE_PUBLIC_KEY_B64 and TELEPORT_UPDATE_ARTIFACT_SIGNATURE_BACKUP_PUBLIC_KEY_B64 must be set when building teleport-update (or set TELEPORT_UPDATE_DEV_BUILD=1 for unsigned dev builds)" >&2; \
exit 1; \
fi
GOOS=$(OS) GOARCH=$(ARCH) CGO_ENABLED=0 go build -o $(BUILDDIR)/teleport-update $(BUILDFLAGS_TELEPORT_UPDATE) $(TELEPORT_UPDATE_LDFLAGS) ./tool/teleport-update
TELEPORT_ARGS ?= start
.PHONY: teleport-hot-reload
@@ -1446,6 +1456,7 @@ IS_PROD_SEMVER = $(if $(findstring -,$(VERSION)),$(call find-any,$(PROD_VERSIONS
.PHONY: tag-build
tag-build: CLOUD_ONLY = $(if $(IS_CLOUD_SEMVER),true,false)
tag-build: ENVIRONMENT = $(if $(IS_PROD_SEMVER),prod/build,stage/build)
tag-build: MANAGED_UPDATES_SIGNING_KEY ?= primary
tag-build:
@which gh >/dev/null 2>&1 || { echo 'gh command needed. https://github.com/cli/cli'; exit 1; }
gh workflow run tag-build.yaml \
@@ -1454,7 +1465,8 @@ tag-build:
-f "oss-teleport-repo=$(shell gh repo view --json nameWithOwner --jq .nameWithOwner)" \
-f "oss-teleport-ref=v$(VERSION)" \
-f "cloud-only=$(CLOUD_ONLY)" \
-f "environment=$(ENVIRONMENT)"
-f "environment=$(ENVIRONMENT)" \
-f "managed-updates-signing-key=$(MANAGED_UPDATES_SIGNING_KEY)"
@echo See runs at: https://github.com/gravitational/teleport.e/actions/workflows/tag-build.yaml
# Publishes a tag build.
+1 -1
View File
@@ -8,7 +8,7 @@ HOSTNAME=buildbox
SRCDIR=/go/src/github.com/gravitational/teleport
GOMODCACHE ?= /tmp/gomodcache
# TODO(hugoShaka) remove HELM_PLUGINS with teleport13 buildbox
DOCKERFLAGS := --rm=true -v "$$(pwd)/../":$(SRCDIR) -v /tmp:/tmp -w $(SRCDIR) -h $(HOSTNAME) -e GOMODCACHE=$(GOMODCACHE) -e HELM_PLUGINS=/home/ci/.local/share/helm/plugins-new -e GITHUB_REPOSITORY_OWNER=$(GITHUB_REPOSITORY_OWNER) -e MAKEFLAGS -e CI=true
DOCKERFLAGS := --rm=true -v "$$(pwd)/../":$(SRCDIR) -v /tmp:/tmp -w $(SRCDIR) -h $(HOSTNAME) -e GOMODCACHE=$(GOMODCACHE) -e HELM_PLUGINS=/home/ci/.local/share/helm/plugins-new -e GITHUB_REPOSITORY_OWNER=$(GITHUB_REPOSITORY_OWNER) -e MAKEFLAGS -e CI=true -e TELEPORT_UPDATE_ARTIFACT_SIGNATURE_PUBLIC_KEY_B64 -e TELEPORT_UPDATE_ARTIFACT_SIGNATURE_BACKUP_PUBLIC_KEY_B64 -e TELEPORT_UPDATE_ARTIFACT_SIGNATURE_ADDITIONAL_PUBLIC_KEY_B64 -e TELEPORT_UPDATE_ARTIFACT_SIGNATURE_ADDITIONAL_BACKUP_PUBLIC_KEY_B64
# Teleport version - some targets require this to be set
VERSION ?= $(shell egrep ^VERSION ../Makefile | cut -d= -f2)
+12
View File
@@ -79,6 +79,12 @@ type UpdateSpec struct {
Group string `yaml:"group,omitempty"`
// BaseURL is CDN base URL used for the Teleport tgz download URL.
BaseURL string `yaml:"base_url,omitempty"`
// InsecureSkipSignatureVerify disables artifact signature verification and falls back
// to checksum-only verification. This is intended for development builds that are not signed.
InsecureSkipSignatureVerify bool `yaml:"insecure_skip_signature_verify,omitempty"`
// EnableStagingSignatureVerify enforces detached signature verification for staging CDN artifacts
// that would otherwise skip signature verification.
EnableStagingSignatureVerify bool `yaml:"enable_staging_signature_verify,omitempty"`
// Enabled controls whether auto-updates are enabled.
Enabled bool `yaml:"enabled"`
// Pinned controls whether the active_version is pinned.
@@ -233,6 +239,12 @@ func updateConfigSpec(spec *UpdateSpec, override OverrideConfig) error {
!strings.HasPrefix(strings.ToLower(spec.BaseURL), "https://") {
return trace.Errorf("Teleport download base URL %s must use TLS (https://)", spec.BaseURL)
}
if override.InsecureSkipSignatureVerifyChanged {
spec.InsecureSkipSignatureVerify = override.InsecureSkipSignatureVerify
}
if override.EnableStagingSignatureVerifyChanged {
spec.EnableStagingSignatureVerify = override.EnableStagingSignatureVerify
}
if override.Enabled {
spec.Enabled = true
}
+77 -6
View File
@@ -130,11 +130,15 @@ func TestValidateConfigSpec(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
name string
config UpdateSpec
override UpdateSpec
result UpdateSpec
errMatch string
name string
config UpdateSpec
override UpdateSpec
insecureSkipSignatureVerify bool
insecureChanged bool
enableStagingSignatureVerify bool
enableStagingChanged bool
result UpdateSpec
errMatch string
}{
{
name: "overrides",
@@ -221,9 +225,76 @@ func TestValidateConfigSpec(t *testing.T) {
},
errMatch: "must use TLS",
},
{
name: "insecure checksum-only mode allowed",
override: UpdateSpec{
BaseURL: "https://example.com",
},
insecureSkipSignatureVerify: true,
insecureChanged: true,
result: UpdateSpec{
BaseURL: "https://example.com",
InsecureSkipSignatureVerify: true,
},
},
{
name: "insecure checksum-only mode allowed with default base URL",
insecureSkipSignatureVerify: true,
insecureChanged: true,
result: UpdateSpec{
InsecureSkipSignatureVerify: true,
},
},
{
name: "insecure checksum-only mode can be cleared",
config: UpdateSpec{
BaseURL: "https://example.com",
InsecureSkipSignatureVerify: true,
},
insecureChanged: true,
result: UpdateSpec{
BaseURL: "https://example.com",
},
},
{
name: "enable staging signature verification can be enabled",
override: UpdateSpec{
BaseURL: "https://cdn.cloud.gravitational.io",
},
enableStagingSignatureVerify: true,
enableStagingChanged: true,
result: UpdateSpec{
BaseURL: "https://cdn.cloud.gravitational.io",
EnableStagingSignatureVerify: true,
},
},
{
name: "enable staging signature verification can be cleared",
config: UpdateSpec{
BaseURL: "https://cdn.cloud.gravitational.io",
EnableStagingSignatureVerify: true,
},
enableStagingChanged: true,
result: UpdateSpec{
BaseURL: "https://cdn.cloud.gravitational.io",
},
},
} {
t.Run(tt.name, func(t *testing.T) {
err := updateConfigSpec(&tt.config, OverrideConfig{UpdateSpec: tt.override})
err := updateConfigSpec(&tt.config, OverrideConfig{
UpdateSpec: UpdateSpec{
Proxy: tt.override.Proxy,
Path: tt.override.Path,
Group: tt.override.Group,
BaseURL: tt.override.BaseURL,
Enabled: tt.override.Enabled,
Pinned: tt.override.Pinned,
InsecureSkipSignatureVerify: tt.insecureSkipSignatureVerify,
EnableStagingSignatureVerify: tt.enableStagingSignatureVerify,
},
InsecureSkipSignatureVerifyChanged: tt.insecureChanged,
EnableStagingSignatureVerifyChanged: tt.enableStagingChanged,
})
if tt.errMatch != "" {
require.ErrorContains(t, err, tt.errMatch)
return
+199 -8
View File
@@ -22,19 +22,27 @@ import (
"bytes"
"compress/gzip"
"context"
"crypto"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"io"
"log/slog"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/coreos/go-semver/semver"
"github.com/gravitational/trace"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
sigopts "github.com/sigstore/sigstore/pkg/signature/options"
"github.com/gravitational/teleport/lib/autoupdate"
"github.com/gravitational/teleport/lib/utils"
@@ -51,6 +59,14 @@ const (
configFileMode = 0644
// systemDirMode is the mode used for new directories.
systemDirMode = 0755
// artifactSignatureType is the detached cosign signature extension for Teleport tgzs.
artifactSignatureType = "sig"
// maxArtifactSignatureSize is the maximum allowed size of a detached artifact signature.
maxArtifactSignatureSize = 4_096 // 4 KB, ECDSA P256 signatures are ~96 bytes
// stagingCDNBaseURL is the staging CDN used for tenant dev-build workflows.
stagingCDNBaseURL = "https://cdn.cloud.gravitational.io"
// stagingSignatureVerificationVersion is the first major version that verifies staging CDN artifacts by default.
stagingSignatureVerificationVersion = 19
)
const (
@@ -58,6 +74,8 @@ const (
serviceDir = "lib/systemd/system"
// serviceName contains the upstream name of the Teleport SystemD service file.
serviceName = "teleport.service"
// artifactSignatureHash is the digest algorithm used for detached cosign signatures.
artifactSignatureHash = crypto.SHA256
)
// ServiceFile represents a systemd service file for a Teleport binary.
@@ -110,6 +128,8 @@ type LocalInstaller struct {
ValidateBinary func(ctx context.Context, path string) (bool, error)
// Template is download URI Template of Teleport packages.
Template string
// ArtifactSignatureVerifiers verifies detached release artifact signatures.
ArtifactSignatureVerifiers []signature.Verifier
}
// Remove a Teleport version directory from InstallDir.
@@ -140,7 +160,7 @@ func (li *LocalInstaller) Remove(ctx context.Context, rev Revision) error {
// Install a Teleport version directory in InstallDir.
// This function is idempotent.
// See Installer interface for additional specs.
func (li *LocalInstaller) Install(ctx context.Context, rev Revision, baseURL string, force bool) (err error) {
func (li *LocalInstaller) Install(ctx context.Context, rev Revision, baseURL string, force, insecureSkipSignatureVerify, enableStagingSignatureVerify bool) (err error) {
versionDir, err := li.revisionDir(rev)
if err != nil {
return trace.Wrap(err)
@@ -153,8 +173,6 @@ func (li *LocalInstaller) Install(ctx context.Context, rev Revision, baseURL str
return trace.Wrap(err)
}
// Get new and old checksums. If they match, skip download.
// Otherwise, clear the old version directory and re-download.
checksumURI := uri + "." + checksumType
newSum, err := li.getChecksum(ctx, checksumURI)
if err != nil {
@@ -200,9 +218,8 @@ func (li *LocalInstaller) Install(ctx context.Context, rev Revision, baseURL str
if err != nil {
return trace.Wrap(err, "failed to download teleport")
}
// Seek to the start of the tgz file after writing
if _, err := f.Seek(0, io.SeekStart); err != nil {
return trace.Wrap(err, "failed seek to start of download")
return trace.Wrap(err, "failed to reset artifact after download")
}
// If interrupted, close the file immediately to stop extracting.
@@ -211,10 +228,19 @@ func (li *LocalInstaller) Install(ctx context.Context, rev Revision, baseURL str
context.AfterFunc(ctx, func() {
_ = f.Close() // safe to close file multiple times
})
// Check integrity before decompression
if !bytes.Equal(newSum, pathSum) {
return trace.Errorf("mismatched checksum, download possibly corrupt")
if insecureSkipSignatureVerify {
li.Log.WarnContext(ctx, "Artifact signature verification is disabled. Falling back to checksum-only verification.", "version", rev)
} else if shouldSkipStagingSignatureVerification(baseURL, rev.Version, enableStagingSignatureVerify) {
li.Log.WarnContext(ctx, "Artifact signature verification skipped: staging CDN in use.", "version", rev)
} else if err := li.verifyArtifactSignature(ctx, uri+"."+artifactSignatureType, pathSum); err != nil {
return trace.Wrap(err)
}
if !bytes.Equal(newSum, pathSum) {
return trace.BadParameter("downloaded checksum does not match artifact digest")
}
// Get uncompressed size of the tgz
n, err := uncompressedSize(f)
if err != nil {
@@ -297,6 +323,171 @@ func (li *LocalInstaller) getChecksum(ctx context.Context, url string) ([]byte,
return sum, nil
}
func isStagingCDN(baseURL string) bool {
ok, err := sameHostname(baseURL, stagingCDNBaseURL)
return err == nil && ok
}
func shouldSkipStagingSignatureVerification(baseURL, version string, enableStagingSignatureVerify bool) bool {
if !isStagingCDN(baseURL) || enableStagingSignatureVerify {
return false
}
semVersion, err := semver.NewVersion(version)
if err != nil {
return false
}
return semVersion.Major < stagingSignatureVerificationVersion
}
func sameHostname(rawURL, wantURL string) (bool, error) {
u, err := url.Parse(rawURL)
if err != nil {
return false, trace.Wrap(err)
}
w, err := url.Parse(wantURL)
if err != nil {
return false, trace.Wrap(err)
}
got := strings.TrimSuffix(strings.ToLower(u.Hostname()), ".")
want := strings.TrimSuffix(strings.ToLower(w.Hostname()), ".")
return got == want, nil
}
// NewArtifactSignatureVerifiers constructs the trusted verifier set for
// teleport-update. The primary/backup pair is required. An additional
// primary/backup pair may be provided to extend trust for specific build lanes,
// such as allowing a staging-built updater to verify prod-signed artifacts.
func NewArtifactSignatureVerifiers(primaryPublicKeyB64, backupPublicKeyB64, additionalPublicKeyB64, additionalBackupPublicKeyB64 string) ([]signature.Verifier, error) {
if primaryPublicKeyB64 == "" {
return nil, trace.BadParameter("teleport-update artifact signature public key is not configured")
}
if backupPublicKeyB64 == "" {
return nil, trace.BadParameter("teleport-update backup artifact signature public key is not configured")
}
if additionalPublicKeyB64 != "" && additionalBackupPublicKeyB64 == "" {
return nil, trace.BadParameter("teleport-update additional backup artifact signature public key is not configured")
}
if additionalPublicKeyB64 == "" && additionalBackupPublicKeyB64 != "" {
return nil, trace.BadParameter("teleport-update additional artifact signature public key is not configured")
}
primaryVerifier, err := loadArtifactSignatureVerifier(primaryPublicKeyB64, "primary")
if err != nil {
return nil, trace.Wrap(err)
}
backupVerifier, err := loadArtifactSignatureVerifier(backupPublicKeyB64, "backup")
if err != nil {
return nil, trace.Wrap(err)
}
verifiers := []signature.Verifier{primaryVerifier, backupVerifier}
if additionalPublicKeyB64 != "" {
additionalPrimaryVerifier, err := loadArtifactSignatureVerifier(additionalPublicKeyB64, "additional primary")
if err != nil {
return nil, trace.Wrap(err)
}
verifiers = append(verifiers, additionalPrimaryVerifier)
additionalBackupVerifier, err := loadArtifactSignatureVerifier(additionalBackupPublicKeyB64, "additional backup")
if err != nil {
return nil, trace.Wrap(err)
}
verifiers = append(verifiers, additionalBackupVerifier)
}
return verifiers, nil
}
func loadArtifactSignatureVerifier(publicKeyB64 string, label string) (signature.Verifier, error) {
pem, err := base64.StdEncoding.DecodeString(publicKeyB64)
if err != nil {
return nil, trace.Wrap(err, "failed to decode %s teleport-update artifact signature public key", label)
}
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pem)
if err != nil {
return nil, trace.Wrap(err, "failed to parse %s teleport-update artifact signature public key", label)
}
verifier, err := signature.LoadVerifier(pubKey, artifactSignatureHash)
if err != nil {
return nil, trace.Wrap(err, "failed to initialize %s teleport-update artifact signature verifier", label)
}
return verifier, nil
}
func (li *LocalInstaller) getSignature(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, trace.Wrap(err)
}
resp, err := li.HTTP.Do(req)
if err != nil {
return nil, trace.Wrap(err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
return nil, trace.Errorf("signature not found: %s", url)
}
if resp.StatusCode != http.StatusOK {
return nil, trace.Errorf("unexpected HTTP status code: %d", resp.StatusCode)
}
raw, err := io.ReadAll(io.LimitReader(resp.Body, maxArtifactSignatureSize+1))
if err != nil {
return nil, trace.Wrap(err)
}
if len(raw) > maxArtifactSignatureSize {
return nil, trace.BadParameter("signature exceeds maximum size")
}
sig, err := base64.StdEncoding.DecodeString(string(bytes.TrimSpace(raw)))
if err != nil {
return nil, trace.Wrap(err, "failed to decode signature")
}
return sig, nil
}
func (li *LocalInstaller) verifyArtifactSignature(ctx context.Context, url string, digest []byte) error {
if len(li.ArtifactSignatureVerifiers) == 0 {
return trace.BadParameter("teleport-update artifact signature verifier is not configured")
}
sig, err := li.getSignature(ctx, url)
if err != nil {
return trace.Wrap(err, "failed to download signature from %s", url)
}
// WithDigest reuses the SHA-256 computed during download, so the verifier does
// not need the artifact bytes here for the current ECDSA verifier path. Use
// an empty reader as a safe placeholder rather than nil; upstream
// sigstore-go uses the same digest-only pattern:
// https://github.com/sigstore/sigstore-go/blob/v1.1.4/pkg/verify/signature.go#L398
//
// Note: this is not a generic sigstore pattern for all key types. In
// particular, sigstore's Ed25519 verifier rejects WithDigest-based
// verification, so this code would need to change if the signing algorithm
// changes in the future.
//
// Sigstore documents an ECDSA malleability warning for WithDigest:
// https://github.com/sigstore/sigstore/blob/v1.10.5/pkg/signature/options/digest.go#L29
// Here the digest is computed locally from the downloaded artifact, not
// supplied by an untrusted source.
var errs []error
for _, verifier := range li.ArtifactSignatureVerifiers {
err := verifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(nil), sigopts.WithDigest(digest))
if err == nil {
return nil
}
errs = append(errs, err)
}
return trace.Wrap(errors.Join(errs...), "artifact signature verification failed for all trusted keys")
}
func (li *LocalInstaller) download(ctx context.Context, w io.Writer, max int64, url string) (sum []byte, err error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
+350 -34
View File
@@ -23,13 +23,21 @@ import (
"bytes"
"compress/gzip"
"context"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"runtime"
@@ -38,6 +46,7 @@ import (
"strings"
"testing"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -48,15 +57,23 @@ func TestLocalInstaller_Install(t *testing.T) {
t.Parallel()
const version = "new-version"
_, testSum := testTGZ(t, version)
tgz, testSum := testTGZ(t, version)
verifier, sig := testArtifactSignatureVerifier(t, tgz.Bytes())
tests := []struct {
name string
reservedTmp uint64
reservedInstall uint64
existingSum string
flags autoupdate.InstallFlags
force bool
name string
reservedTmp uint64
reservedInstall uint64
existingSum string
checksumValue string
signatureValue string
signatureStatus int
flags autoupdate.InstallFlags
baseURL string
force bool
insecure bool
enableStagingSignatureVerify bool
alreadyPresent bool
errMatch string
}{
@@ -64,14 +81,52 @@ func TestLocalInstaller_Install(t *testing.T) {
name: "not present",
},
{
name: "present",
existingSum: testSum,
name: "not present insecure checksum only",
insecure: true,
checksumValue: testSum,
},
{
name: "not present internal development CDN pre-v19 checksum fallback by default",
baseURL: stagingCDNBaseURL,
checksumValue: testSum,
},
{
name: "not present internal development CDN pre-v19 signature verification explicitly enabled",
baseURL: stagingCDNBaseURL,
checksumValue: testSum,
enableStagingSignatureVerify: true,
},
{
name: "v19 internal development CDN verifies by default",
baseURL: stagingCDNBaseURL,
checksumValue: testSum,
},
{
name: "present",
existingSum: testSum,
alreadyPresent: true,
},
{
name: "mismatched checksum",
existingSum: hex.EncodeToString(sha256.New().Sum(nil)),
force: true,
},
{
name: "insecure checksum mismatch",
insecure: true,
checksumValue: strings.Repeat("0", checksumHexLen),
errMatch: "downloaded checksum does not match artifact digest",
},
{
name: "invalid signature",
signatureValue: base64.StdEncoding.EncodeToString([]byte("invalid-signature")),
errMatch: "artifact signature verification failed",
},
{
name: "missing signature",
signatureStatus: http.StatusNotFound,
errMatch: "signature not found",
},
{
name: "unreadable checksum",
existingSum: "bad",
@@ -98,29 +153,51 @@ func TestLocalInstaller_Install(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
installVersion := version
switch tt.name {
case "not present internal development CDN pre-v19 checksum fallback by default", "not present internal development CDN pre-v19 signature verification explicitly enabled":
installVersion = "18.10.1"
case "v19 internal development CDN verifies by default":
installVersion = "19.0.0-dev.test.1"
}
dir := t.TempDir()
err := os.MkdirAll(filepath.Join(dir, version), os.ModePerm)
err := os.MkdirAll(filepath.Join(dir, installVersion), os.ModePerm)
require.NoError(t, err)
if tt.existingSum != "" {
err := os.WriteFile(filepath.Join(dir, version, checksumType), []byte(tt.existingSum), os.ModePerm)
err := os.WriteFile(filepath.Join(dir, installVersion, checksumType), []byte(tt.existingSum), os.ModePerm)
require.NoError(t, err)
}
// test parameters
var dlPath, shaPath, shasum string
var dlPath, shaPath, sigPath string
// test server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tgz, sum := testTGZ(t, version)
shasum = sum
var out *bytes.Buffer
if strings.HasSuffix(r.URL.Path, "."+checksumType) { // checksum request
switch {
case strings.HasSuffix(r.URL.Path, "."+checksumType): // checksum request
shaPath = r.URL.Path
out = bytes.NewBufferString(sum)
} else { // tgz request
checksum := testSum
if tt.checksumValue != "" {
checksum = tt.checksumValue
}
out = bytes.NewBufferString(checksum)
case strings.HasSuffix(r.URL.Path, "."+artifactSignatureType):
sigPath = r.URL.Path
if tt.signatureStatus != 0 {
w.WriteHeader(tt.signatureStatus)
return
}
signatureValue := sig
if tt.signatureValue != "" {
signatureValue = tt.signatureValue
}
out = bytes.NewBufferString(signatureValue)
default: // tgz request
dlPath = r.URL.Path
out = tgz
out = bytes.NewBuffer(tgz.Bytes())
}
w.Header().Set("Content-Length", strconv.Itoa(out.Len()))
_, err := io.Copy(w, out)
@@ -130,16 +207,39 @@ func TestLocalInstaller_Install(t *testing.T) {
}))
t.Cleanup(server.Close)
httpClient := server.Client()
baseURL := server.URL
if tt.baseURL != "" {
baseURL = tt.baseURL
}
if isStagingCDN(baseURL) {
serverURL, err := url.Parse(server.URL)
require.NoError(t, err)
baseTransport := http.DefaultTransport.(*http.Transport).Clone()
httpClient = &http.Client{
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
clone := r.Clone(r.Context())
clone.URL = new(url.URL)
*clone.URL = *r.URL
clone.URL.Scheme = serverURL.Scheme
clone.URL.Host = serverURL.Host
clone.Host = serverURL.Host
return baseTransport.RoundTrip(clone)
}),
}
}
installer := &LocalInstaller{
InstallDir: dir,
HTTP: http.DefaultClient,
Log: slog.Default(),
ReservedFreeTmpDisk: tt.reservedTmp,
ReservedFreeInstallDisk: tt.reservedInstall,
Template: "{{.BaseURL}}/{{.Package}}-{{.OS}}/{{.Arch}}/{{.Version}}",
InstallDir: dir,
HTTP: httpClient,
Log: slog.Default(),
ReservedFreeTmpDisk: tt.reservedTmp,
ReservedFreeInstallDisk: tt.reservedInstall,
Template: "{{.BaseURL}}/{{.Package}}-{{.OS}}/{{.Arch}}/{{.Version}}",
ArtifactSignatureVerifiers: []signature.Verifier{verifier},
}
ctx := context.Background()
err = installer.Install(ctx, NewRevision(version, tt.flags), server.URL, tt.force)
err = installer.Install(ctx, NewRevision(installVersion, tt.flags), baseURL, tt.force, tt.insecure, tt.enableStagingSignatureVerify)
if tt.errMatch != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.errMatch)
@@ -147,32 +247,217 @@ func TestLocalInstaller_Install(t *testing.T) {
}
require.NoError(t, err)
const expectedPath = "/teleport-" + runtime.GOOS + "/" + runtime.GOARCH + "/" + version
require.Equal(t, expectedPath+"."+checksumType, shaPath)
if tt.existingSum == testSum {
expectedPath := "/teleport-" + runtime.GOOS + "/" + runtime.GOARCH + "/" + installVersion
if tt.alreadyPresent {
require.Equal(t, expectedPath+"."+checksumType, shaPath)
require.Empty(t, dlPath)
require.Empty(t, sigPath)
return
}
require.Equal(t, expectedPath, dlPath)
require.Equal(t, expectedPath+"."+checksumType, shaPath)
if tt.insecure || shouldSkipStagingSignatureVerification(baseURL, installVersion, tt.enableStagingSignatureVerify) {
require.Empty(t, sigPath)
} else {
require.Equal(t, expectedPath+"."+artifactSignatureType, sigPath)
}
for _, p := range []string{
filepath.Join(dir, version, "lib", "systemd", "system", "teleport.service"),
filepath.Join(dir, version, "bin", "teleport"),
filepath.Join(dir, version, "bin", "tsh"),
filepath.Join(dir, installVersion, "lib", "systemd", "system", "teleport.service"),
filepath.Join(dir, installVersion, "bin", "teleport"),
filepath.Join(dir, installVersion, "bin", "tsh"),
} {
v, err := os.ReadFile(p)
require.NoError(t, err)
require.Equal(t, version, string(v))
}
sum, err := os.ReadFile(filepath.Join(dir, version, checksumType))
sum, err := os.ReadFile(filepath.Join(dir, installVersion, checksumType))
require.NoError(t, err)
require.Equal(t, string(sum), shasum)
require.Equal(t, testSum, string(sum))
})
}
}
func TestLocalInstaller_Install_MismatchedChecksumMetadata(t *testing.T) {
t.Parallel()
const version = "new-version"
tgz, _ := testTGZ(t, version)
verifier, sig := testArtifactSignatureVerifier(t, tgz.Bytes())
var shaPath string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case strings.HasSuffix(r.URL.Path, "."+checksumType):
shaPath = r.URL.Path
_, err := fmt.Fprint(w, strings.Repeat("0", checksumHexLen))
require.NoError(t, err)
case strings.HasSuffix(r.URL.Path, "."+artifactSignatureType):
_, err := fmt.Fprint(w, sig)
require.NoError(t, err)
default:
w.Header().Set("Content-Length", strconv.Itoa(tgz.Len()))
_, err := io.Copy(w, bytes.NewReader(tgz.Bytes()))
require.NoError(t, err)
}
}))
t.Cleanup(server.Close)
installDir := t.TempDir()
installer := &LocalInstaller{
InstallDir: installDir,
HTTP: http.DefaultClient,
Log: slog.Default(),
Template: "{{.BaseURL}}/{{.Package}}-{{.OS}}/{{.Arch}}/{{.Version}}",
ArtifactSignatureVerifiers: []signature.Verifier{verifier},
}
err := installer.Install(context.Background(), NewRevision(version, 0), server.URL, false, false, false)
require.ErrorContains(t, err, "downloaded checksum does not match artifact digest")
require.Equal(t, "/teleport-"+runtime.GOOS+"/"+runtime.GOARCH+"/"+version+"."+checksumType, shaPath)
_, err = os.ReadFile(filepath.Join(installDir, version, checksumType))
require.Error(t, err)
}
func TestLocalInstaller_VerifyArtifactSignature_FallsBackToBackupKey(t *testing.T) {
t.Parallel()
primaryVerifier, _ := testArtifactSignatureVerifier(t, []byte("different-payload"))
backupVerifier, backupSig := testArtifactSignatureVerifier(t, []byte("payload"))
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprint(w, backupSig)
require.NoError(t, err)
}))
t.Cleanup(server.Close)
installer := &LocalInstaller{
HTTP: http.DefaultClient,
ArtifactSignatureVerifiers: []signature.Verifier{
primaryVerifier,
backupVerifier,
},
}
digest := sha256.Sum256([]byte("payload"))
require.NoError(t, installer.verifyArtifactSignature(context.Background(), server.URL, digest[:]))
}
func TestIsStagingCDN(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
name string
baseURL string
want bool
}{
{
name: "exact match",
baseURL: stagingCDNBaseURL,
want: true,
},
{
name: "trailing slash",
baseURL: stagingCDNBaseURL + "/",
want: true,
},
{
name: "uppercase host",
baseURL: "https://CDN.CLOUD.GRAVITATIONAL.IO",
want: true,
},
{
name: "hostname trailing dot",
baseURL: "https://cdn.cloud.gravitational.io.",
want: true,
},
{
name: "port preserved",
baseURL: "https://cdn.cloud.gravitational.io:443",
want: true,
},
{
name: "path ignored",
baseURL: "https://cdn.cloud.gravitational.io/releases",
want: true,
},
{
name: "default prod cdn",
baseURL: autoupdate.DefaultBaseURL,
want: false,
},
{
name: "different dev host",
baseURL: "https://cdn.dev-blue.cloud.teleportinfra.dev",
want: false,
},
{
name: "invalid url",
baseURL: "://bad-url",
want: false,
},
} {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, isStagingCDN(tt.baseURL))
})
}
}
func TestNewArtifactSignatureVerifiers(t *testing.T) {
_, err := NewArtifactSignatureVerifiers("", "", "", "")
require.ErrorContains(t, err, "teleport-update artifact signature public key is not configured")
primaryKey, primaryB64 := testArtifactSignatureKey(t)
_, err = NewArtifactSignatureVerifiers(primaryB64, "", "", "")
require.ErrorContains(t, err, "teleport-update backup artifact signature public key is not configured")
backupKey, backupB64 := testArtifactSignatureKey(t)
verifiers, err := NewArtifactSignatureVerifiers(primaryB64, backupB64, "", "")
require.NoError(t, err)
require.Len(t, verifiers, 2)
digest := sha256.Sum256([]byte("payload"))
sig, err := ecdsa.SignASN1(rand.Reader, primaryKey, digest[:])
require.NoError(t, err)
require.NoError(t, verifiers[0].VerifySignature(bytes.NewReader(sig), bytes.NewReader([]byte("payload"))))
sig, err = ecdsa.SignASN1(rand.Reader, backupKey, digest[:])
require.NoError(t, err)
require.Error(t, verifiers[0].VerifySignature(bytes.NewReader(sig), bytes.NewReader([]byte("payload"))))
require.NoError(t, verifiers[1].VerifySignature(bytes.NewReader(sig), bytes.NewReader([]byte("payload"))))
extraKey, extraB64 := testArtifactSignatureKey(t)
additionalBackupKey, additionalBackupB64 := testArtifactSignatureKey(t)
verifiers, err = NewArtifactSignatureVerifiers(primaryB64, backupB64, extraB64, additionalBackupB64)
require.NoError(t, err)
require.Len(t, verifiers, 4)
sig, err = ecdsa.SignASN1(rand.Reader, extraKey, digest[:])
require.NoError(t, err)
require.NoError(t, verifiers[2].VerifySignature(bytes.NewReader(sig), bytes.NewReader([]byte("payload"))))
sig, err = ecdsa.SignASN1(rand.Reader, additionalBackupKey, digest[:])
require.NoError(t, err)
require.NoError(t, verifiers[3].VerifySignature(bytes.NewReader(sig), bytes.NewReader([]byte("payload"))))
_, err = NewArtifactSignatureVerifiers(primaryB64, "not-base64", "", "")
require.ErrorContains(t, err, "failed to decode backup teleport-update artifact signature public key")
_, err = NewArtifactSignatureVerifiers(primaryB64, backupB64, extraB64, "")
require.ErrorContains(t, err, "teleport-update additional backup artifact signature public key is not configured")
_, err = NewArtifactSignatureVerifiers(primaryB64, backupB64, "", additionalBackupB64)
require.ErrorContains(t, err, "teleport-update additional artifact signature public key is not configured")
_, err = NewArtifactSignatureVerifiers(primaryB64, backupB64, "not-base64", additionalBackupB64)
require.ErrorContains(t, err, "failed to decode additional primary teleport-update artifact signature public key")
_, err = NewArtifactSignatureVerifiers(primaryB64, backupB64, extraB64, "not-base64")
require.ErrorContains(t, err, "failed to decode additional backup teleport-update artifact signature public key")
}
func testTGZ(t *testing.T, version string) (tgz *bytes.Buffer, shasum string) {
t.Helper()
@@ -211,6 +496,37 @@ func testTGZ(t *testing.T, version string) (tgz *bytes.Buffer, shasum string) {
return &buf, hex.EncodeToString(sha.Sum(nil))
}
func testArtifactSignatureVerifier(t *testing.T, payload []byte) (signature.Verifier, string) {
t.Helper()
key, _ := testArtifactSignatureKey(t)
verifier, err := signature.LoadVerifier(key.Public(), crypto.SHA256)
require.NoError(t, err)
digest := sha256.Sum256(payload)
sig, err := ecdsa.SignASN1(rand.Reader, key, digest[:])
require.NoError(t, err)
return verifier, base64.StdEncoding.EncodeToString(sig)
}
func testArtifactSignatureKey(t *testing.T) (*ecdsa.PrivateKey, string) {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
pubDER, err := x509.MarshalPKIXPublicKey(key.Public())
require.NoError(t, err)
publicKeyB64 := base64.StdEncoding.EncodeToString(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubDER}))
return key, publicKeyB64
}
type roundTripperFunc func(*http.Request) (*http.Response, error)
func (f roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
return f(r)
}
func TestLocalInstaller_Link(t *testing.T) {
t.Parallel()
const version = "new-version"
+24 -10
View File
@@ -40,6 +40,7 @@ import (
"github.com/google/uuid"
"github.com/gravitational/trace"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/constants"
@@ -124,6 +125,10 @@ func NewLocalUpdater(cfg LocalUpdaterConfig, ns *Namespace) (*Updater, error) {
}
validator := Validator{Log: cfg.Log}
debugClient := debug.NewClient(ns.dataDir)
if len(cfg.ArtifactSignatureVerifiers) == 0 {
return nil, trace.BadParameter("teleport-update artifact signature verifier is not configured")
}
return &Updater{
Log: cfg.Log,
Pool: certPool,
@@ -146,14 +151,15 @@ func NewLocalUpdater(cfg LocalUpdaterConfig, ns *Namespace) (*Updater, error) {
ExampleFunc: ns.ReplaceTeleportService,
},
},
SystemBinDir: filepath.Join(cfg.SystemDir, "bin"),
SystemServiceDir: filepath.Join(cfg.SystemDir, serviceDir),
HTTP: client,
Log: cfg.Log,
ReservedFreeTmpDisk: reservedFreeDisk,
ReservedFreeInstallDisk: reservedFreeDisk,
ValidateBinary: validator.IsBinary,
Template: autoupdate.DefaultCDNURITemplate,
SystemBinDir: filepath.Join(cfg.SystemDir, "bin"),
SystemServiceDir: filepath.Join(cfg.SystemDir, serviceDir),
HTTP: client,
Log: cfg.Log,
ReservedFreeTmpDisk: reservedFreeDisk,
ReservedFreeInstallDisk: reservedFreeDisk,
ValidateBinary: validator.IsBinary,
Template: autoupdate.DefaultCDNURITemplate,
ArtifactSignatureVerifiers: cfg.ArtifactSignatureVerifiers,
},
Process: &SystemdService{
ServiceName: filepath.Base(ns.serviceFile),
@@ -216,6 +222,8 @@ type LocalUpdaterConfig struct {
Debug bool
// LogFormat controls the format of logging. Can be either `json` or `text`.
LogFormat string
// ArtifactSignatureVerifiers contains the trusted verifiers used to validate detached artifact signatures.
ArtifactSignatureVerifiers []signature.Verifier
}
// Updater implements the agent-local logic for Teleport agent auto-updates.
@@ -267,7 +275,7 @@ type Installer interface {
// Install the Teleport agent at revision from the download Template.
// If force is true, Install will remove broken revisions.
// Install must be idempotent.
Install(ctx context.Context, rev Revision, baseURL string, force bool) error
Install(ctx context.Context, rev Revision, baseURL string, force bool, insecureSkipSignatureVerify bool, enableStagingSignatureVerify bool) error
// Link the Teleport agent at the specified revision of Teleport into path.
// The revert function must restore the previous linking, returning false on any failure.
// If force is true, Link will overwrite non-symlinks.
@@ -362,6 +370,12 @@ type OverrideConfig struct {
AllowOverwrite bool
// AllowProxyConflict when proxies in teleport.yaml and update.yaml are mismatched.
AllowProxyConflict bool
// InsecureSkipSignatureVerifyChanged specifies whether the user explicitly toggled
// artifact verification fallback behavior.
InsecureSkipSignatureVerifyChanged bool
// EnableStagingSignatureVerifyChanged specifies whether the user explicitly toggled
// detached signature verification for pre-v19 artifacts downloaded from the staging CDN.
EnableStagingSignatureVerifyChanged bool
}
func deref[T any](ptr *T) T {
@@ -995,7 +1009,7 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, target Revision
if err != nil {
return trace.Wrap(err, "failed to determine if linked")
}
err = u.Installer.Install(ctx, target, baseURL, !linked)
err = u.Installer.Install(ctx, target, baseURL, !linked, cfg.Spec.InsecureSkipSignatureVerify, cfg.Spec.EnableStagingSignatureVerify)
if err != nil {
return trace.Wrap(err, "failed to install")
}
+50 -12
View File
@@ -21,7 +21,13 @@ package agent
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"log/slog"
@@ -35,6 +41,7 @@ import (
"testing"
"time"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
@@ -49,6 +56,29 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
func testArtifactSignatureVerifiers(t *testing.T) []signature.Verifier {
t.Helper()
setEmbeddedArtifactSignatureKey := func() string {
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
require.NoError(t, err)
pubDER, err := x509.MarshalPKIXPublicKey(key.Public())
require.NoError(t, err)
return base64.StdEncoding.EncodeToString(
pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubDER}),
)
}
verifiers, err := NewArtifactSignatureVerifiers(
setEmbeddedArtifactSignatureKey(),
setEmbeddedArtifactSignatureKey(),
"",
"",
)
require.NoError(t, err)
return verifiers
}
func TestWarnUmask(t *testing.T) {
t.Parallel()
@@ -133,7 +163,8 @@ func TestUpdater_Disable(t *testing.T) {
require.NoError(t, err)
cfgPath := filepath.Join(ns.Dir(), updateConfigName)
updater, err := NewLocalUpdater(LocalUpdaterConfig{
InsecureSkipVerify: true,
InsecureSkipVerify: true,
ArtifactSignatureVerifiers: testArtifactSignatureVerifiers(t),
}, ns)
require.NoError(t, err)
@@ -221,7 +252,8 @@ func TestUpdater_Unpin(t *testing.T) {
cfgPath := filepath.Join(ns.Dir(), updateConfigName)
updater, err := NewLocalUpdater(LocalUpdaterConfig{
InsecureSkipVerify: true,
InsecureSkipVerify: true,
ArtifactSignatureVerifiers: testArtifactSignatureVerifiers(t),
}, ns)
require.NoError(t, err)
@@ -763,7 +795,8 @@ func TestUpdater_Update(t *testing.T) {
cfgPath := filepath.Join(ns.Dir(), updateConfigName)
updater, err := NewLocalUpdater(LocalUpdaterConfig{
InsecureSkipVerify: true,
InsecureSkipVerify: true,
ArtifactSignatureVerifiers: testArtifactSignatureVerifiers(t),
}, ns)
require.NoError(t, err)
@@ -787,7 +820,7 @@ func TestUpdater_Update(t *testing.T) {
reloadCalls int
)
updater.Installer = &testInstaller{
FuncInstall: func(_ context.Context, rev Revision, baseURL string, force bool) error {
FuncInstall: func(_ context.Context, rev Revision, baseURL string, force bool, insecureSkipSignatureVerify bool, enableStagingSignatureVerify bool) error {
for _, r := range tt.linkedRevisions {
if r == rev {
require.False(t, force)
@@ -1019,7 +1052,8 @@ func TestUpdater_LinkPackage(t *testing.T) {
cfgPath := filepath.Join(ns.Dir(), updateConfigName)
updater, err := NewLocalUpdater(LocalUpdaterConfig{
InsecureSkipVerify: true,
InsecureSkipVerify: true,
ArtifactSignatureVerifiers: testArtifactSignatureVerifiers(t),
}, ns)
require.NoError(t, err)
@@ -1332,7 +1366,8 @@ func TestUpdater_Remove(t *testing.T) {
cfgPath := filepath.Join(ns.Dir(), updateConfigName)
updater, err := NewLocalUpdater(LocalUpdaterConfig{
InsecureSkipVerify: true,
InsecureSkipVerify: true,
ArtifactSignatureVerifiers: testArtifactSignatureVerifiers(t),
}, ns)
require.NoError(t, err)
updater.TeleportServiceName = serviceName
@@ -1705,7 +1740,8 @@ func TestUpdater_Install(t *testing.T) {
cfgPath := filepath.Join(ns.Dir(), updateConfigName)
updater, err := NewLocalUpdater(LocalUpdaterConfig{
InsecureSkipVerify: true,
InsecureSkipVerify: true,
ArtifactSignatureVerifiers: testArtifactSignatureVerifiers(t),
}, ns)
require.NoError(t, err)
@@ -1754,7 +1790,7 @@ func TestUpdater_Install(t *testing.T) {
revertSetupCalls int
)
updater.Installer = &testInstaller{
FuncInstall: func(_ context.Context, rev Revision, baseURL string, force bool) error {
FuncInstall: func(_ context.Context, rev Revision, baseURL string, force bool, insecureSkipSignatureVerify bool, enableStagingSignatureVerify bool) error {
installedRevision = rev
installedBaseURL = baseURL
return tt.installErr
@@ -1971,7 +2007,9 @@ func TestUpdater_Setup(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ns := &Namespace{}
updater, err := NewLocalUpdater(LocalUpdaterConfig{}, ns)
updater, err := NewLocalUpdater(LocalUpdaterConfig{
ArtifactSignatureVerifiers: testArtifactSignatureVerifiers(t),
}, ns)
require.NoError(t, err)
updater.Process = &testProcess{
@@ -2108,7 +2146,7 @@ func blankTestAddr(s []byte) []byte {
}
type testInstaller struct {
FuncInstall func(ctx context.Context, rev Revision, baseURL string, force bool) error
FuncInstall func(ctx context.Context, rev Revision, baseURL string, force bool, insecureSkipSignatureVerify bool, enableStagingSignatureVerify bool) error
FuncRemove func(ctx context.Context, rev Revision) error
FuncLink func(ctx context.Context, rev Revision, path string, force bool) (revert func(context.Context) bool, err error)
FuncLinkSystem func(ctx context.Context) (revert func(context.Context) bool, err error)
@@ -2120,8 +2158,8 @@ type testInstaller struct {
FuncIsLinked func(ctx context.Context, rev Revision, path string) (bool, error)
}
func (ti *testInstaller) Install(ctx context.Context, rev Revision, baseURL string, force bool) error {
return ti.FuncInstall(ctx, rev, baseURL, force)
func (ti *testInstaller) Install(ctx context.Context, rev Revision, baseURL string, force bool, insecureSkipSignatureVerify bool, enableStagingSignatureVerify bool) error {
return ti.FuncInstall(ctx, rev, baseURL, force, insecureSkipSignatureVerify, enableStagingSignatureVerify)
}
func (ti *testInstaller) Remove(ctx context.Context, rev Revision) error {
+64 -15
View File
@@ -29,6 +29,7 @@ import (
"time"
"github.com/gravitational/trace"
"github.com/sigstore/sigstore/pkg/signature"
"gopkg.in/yaml.v3"
"github.com/gravitational/teleport"
@@ -65,6 +66,26 @@ const (
var plog = logutils.NewPackageLogger(teleport.ComponentKey, teleport.ComponentUpdater)
// artifactSignaturePublicKeyB64 is injected into the teleport-update
// binary at build time via ldflags.
var artifactSignaturePublicKeyB64 string
// artifactSignatureBackupPublicKeyB64 is injected into the teleport-update
// binary at build time via ldflags.
var artifactSignatureBackupPublicKeyB64 string
// artifactSignatureAdditionalPublicKeyB64 is injected into the
// teleport-update binary at build time via ldflags. It provides an additional
// trusted artifact signing key beyond the primary/backup pair, for example to
// allow a staging-built updater to verify prod-signed artifacts.
var artifactSignatureAdditionalPublicKeyB64 string
// artifactSignatureAdditionalBackupPublicKeyB64 is injected into the
// teleport-update binary at build time via ldflags. It provides an additional
// trusted backup artifact signing key beyond the primary/backup pair, for
// example to allow a staging-built updater to verify prod-signed artifacts.
var artifactSignatureAdditionalBackupPublicKeyB64 string
func main() {
if code := Run(os.Args[1:]); code != 0 {
os.Exit(code)
@@ -113,6 +134,10 @@ func Run(args []string) int {
Hidden().StringVar(&ccfg.InstallDir)
app.Flag("insecure", "Insecure mode disables certificate verification. Do not use in production.").
BoolVar(&ccfg.Insecure)
app.Flag("insecure-skip-signature-verify", "Disable artifact signature verification and fall back to checksum-only verification. Do not use in production.").
Hidden().IsSetByUser(&ccfg.InsecureSkipSignatureVerifyChanged).BoolVar(&ccfg.InsecureSkipSignatureVerify)
app.Flag("enable-staging-signature-verify", "Enable detached signature verification for staging CDN artifacts that would otherwise skip verification").
IsSetByUser(&ccfg.EnableStagingSignatureVerifyChanged).BoolVar(&ccfg.EnableStagingSignatureVerify)
app.HelpFlag.Short('h')
@@ -277,6 +302,15 @@ func setupLogger(debug bool, format string) error {
return nil
}
func newArtifactSignatureVerifiers() ([]signature.Verifier, error) {
return autoupdate.NewArtifactSignatureVerifiers(
artifactSignaturePublicKeyB64,
artifactSignatureBackupPublicKeyB64,
artifactSignatureAdditionalPublicKeyB64,
artifactSignatureAdditionalBackupPublicKeyB64,
)
}
func initConfig(ctx context.Context, ccfg *cliConfig) (updater *autoupdate.Updater, lockFile string, err error) {
ns, err := autoupdate.NewNamespace(ctx, plog, ccfg.InstallSuffix, ccfg.InstallDir)
if err != nil {
@@ -286,12 +320,17 @@ func initConfig(ctx context.Context, ccfg *cliConfig) (updater *autoupdate.Updat
if err != nil {
return nil, "", trace.Wrap(err)
}
artifactSignatureVerifiers, err := newArtifactSignatureVerifiers()
if err != nil {
return nil, "", trace.Wrap(err)
}
updater, err = autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
SelfSetup: ccfg.SelfSetup,
Log: plog,
LogFormat: ccfg.LogFormat,
Debug: ccfg.Debug,
InsecureSkipVerify: ccfg.Insecure,
SelfSetup: ccfg.SelfSetup,
Log: plog,
LogFormat: ccfg.LogFormat,
Debug: ccfg.Debug,
InsecureSkipVerify: ccfg.Insecure,
ArtifactSignatureVerifiers: artifactSignatureVerifiers,
}, ns)
return updater, lockFile, trace.Wrap(err)
}
@@ -301,12 +340,17 @@ func statusConfig(ctx context.Context, ccfg *cliConfig) (*autoupdate.Updater, er
if err != nil {
return nil, trace.Wrap(err)
}
artifactSignatureVerifiers, err := newArtifactSignatureVerifiers()
if err != nil {
return nil, trace.Wrap(err)
}
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
SelfSetup: ccfg.SelfSetup,
Log: plog,
LogFormat: ccfg.LogFormat,
Debug: ccfg.Debug,
InsecureSkipVerify: ccfg.Insecure,
SelfSetup: ccfg.SelfSetup,
Log: plog,
LogFormat: ccfg.LogFormat,
Debug: ccfg.Debug,
InsecureSkipVerify: ccfg.Insecure,
ArtifactSignatureVerifiers: artifactSignatureVerifiers,
}, ns)
return updater, trace.Wrap(err)
}
@@ -466,12 +510,17 @@ func cmdSetup(ctx context.Context, ccfg *cliConfig) error {
if err != nil {
return trace.Wrap(err)
}
artifactSignatureVerifiers, err := newArtifactSignatureVerifiers()
if err != nil {
return trace.Wrap(err)
}
updater, err := autoupdate.NewLocalUpdater(autoupdate.LocalUpdaterConfig{
SelfSetup: ccfg.SelfSetup,
Log: plog,
LogFormat: ccfg.LogFormat,
Debug: ccfg.Debug,
InsecureSkipVerify: ccfg.Insecure,
SelfSetup: ccfg.SelfSetup,
Log: plog,
LogFormat: ccfg.LogFormat,
Debug: ccfg.Debug,
InsecureSkipVerify: ccfg.Insecure,
ArtifactSignatureVerifiers: artifactSignatureVerifiers,
}, ns)
if err != nil {
return trace.Wrap(err)