Repository: external-secrets/external-secrets (go)

Issue:
Support for CAProvider field in gitlab provider
**Is your feature request related to a problem? Please describe.**
I have a self-hosted internal gitlab instance, the certificate is self-signed. I am not able to use the gitlab provider.

**Describe the solution you'd like**
Hashicorp vault and few others have a field called "CAProvider" to specify a custom CA certificate. It would be great if we have a similar feature for the gitlab provider

**Describe alternatives you've considered**
Using any other provider can save my day. I am currently using gitlab.com as an alternative. But thats not ideal for an on-prem environment.

**Additional context**

secret store
```yaml
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: gitlab-secret-store
spec:
  provider:
    # provider type: gitlab
    gitlab:
      url: https://gitlab.xxxx.com/
      auth:
        SecretRef:
          accessToken:
            name: gitlab-secret
            key: token
      projectID: "xxxx"
```

TLS verification is failing
```bash
Warning  ValidationFailed  16s (x5 over 29s)  secret-store  could not verify whether the gitlabClient is valid: Get "https://gitlab.xxxx.com/api/v4/projects/xxxx/variables": tls: failed to verify certificate: x509: certificate signed by unknown authority
```

Pull request description:
feat(provider/gitlab): Add support for custom CAs
## Problem Statement

The GitLab provider cannot connect to self-hosted instances that use custom TLS certificates, causing certificate validation errors.

## Related Issue

Fixes #4938

## Proposed Changes

This PR adds support for custom Certificate Authorities (CAs) to the GitLab provider by introducing caBundle and caProvider fields. This allows users to provide a trusted CA certificate, enabling secure connections to their self-hosted GitLab. The implementation aligns with existing patterns in other providers.

## Checklist

- [x] I have read the [contribution guidelines](https://external-secrets.io/latest/contributing/process/#submitting-a-pull-request)
- [x] All commits are signed with `git commit --signoff`
- [x] My changes have reasonable test coverage
- [x] All tests pass with `make test`
- [x] I ensured my PR is ready for review with `make reviewable`


Patch:
diff --git a/apis/externalsecrets/v1/secretstore_gitlab_types.go b/apis/externalsecrets/v1/secretstore_gitlab_types.go
index 6cdac1721..8e859919c 100644
--- a/apis/externalsecrets/v1/secretstore_gitlab_types.go
+++ b/apis/externalsecrets/v1/secretstore_gitlab_types.go
@@ -37,6 +37,14 @@ type GitlabProvider struct {
 
 	// Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments)
 	Environment string `json:"environment,omitempty"`
+
+	// Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+	// can be performed.
+	// +optional
+	CABundle []byte `json:"caBundle,omitempty"`
+	// see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider
+	// +optional
+	CAProvider *CAProvider `json:"caProvider,omitempty"`
 }
 
 type GitlabAuth struct {
diff --git a/apis/externalsecrets/v1/zz_generated.deepcopy.go b/apis/externalsecrets/v1/zz_generated.deepcopy.go
index 45cd4259f..ae47f4325 100644
--- a/apis/externalsecrets/v1/zz_generated.deepcopy.go
+++ b/apis/externalsecrets/v1/zz_generated.deepcopy.go
@@ -1885,6 +1885,16 @@ func (in *GitlabProvider) DeepCopyInto(out *GitlabProvider) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.CABundle != nil {
+		in, out := &in.CABundle, &out.CABundle
+		*out = make([]byte, len(*in))
+		copy(*out, *in)
+	}
+	if in.CAProvider != nil {
+		in, out := &in.CAProvider, &out.CAProvider
+		*out = new(CAProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabProvider.
diff --git a/apis/externalsecrets/v1beta1/secretstore_gitlab_types.go b/apis/externalsecrets/v1beta1/secretstore_gitlab_types.go
index 5e9bf64e8..a7ec651c0 100644
--- a/apis/externalsecrets/v1beta1/secretstore_gitlab_types.go
+++ b/apis/externalsecrets/v1beta1/secretstore_gitlab_types.go
@@ -37,6 +37,15 @@ type GitlabProvider struct {
 
 	// Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments)
 	Environment string `json:"environment,omitempty"`
+
+	// Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+	// can be performed.
+	// +optional
+	CABundle []byte `json:"caBundle,omitempty"`
+
+	// see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider
+	// +optional
+	CAProvider *CAProvider `json:"caProvider,omitempty"`
 }
 
 type GitlabAuth struct {
diff --git a/apis/externalsecrets/v1beta1/zz_generated.deepcopy.go b/apis/externalsecrets/v1beta1/zz_generated.deepcopy.go
index 0d826739e..49b97b46d 100644
--- a/apis/externalsecrets/v1beta1/zz_generated.deepcopy.go
+++ b/apis/externalsecrets/v1beta1/zz_generated.deepcopy.go
@@ -1866,6 +1866,16 @@ func (in *GitlabProvider) DeepCopyInto(out *GitlabProvider) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.CABundle != nil {
+		in, out := &in.CABundle, &out.CABundle
+		*out = make([]byte, len(*in))
+		copy(*out, *in)
+	}
+	if in.CAProvider != nil {
+		in, out := &in.CAProvider, &out.CAProvider
+		*out = new(CAProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitlabProvider.
diff --git a/config/crds/bases/external-secrets.io_clustersecretstores.yaml b/config/crds/bases/external-secrets.io_clustersecretstores.yaml
index 19f0e8109..2d96cda84 100644
--- a/config/crds/bases/external-secrets.io_clustersecretstores.yaml
+++ b/config/crds/bases/external-secrets.io_clustersecretstores.yaml
@@ -2033,6 +2033,48 @@ spec:
                         required:
                         - SecretRef
                         type: object
+                      caBundle:
+                        description: |-
+                          Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+                          can be performed.
+                        format: byte
+                        type: string
+                      caProvider:
+                        description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
+                        properties:
+                          key:
+                            description: The key where the CA certificate can be found
+                              in the Secret or ConfigMap.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace the Provider type is in.
+                              Can only be defined when used in a ClusterSecretStore.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - type
+                        type: object
                       environment:
                         description: Environment environment_scope of gitlab CI/CD
                           variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment
@@ -6531,6 +6573,48 @@ spec:
                         required:
                         - SecretRef
                         type: object
+                      caBundle:
+                        description: |-
+                          Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+                          can be performed.
+                        format: byte
+                        type: string
+                      caProvider:
+                        description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
+                        properties:
+                          key:
+                            description: The key where the CA certificate can be found
+                              in the Secret or ConfigMap.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace the Provider type is in.
+                              Can only be defined when used in a ClusterSecretStore.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - type
+                        type: object
                       environment:
                         description: Environment environment_scope of gitlab CI/CD
                           variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment
diff --git a/config/crds/bases/external-secrets.io_secretstores.yaml b/config/crds/bases/external-secrets.io_secretstores.yaml
index 70d53c3c2..b833fdf23 100644
--- a/config/crds/bases/external-secrets.io_secretstores.yaml
+++ b/config/crds/bases/external-secrets.io_secretstores.yaml
@@ -2033,6 +2033,48 @@ spec:
                         required:
                         - SecretRef
                         type: object
+                      caBundle:
+                        description: |-
+                          Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+                          can be performed.
+                        format: byte
+                        type: string
+                      caProvider:
+                        description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
+                        properties:
+                          key:
+                            description: The key where the CA certificate can be found
+                              in the Secret or ConfigMap.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace the Provider type is in.
+                              Can only be defined when used in a ClusterSecretStore.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - type
+                        type: object
                       environment:
                         description: Environment environment_scope of gitlab CI/CD
                           variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment
@@ -6531,6 +6573,48 @@ spec:
                         required:
                         - SecretRef
                         type: object
+                      caBundle:
+                        description: |-
+                          Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+                          can be performed.
+                        format: byte
+                        type: string
+                      caProvider:
+                        description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
+                        properties:
+                          key:
+                            description: The key where the CA certificate can be found
+                              in the Secret or ConfigMap.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace the Provider type is in.
+                              Can only be defined when used in a ClusterSecretStore.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - type
+                        type: object
                       environment:
                         description: Environment environment_scope of gitlab CI/CD
                           variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment
diff --git a/deploy/crds/bundle.yaml b/deploy/crds/bundle.yaml
index 80f099ca7..46db96a51 100644
--- a/deploy/crds/bundle.yaml
+++ b/deploy/crds/bundle.yaml
@@ -3888,6 +3888,45 @@ spec:
                           required:
                             - SecretRef
                           type: object
+                        caBundle:
+                          description: |-
+                            Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+                            can be performed.
+                          format: byte
+                          type: string
+                        caProvider:
+                          description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
+                          properties:
+                            key:
+                              description: The key where the CA certificate can be found in the Secret or ConfigMap.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[-._a-zA-Z0-9]+$
+                              type: string
+                            name:
+                              description: The name of the object located at the provider type.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                              type: string
+                            namespace:
+                              description: |-
+                                The namespace the Provider type is in.
+                                Can only be defined when used in a ClusterSecretStore.
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                              type: string
+                            type:
+                              description: The type of provider to use such as "Secret", or "ConfigMap".
+                              enum:
+                                - Secret
+                                - ConfigMap
+                              type: string
+                          required:
+                            - name
+                            - type
+                          type: object
                         environment:
                           description: Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments)
                           type: string
@@ -8086,6 +8125,45 @@ spec:
                           required:
                             - SecretRef
                           type: object
+                        caBundle:
+                          description: |-
+                            Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+                            can be performed.
+                          format: byte
+                          type: string
+                        caProvider:
+                          description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
+                          properties:
+                            key:
+                              description: The key where the CA certificate can be found in the Secret or ConfigMap.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[-._a-zA-Z0-9]+$
+                              type: string
+                            name:
+                              description: The name of the object located at the provider type.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                              type: string
+                            namespace:
+                              description: |-
+                                The namespace the Provider type is in.
+                                Can only be defined when used in a ClusterSecretStore.
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                              type: string
+                            type:
+                              description: The type of provider to use such as "Secret", or "ConfigMap".
+                              enum:
+                                - Secret
+                                - ConfigMap
+                              type: string
+                          required:
+                            - name
+                            - type
+                          type: object
                         environment:
                           description: Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments)
                           type: string
@@ -13960,6 +14038,45 @@ spec:
                           required:
                             - SecretRef
                           type: object
+                        caBundle:
+                          description: |-
+                            Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+                            can be performed.
+                          format: byte
+                          type: string
+                        caProvider:
+                          description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
+                          properties:
+                            key:
+                              description: The key where the CA certificate can be found in the Secret or ConfigMap.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[-._a-zA-Z0-9]+$
+                              type: string
+                            name:
+                              description: The name of the object located at the provider type.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                              type: string
+                            namespace:
+                              description: |-
+                                The namespace the Provider type is in.
+                                Can only be defined when used in a ClusterSecretStore.
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                              type: string
+                            type:
+                              description: The type of provider to use such as "Secret", or "ConfigMap".
+                              enum:
+                                - Secret
+                                - ConfigMap
+                              type: string
+                          required:
+                            - name
+                            - type
+                          type: object
                         environment:
                           description: Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments)
                           type: string
@@ -18158,6 +18275,45 @@ spec:
                           required:
                             - SecretRef
                           type: object
+                        caBundle:
+                          description: |-
+                            Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+                            can be performed.
+                          format: byte
+                          type: string
+                        caProvider:
+                          description: 'see: https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider'
+                          properties:
+                            key:
+                              description: The key where the CA certificate can be found in the Secret or ConfigMap.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[-._a-zA-Z0-9]+$
+                              type: string
+                            name:
+                              description: The name of the object located at the provider type.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                              type: string
+                            namespace:
+                              description: |-
+                                The namespace the Provider type is in.
+                                Can only be defined when used in a ClusterSecretStore.
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                              type: string
+                            type:
+                              description: The type of provider to use such as "Secret", or "ConfigMap".
+                              enum:
+                                - Secret
+                                - ConfigMap
+                              type: string
+                          required:
+                            - name
+                            - type
+                          type: object
                         environment:
                           description: Environment environment_scope of gitlab CI/CD variables (Please see https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment on how to create environments)
                           type: string
diff --git a/docs/api/spec.md b/docs/api/spec.md
index bc25b998c..561937f25 100644
--- a/docs/api/spec.md
+++ b/docs/api/spec.md
@@ -1482,6 +1482,7 @@ External Secrets meta/v1.SecretKeySelector
 <a href="#external-secrets.io/v1.AkeylessProvider">AkeylessProvider</a>, 
 <a href="#external-secrets.io/v1.BitwardenSecretsManagerProvider">BitwardenSecretsManagerProvider</a>, 
 <a href="#external-secrets.io/v1.ConjurProvider">ConjurProvider</a>, 
+<a href="#external-secrets.io/v1.GitlabProvider">GitlabProvider</a>, 
 <a href="#external-secrets.io/v1.KubernetesServer">KubernetesServer</a>, 
 <a href="#external-secrets.io/v1.VaultProvider">VaultProvider</a>)
 </p>
@@ -5230,6 +5231,33 @@ string
 <p>Environment environment_scope of gitlab CI/CD variables (Please see <a href="https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment">https://docs.gitlab.com/ee/ci/environments/#create-a-static-environment</a> on how to create environments)</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>caBundle</code></br>
+<em>
+[]byte
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>Base64 encoded certificate for the GitLab server sdk. The sdk MUST run with HTTPS to make sure no MITM attack
+can be performed.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>caProvider</code></br>
+<em>
+<a href="#external-secrets.io/v1.CAProvider">
+CAProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>see: <a href="https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider">https://external-secrets.io/latest/spec/#external-secrets.io/v1alpha1.CAProvider</a></p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1.GitlabSecretRef">GitlabSecretRef
diff --git a/pkg/provider/gitlab/provider.go b/pkg/provider/gitlab/provider.go
index 0f2e4ee29..410a2e407 100644
--- a/pkg/provider/gitlab/provider.go
+++ b/pkg/provider/gitlab/provider.go
@@ -16,6 +16,8 @@ package gitlab
 
 import (
 	"context"
+	"crypto/tls"
+	"crypto/x509"
 	"errors"
 	"fmt"
 	"net/http"
@@ -88,6 +90,33 @@ func (g *gitlabBase) getClient(ctx context.Context, provider *esv1.GitlabProvide
 		opts = append(opts, gitlab.WithBaseURL(provider.URL))
 	}
 
+	if len(provider.CABundle) > 0 || provider.CAProvider != nil {
+		caCertPool := x509.NewCertPool()
+		ca, err := utils.FetchCACertFromSource(ctx, utils.CreateCertOpts{
+			CABundle:   provider.CABundle,
+			CAProvider: provider.CAProvider,
+			StoreKind:  g.storeKind,
+			Namespace:  g.namespace,
+			Client:     g.kube,
+		})
+		if err != nil {
+			return nil, fmt.Errorf("failed to read ca bundle: %w", err)
+		}
+		if ok := caCertPool.AppendCertsFromPEM(ca); !ok {
+			return nil, errors.New("failed to append ca bundle")
+		}
+
+		transport := &http.Transport{
+			TLSClientConfig: &tls.Config{
+				RootCAs:    caCertPool,
+				MinVersion: tls.VersionTLS12,
+			},
+		}
+
+		httpClient := &http.Client{Transport: transport}
+		opts = append(opts, gitlab.WithHTTPClient(httpClient))
+	}
+
 	// ClientOptionFunc from the gitlab package can be mapped with the CRD
 	// in a similar way to extend functionality of the provider
 


Test patch:
diff --git a/pkg/provider/gitlab/provider_test.go b/pkg/provider/gitlab/provider_test.go
new file mode 100644
index 000000000..cb6fe174e
--- /dev/null
+++ b/pkg/provider/gitlab/provider_test.go
@@ -0,0 +1,275 @@
+// /*
+// Licensed 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 gitlab
+
+import (
+	"context"
+	"encoding/pem"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
+	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
+	esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
+)
+
+func TestGetClientWithCABundle(t *testing.T) {
+	// Create a mock TLS server that asserts a client certificate is present
+	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// We expect a GET request to the variables API
+		assert.Equal(t, "/api/v4/projects/1234/variables/test-secret", r.URL.Path)
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		w.Write([]byte(`{}`))
+	}))
+	defer server.Close()
+
+	// Define the GitLab provider with the CABundle
+	provider := &esv1.GitlabProvider{
+		URL:       server.URL,
+		ProjectID: "1234",
+		CABundle:  pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw}),
+		Auth: esv1.GitlabAuth{
+			SecretRef: esv1.GitlabSecretRef{
+				AccessToken: esmeta.SecretKeySelector{
+					Name: "gitlab-secret",
+					Key:  "token",
+				},
+			},
+		},
+	}
+
+	// Create a fake Kubernetes client with the required secret
+	secret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "gitlab-secret",
+			Namespace: "default",
+		},
+		Data: map[string][]byte{
+			"token": []byte("test-token"),
+		},
+	}
+	fakeClient := clientfake.NewClientBuilder().WithObjects(secret).Build()
+
+	// Create the gitlabBase struct
+	gl := &gitlabBase{
+		kube:      fakeClient,
+		store:     provider,
+		namespace: "default",
+	}
+
+	// We need to initialize the gitlab clients inside our gitlabBase struct
+	client, err := gl.getClient(context.Background(), provider)
+	assert.NoError(t, err)
+	gl.projectsClient = client.Projects
+	gl.projectVariablesClient = client.ProjectVariables
+	gl.groupVariablesClient = client.GroupVariables
+
+	// Call getVariables to trigger a network request to the mock server.
+	// The request will only succeed if the custom CA is correctly configured.
+	_, _, err = gl.getVariables(esv1.ExternalSecretDataRemoteRef{Key: "test-secret"}, nil)
+	assert.NoError(t, err, "getVariables should succeed with the correct CA")
+}
+
+func TestGetClientWithInvalidCABundle(t *testing.T) {
+	provider := &esv1.GitlabProvider{
+		CABundle: []byte("invalid-ca-bundle"),
+		Auth: esv1.GitlabAuth{
+			SecretRef: esv1.GitlabSecretRef{
+				AccessToken: esmeta.SecretKeySelector{
+					Name: "gitlab-secret",
+					Key:  "token",
+				},
+			},
+		},
+	}
+	secret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "gitlab-secret",
+			Namespace: "default",
+		},
+		Data: map[string][]byte{
+			"token": []byte("test-token"),
+		},
+	}
+	fakeClient := clientfake.NewClientBuilder().WithObjects(secret).Build()
+
+	gl := &gitlabBase{
+		kube:      fakeClient,
+		store:     provider,
+		namespace: "default",
+	}
+
+	_, err := gl.getClient(context.Background(), provider)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "failed to read ca bundle")
+}
+
+func TestGetClientWithCAProviderSecret(t *testing.T) {
+	// Create a mock TLS server
+	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		w.Write([]byte(`{}`))
+	}))
+	defer server.Close()
+
+	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw})
+	caSecret := makeFakeCASource(t, "Secret", "ca-secret", "default", certPEM)
+
+	// Define the GitLab provider with the CAProvider
+	provider := &esv1.GitlabProvider{
+		URL:       server.URL,
+		ProjectID: "1234",
+		Auth: esv1.GitlabAuth{
+			SecretRef: esv1.GitlabSecretRef{
+				AccessToken: esmeta.SecretKeySelector{
+					Name: "gitlab-secret",
+					Key:  "token",
+				},
+			},
+		},
+		CAProvider: &esv1.CAProvider{
+			Type: esv1.CAProviderTypeSecret,
+			Name: "ca-secret",
+			Key:  "tls.crt",
+		},
+	}
+
+	// Create a fake Kubernetes client with the required secrets
+	accessTokenSecret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "gitlab-secret",
+			Namespace: "default",
+		},
+		Data: map[string][]byte{
+			"token": []byte("test-token"),
+		},
+	}
+	fakeClient := clientfake.NewClientBuilder().WithObjects(accessTokenSecret, caSecret).Build()
+
+	// Create the gitlabBase struct
+	gl := &gitlabBase{
+		kube:      fakeClient,
+		store:     provider,
+		namespace: "default",
+	}
+
+	// We need to initialize the gitlab clients inside our gitlabBase struct
+	client, err := gl.getClient(context.Background(), provider)
+	assert.NoError(t, err)
+	gl.projectsClient = client.Projects
+	gl.projectVariablesClient = client.ProjectVariables
+	gl.groupVariablesClient = client.GroupVariables
+
+	// Call getVariables to trigger a network request to the mock server.
+	_, _, err = gl.getVariables(esv1.ExternalSecretDataRemoteRef{Key: "test-secret"}, nil)
+	assert.NoError(t, err, "getVariables should succeed with the correct CA from Secret")
+}
+
+func TestGetClientWithCAProviderConfigMap(t *testing.T) {
+	// Create a mock TLS server
+	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "application/json")
+		w.WriteHeader(http.StatusOK)
+		w.Write([]byte(`{}`))
+	}))
+	defer server.Close()
+
+	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: server.Certificate().Raw})
+	caCM := makeFakeCASource(t, "ConfigMap", "ca-cm", "default", certPEM)
+
+	// Define the GitLab provider with the CAProvider
+	provider := &esv1.GitlabProvider{
+		URL:       server.URL,
+		ProjectID: "1234",
+		Auth: esv1.GitlabAuth{
+			SecretRef: esv1.GitlabSecretRef{
+				AccessToken: esmeta.SecretKeySelector{
+					Name: "gitlab-secret",
+					Key:  "token",
+				},
+			},
+		},
+		CAProvider: &esv1.CAProvider{
+			Type: esv1.CAProviderTypeConfigMap,
+			Name: "ca-cm",
+			Key:  "ca.crt",
+		},
+	}
+
+	// Create a fake Kubernetes client with the required secrets
+	accessTokenSecret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "gitlab-secret",
+			Namespace: "default",
+		},
+		Data: map[string][]byte{
+			"token": []byte("test-token"),
+		},
+	}
+	fakeClient := clientfake.NewClientBuilder().WithObjects(accessTokenSecret, caCM).Build()
+
+	// Create the gitlabBase struct
+	gl := &gitlabBase{
+		kube:      fakeClient,
+		store:     provider,
+		namespace: "default",
+	}
+
+	// We need to initialize the gitlab clients inside our gitlabBase struct
+	client, err := gl.getClient(context.Background(), provider)
+	assert.NoError(t, err)
+	gl.projectsClient = client.Projects
+	gl.projectVariablesClient = client.ProjectVariables
+	gl.groupVariablesClient = client.GroupVariables
+
+	// Call getVariables to trigger a network request to the mock server.
+	_, _, err = gl.getVariables(esv1.ExternalSecretDataRemoteRef{Key: "test-secret"}, nil)
+	assert.NoError(t, err, "getVariables should succeed with the correct CA from ConfigMap")
+}
+
+func makeFakeCASource(t *testing.T, kind, name, namespace string, certData []byte) kclient.Object {
+	t.Helper()
+	switch kind {
+	case "Secret":
+		return &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      name,
+				Namespace: namespace,
+			},
+			Data: map[string][]byte{
+				"tls.crt": certData,
+			},
+		}
+	case "ConfigMap":
+		return &corev1.ConfigMap{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      name,
+				Namespace: namespace,
+			},
+			Data: map[string]string{
+				"ca.crt": string(certData),
+			},
+		}
+	}
+	return nil
+}