Compare commits

...

1 Commits

Author SHA1 Message Date
Raghav e44c053dcc feat: [CI-23743]: call TrustHarnessCA before wrapper auth + export for reuse
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

AI-Session-Id: 6ea09391-f44a-4dcd-9618-f17586946b96
AI-Tool: claude-code
AI-Model: unknown
2026-07-16 13:29:34 +05:30
8 changed files with 46 additions and 24 deletions
+10 -6
View File
@@ -123,8 +123,8 @@ store**, so base-image pulls fail with
`x509: certificate signed by unknown authority` unless that CA is trusted. `x509: certificate signed by unknown authority` unless that CA is trusted.
Set the `HARNESS_CA_PATH` environment variable to a PEM CA (bundle) file and the Set the `HARNESS_CA_PATH` environment variable to a PEM CA (bundle) file and the
plugin installs it into the host trust store **before starting the Docker plugin installs it into the host trust store **before any HTTPS** (registry
daemon**: wrapper auth such as GCP STS, and before starting the Docker daemon):
```yaml ```yaml
steps: steps:
@@ -146,18 +146,22 @@ Behavior:
`update-ca-certificates` / `update-ca-trust`. It also appends the CA directly `update-ca-certificates` / `update-ca-trust`. It also appends the CA directly
to the consolidated bundle (`/etc/ssl/certs/ca-certificates.crt` or the RHEL to the consolidated bundle (`/etc/ssl/certs/ca-certificates.crt` or the RHEL
equivalent) so trust holds even on minimal images without a refresh tool. equivalent) so trust holds even on minimal images without a refresh tool.
- **Windows** — imports the CA into the machine trust store via - **Windows** — imports the CA into `LocalMachine\Root` via the Windows
`certutil -addstore -f Root`. CryptoAPI (`crypt32.dll`). This works on Nano Server images that lack
`certutil` / PowerShell.
- **macOS / other** — logged no-op (not currently supported). - **macOS / other** — logged no-op (not currently supported).
Notes: Notes:
- Registry wrappers (`plugins/gcr`, `gar`, `acr`, `ecr`) call the same install
before their pre-docker HTTPS auth (e.g. `sts.googleapis.com` token exchange).
- It is **best-effort and idempotent**: when `HARNESS_CA_PATH` is unset, the file - It is **best-effort and idempotent**: when `HARNESS_CA_PATH` is unset, the file
is missing, or the file is empty, the plugin logs and continues. Re-running the is missing, or the file is empty, the plugin logs and continues. Re-running the
step will not duplicate the CA in the bundle. step will not duplicate the CA in the bundle.
- The CA is trusted for the daemon's own registry TLS (base-image pulls, cache - The CA is trusted for the daemon's own registry TLS (base-image pulls, cache
endpoints). To make the CA available to `RUN` steps inside the build, also pass endpoints) and for Go TLS clients in the plugin process. To make the CA
it as needed via the Dockerfile / build args. available to `RUN` steps inside the build, also pass it as needed via the
Dockerfile / build args.
- This is separate from the proxy build args - This is separate from the proxy build args
(`http_proxy`/`https_proxy`/`no_proxy`), which the plugin already injects from (`http_proxy`/`https_proxy`/`no_proxy`), which the plugin already injects from
the environment (including `HARNESS_`-prefixed variants). the environment (including `HARNESS_`-prefixed variants).
+4 -4
View File
@@ -31,7 +31,7 @@ func TestTrustHarnessCA_Unset(t *testing.T) {
os.Unsetenv("HARNESS_CA_PATH") os.Unsetenv("HARNESS_CA_PATH")
_, calls := withStubbedInstall(t) _, calls := withStubbedInstall(t)
trustHarnessCA() TrustHarnessCA()
if *calls != 0 { if *calls != 0 {
t.Fatalf("expected installer not to be called when HARNESS_CA_PATH is unset, got %d calls", *calls) t.Fatalf("expected installer not to be called when HARNESS_CA_PATH is unset, got %d calls", *calls)
@@ -44,7 +44,7 @@ func TestTrustHarnessCA_MissingFile(t *testing.T) {
defer os.Unsetenv("HARNESS_CA_PATH") defer os.Unsetenv("HARNESS_CA_PATH")
_, calls := withStubbedInstall(t) _, calls := withStubbedInstall(t)
trustHarnessCA() TrustHarnessCA()
if *calls != 0 { if *calls != 0 {
t.Fatalf("expected installer not to be called when CA file is missing, got %d calls", *calls) t.Fatalf("expected installer not to be called when CA file is missing, got %d calls", *calls)
@@ -60,7 +60,7 @@ func TestTrustHarnessCA_EmptyFile(t *testing.T) {
defer os.Unsetenv("HARNESS_CA_PATH") defer os.Unsetenv("HARNESS_CA_PATH")
_, calls := withStubbedInstall(t) _, calls := withStubbedInstall(t)
trustHarnessCA() TrustHarnessCA()
if *calls != 0 { if *calls != 0 {
t.Fatalf("expected installer not to be called for whitespace-only CA, got %d calls", *calls) t.Fatalf("expected installer not to be called for whitespace-only CA, got %d calls", *calls)
@@ -76,7 +76,7 @@ func TestTrustHarnessCA_ValidFile(t *testing.T) {
defer os.Unsetenv("HARNESS_CA_PATH") defer os.Unsetenv("HARNESS_CA_PATH")
captured, calls := withStubbedInstall(t) captured, calls := withStubbedInstall(t)
trustHarnessCA() TrustHarnessCA()
if *calls != 1 { if *calls != 1 {
t.Fatalf("expected installer to be called once for a valid CA, got %d calls", *calls) t.Fatalf("expected installer to be called once for a valid CA, got %d calls", *calls)
+4
View File
@@ -52,6 +52,10 @@ func main() {
godotenv.Load(env) godotenv.Load(env)
} }
// Must run before Azure / ACR HTTPS auth: this binary does auth before
// spawning drone-docker, which is where TrustHarnessCA also runs for the daemon.
docker.TrustHarnessCA()
var ( var (
repo = getenv("PLUGIN_REPO") repo = getenv("PLUGIN_REPO")
registry = getenv("PLUGIN_REGISTRY") registry = getenv("PLUGIN_REGISTRY")
+4
View File
@@ -24,6 +24,10 @@ func main() {
godotenv.Load(env) godotenv.Load(env)
} }
// Install egress CA before any TLS in this process (including the Docker
// daemon started from Plugin.Exec).
docker.TrustHarnessCA()
app := cli.NewApp() app := cli.NewApp()
app.Name = "docker plugin" app.Name = "docker plugin"
app.Usage = "docker plugin" app.Usage = "docker plugin"
+4
View File
@@ -30,6 +30,10 @@ func main() {
godotenv.Load(env) godotenv.Load(env)
} }
// Must run before AWS / ECR HTTPS auth: this binary does auth before
// spawning drone-docker, which is where TrustHarnessCA also runs for the daemon.
docker.TrustHarnessCA()
var ( var (
repo = getenv("PLUGIN_REPO") repo = getenv("PLUGIN_REPO")
registry = getenv("PLUGIN_REGISTRY") registry = getenv("PLUGIN_REGISTRY")
+4
View File
@@ -49,6 +49,10 @@ func loadConfig() Config {
} }
} }
// Must run before STS / oauth HTTPS: this binary does auth before spawning
// drone-docker, which is where TrustHarnessCA also runs for the daemon.
docker.TrustHarnessCA()
idToken := getenv("PLUGIN_OIDC_TOKEN_ID") idToken := getenv("PLUGIN_OIDC_TOKEN_ID")
projectId := getenv("PLUGIN_PROJECT_NUMBER") projectId := getenv("PLUGIN_PROJECT_NUMBER")
poolId := getenv("PLUGIN_POOL_ID") poolId := getenv("PLUGIN_POOL_ID")
+4
View File
@@ -39,6 +39,10 @@ func loadConfig() Config {
} }
} }
// Must run before STS / oauth HTTPS: this binary does auth before spawning
// drone-docker, which is where TrustHarnessCA also runs for the daemon.
docker.TrustHarnessCA()
idToken := getenv("PLUGIN_OIDC_TOKEN_ID") idToken := getenv("PLUGIN_OIDC_TOKEN_ID")
projectId := getenv("PLUGIN_PROJECT_NUMBER") projectId := getenv("PLUGIN_PROJECT_NUMBER")
poolId := getenv("PLUGIN_POOL_ID") poolId := getenv("PLUGIN_POOL_ID")
+12 -14
View File
@@ -132,14 +132,6 @@ type (
// Exec executes the plugin step // Exec executes the plugin step
func (p Plugin) Exec() error { func (p Plugin) Exec() error {
// Trust the Harness egress-proxy CA (if HARNESS_CA_PATH is set) BEFORE the
// daemon starts. The Docker daemon and its embedded BuildKit verify registry
// TLS against the host system trust store; when traffic goes through the
// TLS-intercepting egress proxy, the presented cert is signed by the Harness
// CA and must be trusted here or base-image pulls fail with an x509
// "unknown authority" error.
trustHarnessCA()
// start the Docker daemon server // start the Docker daemon server
if !p.Daemon.Disabled { if !p.Daemon.Disabled {
p.startDaemon() p.startDaemon()
@@ -583,17 +575,23 @@ func getSecretCmdArg(kvp string, file bool) (string, error) {
return fmt.Sprintf("id=%s,env=%s", key, value), nil return fmt.Sprintf("id=%s,env=%s", key, value), nil
} }
// trustHarnessCA installs the Harness egress-proxy CA into the host trust store // TrustHarnessCA installs the Harness egress-proxy CA into the host trust store
// so the Docker daemon / embedded BuildKit trust the TLS-intercepting proxy when // so the Docker daemon / embedded BuildKit (and this process's Go TLS clients)
// pulling base images. It is driven by HARNESS_CA_PATH (path to a PEM CA bundle) // trust the TLS-intercepting proxy. It is driven by HARNESS_CA_PATH (path to a
// and is a best-effort no-op when the var is unset, the file is missing, or the // PEM CA bundle) and is a best-effort no-op when the var is unset, the file is
// platform isn't supported — build failures are surfaced later by docker itself. // missing, or the platform isn't supported — build failures are surfaced later
// by docker itself.
//
// Call from each binary's main (after loading PLUGIN_ENV_FILE) before any HTTPS:
// Go caches SystemCertPool on first use, and registry wrappers (gcr/gar/acr/ecr)
// perform auth HTTPS before spawning drone-docker.
//
// The platform-specific installation is provided by installHarnessCA (see // The platform-specific installation is provided by installHarnessCA (see
// ca_linux.go / ca_windows.go / ca_other.go); it is indirected through // ca_linux.go / ca_windows.go / ca_other.go); it is indirected through
// installHarnessCAFn so tests can stub the actual system-trust write. // installHarnessCAFn so tests can stub the actual system-trust write.
var installHarnessCAFn = installHarnessCA var installHarnessCAFn = installHarnessCA
func trustHarnessCA() { func TrustHarnessCA() {
caPath := os.Getenv("HARNESS_CA_PATH") caPath := os.Getenv("HARNESS_CA_PATH")
if caPath == "" { if caPath == "" {
return return