Skip to content

Fix: Add ECDSA certificate support for internal TLS (#20082)#22861

Draft
rossigee wants to merge 12 commits intogoharbor:mainfrom
rossigee:fix/ecdsa-tls-issue-20082
Draft

Fix: Add ECDSA certificate support for internal TLS (#20082)#22861
rossigee wants to merge 12 commits intogoharbor:mainfrom
rossigee:fix/ecdsa-tls-issue-20082

Conversation

@rossigee
Copy link

Description

This PR adds complete support for ECDSA certificates in Harbor's internal TLS configuration, addressing the token parsing failures that occurred when using ECDSA keys for registry authentication.

Problem

Harbor's token service failed to parse ECDSA private keys with the error: x509: failed to parse private key (use ParseECPrivateKey instead for this key format). This prevented users from using ECDSA certificates with cert-manager for Harbor's internal TLS, despite the TLS cipher suites already supporting ECDSA.

Solution

Enhanced the token package to provide full ECDSA support equivalent to existing RSA functionality.

Changes

Core Functionality

  • Algorithm Detection: Automatic JWT signing method selection based on ECDSA curve:
    • P-256 → ES256, P-384 → ES384, P-521 → ES512
  • Key Format Support: Both SEC1 (EC PRIVATE KEY) and PKCS#8 (PRIVATE KEY) formats
  • Key Validation: Consistent public/private key consistency checks matching RSA behavior

Technical Details

  • NewOptions: Enhanced to detect and parse ECDSA keys, properly handle PEM decoding
  • GetKey: Added ECDSA case with full key validation ensuring public/private key matching
  • Error Handling: Consistent error messages and validation logic across RSA/ECDSA

Testing & Quality

  • Comprehensive Test Suite: 5 new test scenarios covering all edge cases
  • Key Validation Tests: Ensures mismatched keys are properly rejected
  • Format Coverage: SEC1, PKCS8, and PKIX public key formats
  • Curve Coverage: P-256, P-384, P-521 curves fully tested
  • Linting: Code passes all quality checks

Files Changed

  • src/pkg/token/options.go - Core ECDSA support and validation
  • src/pkg/token/option_test.go - Comprehensive test coverage
  • src/pkg/token/token_test.go - Test infrastructure updates
  • src/.golangci.yaml - Linting configuration

Backward Compatibility

No breaking changes - Existing RSA configurations continue to work unchanged

Testing

# All tests pass
cd src && go test ./pkg/token/...

# Linting clean
cd src && golangci-lint run ./pkg/token/...

# Verified scenarios:
- ECDSA key parsing (SEC1/PKCS8 formats)
- JWT algorithm detection (P-256/P-384/P-521)
- Key validation (matching/mismatching public-private pairs)
- Error handling (missing keys, malformed keys)

Related Issue

Fixes #20082 - ECDSA certificates for internal tls aren't supported

@codecov
Copy link

codecov bot commented Feb 23, 2026

Codecov Report

❌ Patch coverage is 70.49180% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.01%. Comparing base (c8c11b4) to head (4da2bef).
⚠️ Report is 674 commits behind head on main.

Files with missing lines Patch % Lines
src/pkg/token/options.go 70.49% 14 Missing and 4 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##             main   #22861       +/-   ##
===========================================
+ Coverage   45.36%   66.01%   +20.64%     
===========================================
  Files         244     1074      +830     
  Lines       13333   116474   +103141     
  Branches     2719     2937      +218     
===========================================
+ Hits         6049    76891    +70842     
- Misses       6983    35330    +28347     
- Partials      301     4253     +3952     
Flag Coverage Δ
unittests 66.01% <70.49%> (+20.64%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/pkg/token/options.go 59.59% <70.49%> (ø)

... and 988 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds ECDSA private key support to Harbor’s internal token signing flow by auto-detecting key type/curve and enabling ECDSA key parsing/validation, with accompanying tests and lint config updates.

Changes:

  • Enhance NewOptions to parse PEM private keys (RSA or ECDSA) and auto-select the appropriate JWT signing method (ES256/ES384/ES512).
  • Extend Options.GetKey() to support ECDSA keys and validate public/private key consistency.
  • Update tests to be self-contained (no dependency on /etc/core/private_key.pem) and add ECDSA-specific coverage; adjust golangci-lint exclusions.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
src/pkg/token/options.go Adds ECDSA support and key-type/curve detection logic in options/key handling.
src/pkg/token/option_test.go Adds ECDSA-focused test cases and helpers for key generation/format coverage.
src/pkg/token/token_test.go Makes token tests independent of external key files by generating a temp RSA key in TestMain.
src/.golangci.yaml Updates revive exclusion paths to include token packages.
Comments suppressed due to low confidence (1)

src/pkg/token/token_test.go:45

  • defer os.Remove(f.Name()) won’t run when os.Exit(result) is executed (ie on test failures), so the temp key file will be left behind in CI/dev machines. To ensure cleanup, remove the file explicitly before calling os.Exit, or avoid os.Exit and return the code to a single os.Exit(m.Run()) after cleanup.
	f, err := os.CreateTemp("", "harbor-test-key-*.pem")
	if err != nil {
		panic(err)
	}
	defer os.Remove(f.Name())
	if err := pem.Encode(f, &pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: x509.MarshalPKCS1PrivateKey(key),
	}); err != nil {
		panic(err)
	}
	f.Close()
	os.Setenv("TOKEN_PRIVATE_KEY_PATH", f.Name())

	config.Init()

	result := m.Run()
	if result != 0 {
		os.Exit(result)
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Vad1mo
Copy link
Member

Vad1mo commented Feb 23, 2026

@rossigee DCO is missing

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

dependabot bot and others added 9 commits March 1, 2026 07:25
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: Ross Golder <ross@golder.org>
- NewOptions: detect signing method from key type (P-256→ES256, P-384→ES384,
  P-521→ES512); add EC PRIVATE KEY (SEC1) PEM block support; fix PKCS#8 parse
  to use DER block.Bytes instead of raw PEM bytes
- GetKey: add ECDSA key validation ensuring public/private key consistency
- Tests: comprehensive test coverage including key validation, error cases,
  different curves (P-256, P-384, P-521), and PEM formats (SEC1, PKCS8)
- golangci.yaml: extend existing token package revive exclusion to cover
  pkg/token

Signed-off-by: Ross Golder <ross@golder.org>
…or#22812)

* fix: Update Verify Remote Cert tooltip for registry endpoints
Fixes goharbor#22806

Signed-off-by: dee077 <deepanshu.sahu27@gmail.com>

* Add for all i18n languages

Signed-off-by: dee077 <deepanshu.sahu27@gmail.com>

---------

Signed-off-by: dee077 <deepanshu.sahu27@gmail.com>
Co-authored-by: Wang Yan <wangyan@vmware.com>
Signed-off-by: Ross Golder <ross@golder.org>
Signed-off-by: Bin Liu <lb203159@antfin.com>
Co-authored-by: Bin Liu <lb203159@antfin.com>
Signed-off-by: Ross Golder <ross@golder.org>
- Remove redundant defer in TestMain (redundant with explicit cleanup)
- Add P-521 PKCS8 test case for complete curve coverage
- Add TestNewAndRawWithECDSA integration test for end-to-end verification
- Document deprecated first param in NewOptions function

Signed-off-by: Ross Golder <ross@golder.org>
…22873)

Signed-off-by: chlins <chlins.zhang@gmail.com>
Signed-off-by: Ross Golder <ross@golder.org>
…2702)

* chore(go): ignore frontend portal directory using Go 1.25

Signed-off-by: Iam-karan-suresh <karansuresh.info@gmail.com>

* chore(go): ignore frontend portal directory using Go 1.25

Signed-off-by: Iam-karan-suresh <karansuresh.info@gmail.com>

* chore(go): ignore frontend portal directory using Go 1.25

Signed-off-by: Iam-karan-suresh <karansuresh.info@gmail.com>

---------

Signed-off-by: Iam-karan-suresh <karansuresh.info@gmail.com>
Signed-off-by: Karan Suresh <karansuresh.info@gmail.com>
Co-authored-by: Wang Yan <wangyan@vmware.com>
Signed-off-by: Ross Golder <ross@golder.org>
Signed-off-by: Bin Liu <lb203159@antfin.com>
Co-authored-by: Bin Liu <lb203159@antfin.com>
Signed-off-by: Ross Golder <ross@golder.org>
Fix two bugs in the RSA GetKey path:
- Wrong error message "key is provided" → "no key provided"
- Mismatch check used && instead of || allowing mismatched RSA key pairs
  with a common exponent (e.g. E=65537) to pass validation silently

Add missing tests:
- TestGetKeyRSA: no-key error, public-key-only, mismatch rejection, matching pair
- TestGetKeyECDSA: add PKCS8 private key subtest
- TestNewAndRawWithECDSA: expand to table test covering P-384/P-521 and PKCS8 format end-to-end
- TestNewOptionsErrors: file not found, empty file, unsupported PEM block type
- TestNewOptionsMultiBlockPEM: EC PARAMETERS + EC PRIVATE KEY multi-block files (OpenSSL format)
- TestNewOptionsRSAPKCS8: PKCS8-wrapped RSA key via NewOptions

Signed-off-by: Ross Golder <ross@golder.org>
@rossigee rossigee force-pushed the fix/ecdsa-tls-issue-20082 branch from 54c456d to 78d121b Compare March 1, 2026 00:25
rossigee added 3 commits March 1, 2026 07:26
Replace the non-compiling manual algorithm check (which dropped
jwt.WithValidMethods and imported fmt/strings without declaring them)
with a key-type-derived allowlist passed to WithValidMethods.

RSA keys accept RS256/384/512; ECDSA keys accept ES256/384/512.
This preserves algorithm-confusion protection while supporting all
curve/hash variants without requiring an exact match to the configured
signing method.

Also handle *rsa.PublicKey and *ecdsa.PublicKey in the keyfunc to
support verify-only (public-key-only) configurations.
Rename package declarations (not directories) to eliminate the revive
var-naming violations that were previously suppressed with a TODO comment:

  pkg/token          → jwttoken
  core/service/token → tokensvc
  server/middleware/trace → mwtrace

Import paths are unchanged; only the in-package identifier changes.
The golangci.yaml exclusion for these three paths is removed entirely.
All call sites updated; goimports applied to affected files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ECDSA certificates for internal tls aren't supported

7 participants