Conversation
| return NewTransport( | ||
| WithCustomCACert(cfg.CACertificate), | ||
| ) | ||
| } |
There was a problem hiding this comment.
Bug: CA Cert Silently Overrides Insecure Flag
When a custom CA certificate is provided, GetHTTPTransport creates a new transport with only the custom CA configured, ignoring the Insecure flag. This means if both WithInsecure(true) and WithCACert(cert) are passed, the insecure setting is silently dropped, potentially causing TLS verification to fail when the user expects it to be skipped.
There was a problem hiding this comment.
Pull Request Overview
This PR adds CA certificate support for registry endpoints in Harbor, allowing users to provide custom PEM-encoded CA certificates to verify TLS connections to registries using self-signed or private CA certificates.
- Added
CACertificatefield to registry models throughout the codebase (API, database, UI) - Implemented CA certificate validation and custom transport configuration
- Updated all registry adapter implementations to support custom CA certificates
Reviewed Changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/common/http/transport.go | Added CA certificate validation and custom transport configuration with WithCustomCACert and ValidateCACertificate functions |
| src/common/http/transport_test.go | Added comprehensive tests for CA certificate validation and transport configuration |
| src/pkg/registry/client.go | Added NewClientWithCACert function and updated NewClientWithAuthorizer to support CA certificates |
| src/pkg/registry/auth/authorizer.go | Updated NewAuthorizer to accept optional CA certificate parameter |
| src/pkg/reg/model/registry.go | Added CACertificate field to Registry model |
| src/pkg/reg/dao/model.go | Added ca_certificate column to database model |
| src/pkg/reg/manager.go | Updated model conversion functions to include CA certificate field |
| src/pkg/reg/util/util.go | Updated GetHTTPTransport and Ping to support CA certificates |
| src/server/v2.0/handler/registry.go | Added CA certificate validation in CreateRegistry, UpdateRegistry, and PingRegistry endpoints |
| src/server/v2.0/handler/replication.go | Updated convertRegistry to include CA certificate in response |
| src/portal/src/app/shared/services/interface.ts | Added ca_certificate field to Endpoint and PingEndpoint interfaces |
| src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.ts | Added CA certificate to payload in ping operation |
| src/portal/src/app/base/left-side-nav/registries/create-edit-endpoint/create-edit-endpoint.component.html | Added CA certificate textarea field with tooltip, disabled when insecure mode is enabled |
| src/portal/src/i18n/lang/*.json | Added translations for CA certificate labels, tooltips, and helper text in 10 languages |
| make/migrations/postgresql/0180_2.15.0_schema.up.sql | Added database migration to add ca_certificate column to registry table |
| api/v2.0/swagger.yaml | Updated API schema to include ca_certificate field in registry-related definitions |
| src/pkg/reg/adapter/* | Updated all registry adapter implementations (AWS ECR, Azure ACR, AliACR, GitHub CR, GitLab, Harbor, Huawei, JFrog, Quay, Tencent CR, Volcengine CR, Docker Hub, DTR) to pass CA certificate when creating HTTP transports |
| src/controller/proxy/local.go | Updated local proxy helper to pass empty CA certificate string to maintain backward compatibility |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/common/http/transport_test.go
Outdated
| subjects := httpTransport.TLSClientConfig.RootCAs.Subjects() | ||
| assert.Empty(t, subjects, "RootCAs should be empty for invalid cert") |
There was a problem hiding this comment.
Same issue as above: RootCAs.Subjects() is deprecated as of Go 1.18. Consider using an alternative method to verify the certificate pool is empty.
src/common/http/transport.go
Outdated
| // Verify signature for self-signed certificates (root CAs) | ||
| //if cert.Subject.String() == cert.Issuer.String() { | ||
| // if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil { | ||
| // log.Errorf("CA certificate signature verification failed: %v", err) | ||
| // continue | ||
| // } | ||
| //} |
There was a problem hiding this comment.
The commented-out code for certificate signature verification should either be removed or uncommented with a clear explanation. Keeping commented code in production can lead to confusion about intended behavior and creates maintenance debt.
| // Verify signature for self-signed certificates (root CAs) | |
| //if cert.Subject.String() == cert.Issuer.String() { | |
| // if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil { | |
| // log.Errorf("CA certificate signature verification failed: %v", err) | |
| // continue | |
| // } | |
| //} | |
| // Verify signature for self-signed certificates (root CAs). | |
| // This ensures that only valid root CA certificates are trusted. | |
| if cert.Subject.String() == cert.Issuer.String() { | |
| if err := cert.CheckSignature(cert.SignatureAlgorithm, cert.RawTBSCertificate, cert.Signature); err != nil { | |
| log.Errorf("CA certificate signature verification failed: %v", err) | |
| continue | |
| } | |
| } |
| if cfg.CACertificate != "" { | ||
| return NewTransport( | ||
| WithCustomCACert(cfg.CACertificate), | ||
| ) | ||
| } |
There was a problem hiding this comment.
When a CA certificate is provided in GetHTTPTransport, the insecure flag is not applied to the transport. This means that if both WithInsecure(true) and WithCACert(caCert) are passed, InsecureSkipVerify will not be set on the TLSClientConfig, which may not match user expectations. Consider either:
- Setting InsecureSkipVerify when insecure=true even with a custom CA, or
- Documenting this behavior clearly in the function comment
Currently, the priority documentation mentions CA cert takes precedence, but doesn't explicitly state that the insecure flag is ignored when a CA cert is provided.
src/common/http/transport.go
Outdated
| if !strings.HasPrefix(caCert, "-----BEGIN CERTIFICATE-----") { | ||
| return errors.New("invalid CA certificate: must start with -----BEGIN CERTIFICATE-----") | ||
| } | ||
|
|
||
| if !strings.HasSuffix(caCert, "-----END CERTIFICATE-----") { | ||
| return errors.New("invalid CA certificate: must end with -----END CERTIFICATE-----") | ||
| } | ||
|
|
There was a problem hiding this comment.
The validation only checks for the presence of "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" markers, but doesn't validate that they appear in the correct order or that there's actual content between them. A certificate like "-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----" would pass these checks but fail later. Consider using pem.Decode to check proper formatting before attempting to parse.
| if !strings.HasPrefix(caCert, "-----BEGIN CERTIFICATE-----") { | |
| return errors.New("invalid CA certificate: must start with -----BEGIN CERTIFICATE-----") | |
| } | |
| if !strings.HasSuffix(caCert, "-----END CERTIFICATE-----") { | |
| return errors.New("invalid CA certificate: must end with -----END CERTIFICATE-----") | |
| } | |
| block, _ := pem.Decode([]byte(caCert)) | |
| if block == nil || block.Type != "CERTIFICATE" || len(block.Bytes) == 0 { | |
| return errors.New("invalid CA certificate: not a valid PEM encoded CERTIFICATE block") | |
| } |
| if len(caCert) > 0 && caCert[0] != "" { | ||
| transport = commonhttp.GetHTTPTransport( | ||
| commonhttp.WithInsecure(insecure), | ||
| commonhttp.WithCACert(caCert[0]), | ||
| ) |
There was a problem hiding this comment.
When insecure=true and a CA certificate is provided together in NewAuthorizer, the transport will have a custom CA but won't have InsecureSkipVerify set (due to the priority in GetHTTPTransport). This could lead to unexpected behavior where certificate validation still occurs despite the insecure flag being true. Consider handling this case explicitly or documenting the interaction between these two parameters.
| if len(caCert) > 0 && caCert[0] != "" { | |
| transport = commonhttp.GetHTTPTransport( | |
| commonhttp.WithInsecure(insecure), | |
| commonhttp.WithCACert(caCert[0]), | |
| ) | |
| // If both insecure and caCert are provided, ensure InsecureSkipVerify is set | |
| if len(caCert) > 0 && caCert[0] != "" { | |
| if insecure { | |
| // Both insecure and CA cert provided: set both options | |
| transport = commonhttp.GetHTTPTransport( | |
| commonhttp.WithInsecure(true), | |
| commonhttp.WithCACert(caCert[0]), | |
| ) | |
| } else { | |
| transport = commonhttp.GetHTTPTransport( | |
| commonhttp.WithCACert(caCert[0]), | |
| ) | |
| } |
src/pkg/registry/client.go
Outdated
| // Custom CA certificate takes precedence | ||
| transport = commonhttp.GetHTTPTransport(commonhttp.WithCACert(caCert)) |
There was a problem hiding this comment.
When caCert != "", the function creates a new transport with only the CA certificate option, ignoring the insecure parameter. If insecure=true is passed along with a CA certificate, the InsecureSkipVerify flag won't be set on the TLSClientConfig. This is inconsistent with the behavior in NewAuthorizer where both options are passed to GetHTTPTransport. Consider applying the insecure flag when creating the transport:
if caCert != "" {
transport = commonhttp.GetHTTPTransport(
commonhttp.WithInsecure(insecure),
commonhttp.WithCACert(caCert))
}| // Custom CA certificate takes precedence | |
| transport = commonhttp.GetHTTPTransport(commonhttp.WithCACert(caCert)) | |
| // Apply both insecure and custom CA certificate options | |
| transport = commonhttp.GetHTTPTransport( | |
| commonhttp.WithInsecure(insecure), | |
| commonhttp.WithCACert(caCert)) |
src/common/http/transport_test.go
Outdated
| subjects := tr.TLSClientConfig.RootCAs.Subjects() | ||
| assert.Empty(t, subjects, "RootCAs should be empty for invalid cert") |
There was a problem hiding this comment.
The test verifies that RootCAs is not nil and empty for invalid certificates, but there's a potential issue: RootCAs.Subjects() is deprecated as of Go 1.18. Consider using len(tr.TLSClientConfig.RootCAs.Subjects()) == 0 or checking in a different way that doesn't rely on the deprecated Subjects() method to ensure forward compatibility.
6937fa7 to
51580ce
Compare
Signed-off-by: chlins <chlins.zhang@gmail.com>
…2581) fix: resolve 'docker-compose: command not found' error Add fallback to support Docker Compose V2 plugin format when standalone docker-compose binary is not available. Signed-off-by: coldenchen <coldenchen@tencent.com> Co-authored-by: coldenchen <coldenchen@tencent.com>
Signed-off-by: chlins <chlins.zhang@gmail.com>
…goharbor#22587) removed unused lang translation Signed-off-by: carsontham <carsontham@outlook.com> Co-authored-by: carsontham <carsontham@outlook.com>
Signed-off-by: stonezdj <stone.zhang@broadcom.com>
… in /src (goharbor#22600) chore(deps): bump github.com/go-openapi/runtime in /src Bumps [github.com/go-openapi/runtime](https://github.com/go-openapi/runtime) from 0.28.0 to 0.29.2. - [Release notes](https://github.com/go-openapi/runtime/releases) - [Commits](go-openapi/runtime@v0.28.0...v0.29.2) --- updated-dependencies: - dependency-name: github.com/go-openapi/runtime dependency-version: 0.29.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: miner <miner.yang@broadcom.com>
…oharbor#22579) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.43.0 to 0.45.0. - [Commits](golang/crypto@v0.43.0...v0.45.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.45.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…or#22617) - Fix broken placeholder syntax in 'SCAN_BY' value - Update various UI strings for better readability - Translate previously untranslated keys (MANDATORY, OVERRIDE, etc.) Signed-off-by: g-yunjh <yunjh1126@naver.com>
Signed-off-by: stonezdj <stone.zhang@broadcom.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@v5...v6) --- updated-dependencies: - dependency-name: actions/checkout 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>
fix(ui): remove unwanted scrollbar from tag-retention Signed-off-by: bupd <bupdprasanth@gmail.com>
… 5.1.1 (goharbor#22599) chore(deps): bump aws-actions/configure-aws-credentials Bumps [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) from 5.0.0 to 5.1.1. - [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases) - [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md) - [Commits](aws-actions/configure-aws-credentials@v5.0.0...v5.1.1) --- updated-dependencies: - dependency-name: aws-actions/configure-aws-credentials dependency-version: 5.1.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* feat(cosign): support Cosign v3 bundle format Signed-off-by: Aloui-Ikram <ikram@container-registry.com> * fix: format code with gofmt Signed-off-by: Aloui-Ikram <ikram@container-registry.com> * refactor: rename variable to mediaTypeCosignArtifactType Signed-off-by: Aloui-Ikram <ikram@container-registry.com> --------- Signed-off-by: Aloui-Ikram <ikram@container-registry.com> Co-authored-by: Aloui-Ikram <ikram@container-registry.com> Co-authored-by: miner <miner.yang@broadcom.com>
…oharbor#22623) Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.34.1 to 0.34.2. - [Commits](kubernetes/apimachinery@v0.34.1...v0.34.2) --- updated-dependencies: - dependency-name: k8s.io/apimachinery dependency-version: 0.34.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: bupd <bupdprasanth@gmail.com>
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](actions/upload-artifact@v5...v6) --- updated-dependencies: - dependency-name: actions/upload-artifact 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>
…bor#22656) Signed-off-by: chlins <chlins.zhang@gmail.com>
Remove the unused function MostMatchSorter, it should not be implemented in golang, should be implement in the db query. Remove the unused function onBoardCommonUserGroup() fixes goharbor#22573 Signed-off-by: stonezdj <stonezdj@gmail.com>
refine apitest Signed-off-by: my036811 <miner.yang@broadcom.com>
|
This PR is being marked stale due to a period of inactivty. If this PR is still relevant, please comment or remove the stale label. Otherwise, this PR will close in 30 days. |
fixes goharbor#22203 Allowing Harbor to trust self-signed or private CA certificates for individual registry endpoints without modifying the system-level trust store. 1. Database schema changes, API updates with PEM validation. 2. HTTP transport layer modifications across all the registry adapters. 3. UI field to fill in the certificate. The feature is backward compatible - existing installations using system-level CA trust will continue to work without any changes. Signed-off-by: wang yan <yan-yw.wang@broadcom.com>
Signed-off-by: wang yan <yan-yw.wang@broadcom.com>
Signed-off-by: wang yan <yan-yw.wang@broadcom.com>
Signed-off-by: wang yan <yan-yw.wang@broadcom.com>
Signed-off-by: wang yan <yan-yw.wang@broadcom.com>
| if err != nil { | ||
| // if push chunk error, we should query the upload progress for new location and end range. | ||
| newLocation, newEnd, err1 := c.getUploadStatus(location) | ||
| newLocation, newEnd, err1 := c.getUploadStatus(url) |
There was a problem hiding this comment.
Wrong URL passed to upload status query
High Severity
The getUploadStatus call incorrectly passes url instead of location. The url variable contains query parameters added by buildChunkBlobUploadURL (including digest for the last chunk), while getUploadStatus expects the base upload location URL without query parameters. This causes the upload status query to fail or return incorrect results when a chunk upload fails, breaking error recovery for chunked blob uploads.
Signed-off-by: wang yan <yan-yw.wang@broadcom.com>
Thank you for contributing to Harbor!
Comprehensive Summary of your change
Issue being fixed
Fixes #(issue)
Please indicate you've done the following: