From 867db9438a0bbe759a7b0f36d266154f19bd1cf1 Mon Sep 17 00:00:00 2001 From: Abhishek Choudhary Date: Mon, 27 Jul 2026 13:02:08 +0545 Subject: [PATCH 1/3] feat: support a CA bundle for the control plane connection tlsVerify offered only two states: verify against the system trust store, or do not verify at all. A control plane using a self-signed or private-CA certificate has no way to satisfy the first, so the only escape from the connection error is tlsVerify: false -- which turns the insecure opt-out into copy-paste boilerplate. Add the missing third state: an optional PEM-encoded caBundle on GatewayProxy.spec.provider.controlPlane, carried through the translated config to the ADC server, which verifies the control plane against it in place of the system trust store. Unusable CA material is rejected up front -- by a CEL rule at admission and by a PEM parse in the translator -- rather than surfacing later as an opaque TLS failure. --- api/adc/types.go | 7 ++ api/v1alpha1/gatewayproxy_types.go | 8 ++ config/crd-nocel/apisix.apache.org_v2.yaml | 7 ++ .../apisix.apache.org_gatewayproxies.yaml | 10 ++ docs/en/latest/reference/api-reference.md | 1 + internal/adc/client/executor.go | 9 +- internal/adc/client/executor_test.go | 63 +++++++++++ internal/adc/translator/gatewayproxy.go | 12 +++ internal/adc/translator/gatewayproxy_test.go | 101 ++++++++++++++++++ 9 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 internal/adc/client/executor_test.go create mode 100644 internal/adc/translator/gatewayproxy_test.go diff --git a/api/adc/types.go b/api/adc/types.go index 4149be1d..9a8d4f2e 100644 --- a/api/adc/types.go +++ b/api/adc/types.go @@ -806,6 +806,11 @@ type Config struct { Token string TlsVerify bool BackendType string + + // CaBundle is a PEM-encoded CA certificate (or bundle) used to verify the + // control plane, in place of the system trust store. Only meaningful when + // TlsVerify is true. + CaBundle string } // MarshalJSON implements custom JSON marshaling for adcConfig @@ -815,10 +820,12 @@ func (c Config) MarshalJSON() ([]byte, error) { Name string `json:"name"` ServerAddrs []string `json:"serverAddrs"` TlsVerify bool `json:"tlsVerify"` + HasCaBundle bool `json:"hasCaBundle"` }{ Name: c.Name, ServerAddrs: c.ServerAddrs, TlsVerify: c.TlsVerify, + HasCaBundle: c.CaBundle != "", }) } diff --git a/api/v1alpha1/gatewayproxy_types.go b/api/v1alpha1/gatewayproxy_types.go index 680fa8a9..629c780c 100644 --- a/api/v1alpha1/gatewayproxy_types.go +++ b/api/v1alpha1/gatewayproxy_types.go @@ -120,6 +120,7 @@ type ControlPlaneAuth struct { // ControlPlaneProvider defines configuration for control plane provider. // +kubebuilder:validation:XValidation:rule="has(self.endpoints) != has(self.service)" // +kubebuilder:validation:XValidation:rule="oldSelf == null || (!has(self.mode) && !has(oldSelf.mode)) || self.mode == oldSelf.mode",message="mode is immutable" +// +kubebuilder:validation:XValidation:rule="!has(self.caBundle) || self.caBundle.contains('-----BEGIN CERTIFICATE-----')",message="caBundle must be a PEM-encoded certificate" type ControlPlaneProvider struct { // Mode specifies the mode of control plane provider. // Can be `apisix` or `apisix-standalone`. @@ -136,6 +137,13 @@ type ControlPlaneProvider struct { // +optional TlsVerify *bool `json:"tlsVerify,omitempty"` + // CaBundle is a PEM-encoded CA certificate (or bundle) used to verify the + // control plane's TLS certificate, in place of the system trust store. + // Set it when the control plane uses a self-signed or private CA certificate. + // It has no effect when tlsVerify is false. + // +optional + CaBundle string `json:"caBundle,omitempty"` + // Auth specifies the authentication configuration. // +kubebuilder:validation:Required Auth ControlPlaneAuth `json:"auth"` diff --git a/config/crd-nocel/apisix.apache.org_v2.yaml b/config/crd-nocel/apisix.apache.org_v2.yaml index 9e4cc186..bde3da1c 100644 --- a/config/crd-nocel/apisix.apache.org_v2.yaml +++ b/config/crd-nocel/apisix.apache.org_v2.yaml @@ -2273,6 +2273,13 @@ spec: required: - type type: object + caBundle: + description: |- + CaBundle is a PEM-encoded CA certificate (or bundle) used to verify the + control plane's TLS certificate, in place of the system trust store. + Set it when the control plane uses a self-signed or private CA certificate. + It has no effect when tlsVerify is false. + type: string endpoints: description: Endpoints specifies the list of control plane endpoints. diff --git a/config/crd/bases/apisix.apache.org_gatewayproxies.yaml b/config/crd/bases/apisix.apache.org_gatewayproxies.yaml index 23a7ed50..617226d5 100644 --- a/config/crd/bases/apisix.apache.org_gatewayproxies.yaml +++ b/config/crd/bases/apisix.apache.org_gatewayproxies.yaml @@ -120,6 +120,13 @@ spec: - message: adminKey must be specified when type is AdminKey rule: 'self.type == ''AdminKey'' ? has(self.adminKey) : true' + caBundle: + description: |- + CaBundle is a PEM-encoded CA certificate (or bundle) used to verify the + control plane's TLS certificate, in place of the system trust store. + Set it when the control plane uses a self-signed or private CA certificate. + It has no effect when tlsVerify is false. + type: string endpoints: description: Endpoints specifies the list of control plane endpoints. @@ -158,6 +165,9 @@ spec: - message: mode is immutable rule: oldSelf == null || (!has(self.mode) && !has(oldSelf.mode)) || self.mode == oldSelf.mode + - message: caBundle must be a PEM-encoded certificate + rule: '!has(self.caBundle) || self.caBundle.contains(''-----BEGIN + CERTIFICATE-----'')' type: description: Type specifies the type of provider. Can only be `ControlPlane`. diff --git a/docs/en/latest/reference/api-reference.md b/docs/en/latest/reference/api-reference.md index 25b729c2..742ee2e6 100644 --- a/docs/en/latest/reference/api-reference.md +++ b/docs/en/latest/reference/api-reference.md @@ -313,6 +313,7 @@ ControlPlaneProvider defines configuration for control plane provider. | `endpoints` _string array_ | Endpoints specifies the list of control plane endpoints. | | `service` _[ProviderService](#providerservice)_ | | | `tlsVerify` _boolean_ | TlsVerify specifies whether to verify the TLS certificate of the control plane. | +| `caBundle` _string_ | CaBundle is a PEM-encoded CA certificate (or bundle) used to verify the control plane's TLS certificate, in place of the system trust store. Set it when the control plane uses a self-signed or private CA certificate. It has no effect when tlsVerify is false. | | `auth` _[ControlPlaneAuth](#controlplaneauth)_ | Auth specifies the authentication configuration. | diff --git a/internal/adc/client/executor.go b/internal/adc/client/executor.go index 7223b59b..d9d1d494 100644 --- a/internal/adc/client/executor.go +++ b/internal/adc/client/executor.go @@ -79,7 +79,11 @@ type ADCServerOpts struct { LabelSelector map[string]string `json:"labelSelector,omitempty"` IncludeResourceType []string `json:"includeResourceType,omitempty"` TlsSkipVerify *bool `json:"tlsSkipVerify,omitempty"` - CacheKey string `json:"cacheKey"` + // CaCert is the PEM-encoded CA certificate (or bundle) the ADC server verifies + // the control plane against. Older ADC servers ignore it, and omitempty keeps + // requests without a CA bundle byte for byte what they were. + CaCert string `json:"caCert,omitempty"` + CacheKey string `json:"cacheKey"` } // MarshalLog implements logr.Marshaler so logging the request body redacts the @@ -93,6 +97,7 @@ func (r ADCServerRequest) MarshalLog() any { "labelSelector": r.Task.Opts.LabelSelector, "includeResourceType": r.Task.Opts.IncludeResourceType, "tlsSkipVerify": r.Task.Opts.TlsSkipVerify, + "hasCaCert": r.Task.Opts.CaCert != "", "cacheKey": r.Task.Opts.CacheKey, "config": r.Task.Config.MarshalLog(), } @@ -351,6 +356,7 @@ func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr strin LabelSelector: labels, IncludeResourceType: types, TlsSkipVerify: ptr.To(!tlsVerify), + CaCert: config.CaBundle, CacheKey: config.Name, }, Config: *resources, @@ -372,6 +378,7 @@ func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr strin "labelSelector", labels, "includeResourceType", types, "tlsSkipVerify", !tlsVerify, + "hasCaCert", config.CaBundle != "", ) // Create HTTP request diff --git a/internal/adc/client/executor_test.go b/internal/adc/client/executor_test.go new file mode 100644 index 00000000..a4eca32b --- /dev/null +++ b/internal/adc/client/executor_test.go @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package client + +import ( + "context" + "encoding/json" + "io" + "net/http" + "testing" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + adctypes "github.com/apache/apisix-ingress-controller/api/adc" +) + +func TestHTTPADCExecutorBuildHTTPRequestCaCert(t *testing.T) { + e := &HTTPADCExecutor{ + serverURL: "http://127.0.0.1:3000", + log: logr.Discard(), + } + + build := func(config adctypes.Config) (ADCServerOpts, string) { + req, err := e.buildHTTPRequest(context.Background(), "https://apisix:9180", config, nil, nil, + &adctypes.Resources{}, http.MethodPut, "/sync") + require.NoError(t, err) + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + var parsed ADCServerRequest + require.NoError(t, json.Unmarshal(body, &parsed)) + return parsed.Task.Opts, string(body) + } + + // Without a CA bundle the request stays what an ADC server that predates caCert + // already accepts. + opts, raw := build(adctypes.Config{Name: "GatewayProxy/ns/name", TlsVerify: true}) + assert.Empty(t, opts.CaCert) + assert.NotContains(t, raw, "caCert") + + const caCert = "-----BEGIN CERTIFICATE-----\nMIIB\n-----END CERTIFICATE-----" + opts, raw = build(adctypes.Config{Name: "GatewayProxy/ns/name", TlsVerify: true, CaBundle: caCert}) + assert.Equal(t, caCert, opts.CaCert) + assert.Contains(t, raw, "caCert") + // verification stays on, otherwise the bundle would be pointless + assert.Equal(t, false, *opts.TlsSkipVerify) +} diff --git a/internal/adc/translator/gatewayproxy.go b/internal/adc/translator/gatewayproxy.go index 13ace18d..ad2999d6 100644 --- a/internal/adc/translator/gatewayproxy.go +++ b/internal/adc/translator/gatewayproxy.go @@ -18,6 +18,7 @@ package translator import ( + "crypto/x509" "fmt" "net" "strconv" @@ -56,6 +57,17 @@ func (t *Translator) TranslateGatewayProxyToConfig(tctx *provider.TranslateConte cfg.TlsVerify = *cp.TlsVerify } + if cp.CaBundle != "" { + // reject unusable CA material here rather than at connect time + if !x509.NewCertPool().AppendCertsFromPEM([]byte(cp.CaBundle)) { + return nil, errors.New("invalid caBundle: no PEM-encoded certificate found") + } + if !cfg.TlsVerify { + t.Log.Info("caBundle is ignored because tlsVerify is disabled", "gatewayproxy", utils.NamespacedNameKind(gatewayProxy)) + } + cfg.CaBundle = cp.CaBundle + } + if cp.Auth.Type == v1alpha1.AuthTypeAdminKey && cp.Auth.AdminKey != nil { if cp.Auth.AdminKey.ValueFrom != nil && cp.Auth.AdminKey.ValueFrom.SecretKeyRef != nil { secretRef := cp.Auth.AdminKey.ValueFrom.SecretKeyRef diff --git a/internal/adc/translator/gatewayproxy_test.go b/internal/adc/translator/gatewayproxy_test.go new file mode 100644 index 00000000..205d621b --- /dev/null +++ b/internal/adc/translator/gatewayproxy_test.go @@ -0,0 +1,101 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package translator + +import ( + "context" + "testing" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + "github.com/apache/apisix-ingress-controller/api/v1alpha1" + "github.com/apache/apisix-ingress-controller/internal/provider" +) + +func newGatewayProxy(tlsVerify *bool, caBundle string) *v1alpha1.GatewayProxy { + return &v1alpha1.GatewayProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "gp", + }, + Spec: v1alpha1.GatewayProxySpec{ + Provider: &v1alpha1.GatewayProxyProvider{ + Type: v1alpha1.ProviderTypeControlPlane, + ControlPlane: &v1alpha1.ControlPlaneProvider{ + Endpoints: []string{"https://cp.example.com:9180"}, + TlsVerify: tlsVerify, + CaBundle: caBundle, + Auth: v1alpha1.ControlPlaneAuth{ + Type: v1alpha1.AuthTypeAdminKey, + AdminKey: &v1alpha1.AdminKeyAuth{ + Value: "admin-key", + }, + }, + }, + }, + }, + } +} + +func TestTranslateGatewayProxyToConfigCaBundle(t *testing.T) { + t.Run("carries the CA bundle into the config", func(t *testing.T) { + tr := &Translator{Log: logr.Discard()} + tctx := provider.NewDefaultTranslateContext(context.Background()) + + cfg, err := tr.TranslateGatewayProxyToConfig(tctx, newGatewayProxy(ptr.To(true), testCACert), false) + require.NoError(t, err) + require.NotNil(t, cfg) + assert.True(t, cfg.TlsVerify) + assert.Equal(t, testCACert, cfg.CaBundle) + }) + + t.Run("leaves the CA bundle empty when unset", func(t *testing.T) { + tr := &Translator{Log: logr.Discard()} + tctx := provider.NewDefaultTranslateContext(context.Background()) + + cfg, err := tr.TranslateGatewayProxyToConfig(tctx, newGatewayProxy(ptr.To(true), ""), false) + require.NoError(t, err) + require.NotNil(t, cfg) + assert.Empty(t, cfg.CaBundle) + }) + + t.Run("rejects a CA bundle that is not PEM encoded", func(t *testing.T) { + tr := &Translator{Log: logr.Discard()} + tctx := provider.NewDefaultTranslateContext(context.Background()) + + cfg, err := tr.TranslateGatewayProxyToConfig(tctx, newGatewayProxy(ptr.To(true), "not-a-certificate"), false) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid caBundle") + assert.Nil(t, cfg) + }) + + t.Run("still carries the CA bundle when verification is off", func(t *testing.T) { + tr := &Translator{Log: logr.Discard()} + tctx := provider.NewDefaultTranslateContext(context.Background()) + + cfg, err := tr.TranslateGatewayProxyToConfig(tctx, newGatewayProxy(ptr.To(false), testCACert), false) + require.NoError(t, err) + require.NotNil(t, cfg) + assert.False(t, cfg.TlsVerify) + assert.Equal(t, testCACert, cfg.CaBundle) + }) +} From 8090806ad4ab7c3b7b3e385695bbc5e6bed7aca8 Mon Sep 17 00:00:00 2001 From: Abhishek Choudhary Date: Tue, 28 Jul 2026 18:00:38 +0545 Subject: [PATCH 2/3] fix: parse every certificate in the control plane CA bundle x509.CertPool skips PEM blocks it cannot decode, so a bundle whose second certificate is broken passed validation here and failed later at the ADC server, which parses the whole bundle. Reject it up front instead. --- internal/adc/translator/gatewayproxy.go | 30 ++++++++++++++++-- internal/adc/translator/gatewayproxy_test.go | 32 +++++++++++++++++--- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/internal/adc/translator/gatewayproxy.go b/internal/adc/translator/gatewayproxy.go index ad2999d6..aca89772 100644 --- a/internal/adc/translator/gatewayproxy.go +++ b/internal/adc/translator/gatewayproxy.go @@ -19,6 +19,7 @@ package translator import ( "crypto/x509" + "encoding/pem" "fmt" "net" "strconv" @@ -59,8 +60,8 @@ func (t *Translator) TranslateGatewayProxyToConfig(tctx *provider.TranslateConte if cp.CaBundle != "" { // reject unusable CA material here rather than at connect time - if !x509.NewCertPool().AppendCertsFromPEM([]byte(cp.CaBundle)) { - return nil, errors.New("invalid caBundle: no PEM-encoded certificate found") + if err := validateCaBundle(cp.CaBundle); err != nil { + return nil, err } if !cfg.TlsVerify { t.Log.Info("caBundle is ignored because tlsVerify is disabled", "gatewayproxy", utils.NamespacedNameKind(gatewayProxy)) @@ -154,3 +155,28 @@ func (t *Translator) TranslateGatewayProxyToConfig(tctx *provider.TranslateConte return &cfg, nil } + +// validateCaBundle parses every certificate in the bundle. x509.CertPool skips +// blocks it cannot decode, so a bundle whose second certificate is broken would +// otherwise reach the ADC server and fail there instead. +func validateCaBundle(caBundle string) error { + var count int + for rest := []byte(caBundle); len(rest) > 0; { + var block *pem.Block + block, rest = pem.Decode(rest) + if block == nil { + break + } + if block.Type != "CERTIFICATE" { + return fmt.Errorf("invalid caBundle: expected a CERTIFICATE block, got %s", block.Type) + } + if _, err := x509.ParseCertificate(block.Bytes); err != nil { + return fmt.Errorf("invalid caBundle: %w", err) + } + count++ + } + if count == 0 { + return errors.New("invalid caBundle: no PEM-encoded certificate found") + } + return nil +} diff --git a/internal/adc/translator/gatewayproxy_test.go b/internal/adc/translator/gatewayproxy_test.go index 205d621b..3429b711 100644 --- a/internal/adc/translator/gatewayproxy_test.go +++ b/internal/adc/translator/gatewayproxy_test.go @@ -78,14 +78,36 @@ func TestTranslateGatewayProxyToConfigCaBundle(t *testing.T) { assert.Empty(t, cfg.CaBundle) }) - t.Run("rejects a CA bundle that is not PEM encoded", func(t *testing.T) { + // every certificate is parsed: x509.CertPool silently skips the blocks it + // cannot decode, which would let a broken one through to the ADC server. + for name, caBundle := range map[string]string{ + "not PEM at all": "not-a-certificate", + "a header with no certificate": "-----BEGIN CERTIFICATE-----", + "an unparseable body": "-----BEGIN CERTIFICATE-----\nAAAA\n-----END CERTIFICATE-----", + "a key rather than a certificate": "-----BEGIN RSA PRIVATE KEY-----\nAAAA\n-----END RSA PRIVATE KEY-----", + "one good and one broken certificate": testCACert + + "\n-----BEGIN CERTIFICATE-----\nAAAA\n-----END CERTIFICATE-----", + } { + t.Run("rejects a CA bundle that is "+name, func(t *testing.T) { + tr := &Translator{Log: logr.Discard()} + tctx := provider.NewDefaultTranslateContext(context.Background()) + + cfg, err := tr.TranslateGatewayProxyToConfig(tctx, newGatewayProxy(ptr.To(true), caBundle), false) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid caBundle") + assert.Nil(t, cfg) + }) + } + + t.Run("accepts a bundle of several certificates", func(t *testing.T) { tr := &Translator{Log: logr.Discard()} tctx := provider.NewDefaultTranslateContext(context.Background()) - cfg, err := tr.TranslateGatewayProxyToConfig(tctx, newGatewayProxy(ptr.To(true), "not-a-certificate"), false) - require.Error(t, err) - assert.Contains(t, err.Error(), "invalid caBundle") - assert.Nil(t, cfg) + bundle := testCACert + "\n" + testCACert + cfg, err := tr.TranslateGatewayProxyToConfig(tctx, newGatewayProxy(ptr.To(true), bundle), false) + require.NoError(t, err) + require.NotNil(t, cfg) + assert.Equal(t, bundle, cfg.CaBundle) }) t.Run("still carries the CA bundle when verification is off", func(t *testing.T) { From 256b91c60c613512b75f9292a319a0b222bbe310 Mon Sep 17 00:00:00 2001 From: Abhishek Choudhary Date: Wed, 29 Jul 2026 09:57:11 +0545 Subject: [PATCH 3/3] fix: drop the always-PUT method parameter of buildHTTPRequest Both call sites pass http.MethodPut, and the new test made unparam report it. Set the method inside instead of threading it through. --- internal/adc/client/executor.go | 8 ++++---- internal/adc/client/executor_test.go | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/adc/client/executor.go b/internal/adc/client/executor.go index d9d1d494..dcb232ee 100644 --- a/internal/adc/client/executor.go +++ b/internal/adc/client/executor.go @@ -239,7 +239,7 @@ func (e *HTTPADCExecutor) runHTTPSyncForSingleServer(ctx context.Context, server } // Build HTTP request - req, err := e.buildHTTPRequest(ctx, serverAddr, config, labels, types, resources, http.MethodPut, "/sync") + req, err := e.buildHTTPRequest(ctx, serverAddr, config, labels, types, resources, "/sync") if err != nil { return fmt.Errorf("failed to build HTTP request: %w", err) } @@ -273,7 +273,7 @@ func (e *HTTPADCExecutor) runHTTPValidateForSingleServer(ctx context.Context, se return fmt.Errorf("failed to load resources from file %s: %w", filePath, err) } - req, err := e.buildHTTPRequest(ctx, serverAddr, config, labels, types, resources, http.MethodPut, "/validate") + req, err := e.buildHTTPRequest(ctx, serverAddr, config, labels, types, resources, "/validate") if err != nil { return fmt.Errorf("failed to build validate request: %w", err) } @@ -344,7 +344,7 @@ func (e *HTTPADCExecutor) loadResourcesFromFile(filePath string) (*adctypes.Reso } // buildHTTPRequest builds the HTTP request for ADC Server -func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr string, config adctypes.Config, labels map[string]string, types []string, resources *adctypes.Resources, method string, path string) (*http.Request, error) { +func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr string, config adctypes.Config, labels map[string]string, types []string, resources *adctypes.Resources, path string) (*http.Request, error) { // Prepare request body tlsVerify := config.TlsVerify reqBody := ADCServerRequest{ @@ -382,7 +382,7 @@ func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr strin ) // Create HTTP request - req, err := http.NewRequestWithContext(ctx, method, e.serverURL+path, bytes.NewBuffer(jsonData)) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, e.serverURL+path, bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create HTTP request: %w", err) } diff --git a/internal/adc/client/executor_test.go b/internal/adc/client/executor_test.go index a4eca32b..c52bba10 100644 --- a/internal/adc/client/executor_test.go +++ b/internal/adc/client/executor_test.go @@ -21,7 +21,6 @@ import ( "context" "encoding/json" "io" - "net/http" "testing" "github.com/go-logr/logr" @@ -39,7 +38,7 @@ func TestHTTPADCExecutorBuildHTTPRequestCaCert(t *testing.T) { build := func(config adctypes.Config) (ADCServerOpts, string) { req, err := e.buildHTTPRequest(context.Background(), "https://apisix:9180", config, nil, nil, - &adctypes.Resources{}, http.MethodPut, "/sync") + &adctypes.Resources{}, "/sync") require.NoError(t, err) body, err := io.ReadAll(req.Body) require.NoError(t, err)