Skip to content

feat: add mTLS certificate-based Git authentication support for TestWorkflows#6871

Open
dhimanAbhi wants to merge 2 commits intokubeshop:mainfrom
dhimanAbhi:feat/cert_based_git_auth
Open

feat: add mTLS certificate-based Git authentication support for TestWorkflows#6871
dhimanAbhi wants to merge 2 commits intokubeshop:mainfrom
dhimanAbhi:feat/cert_based_git_auth

Conversation

@dhimanAbhi
Copy link
Copy Markdown

@dhimanAbhi dhimanAbhi commented Nov 20, 2025

Pull request description

This PR adds full support for certificate-based Git authentication (mTLS) in Testkube TestWorkflows.
This addresses the missing client-certificate functionality mentioned in #4588 , enabling Testkube to clone Git repositories that require mutual TLS authentication.

New Fields Introduced

  • caCert : CA certificate that Testkube should trust when establishing TLS connections to the Git server.
  • caCertFrom : External CA certificate that Testkube should trust when establishing TLS connections to the Git server.
  • clientCert : Client certificate that the Testkube presents during TLS handshake.
  • clientCertFrom : External Client certificate that the Testkube presents during TLS handshake.
  • clientKey: Private key that corresponds to clientCert
  • clientKeyFrom : External Private key that corresponds to clientCert

These fields map to git options:

-c http.sslCAInfo=<"path">
-c http.sslCert=<"path">
-c http.sslKey=<"path">

Example Testworkflow Usage

apiVersion: testworkflows.testkube.io/v1
kind: TestWorkflow
metadata:
  name: mtls-raw-values
spec:
  content:
    git:
      uri: "https://host.docker.internal:8443/dhimanAbhi/mtls-demo.git"
      username: "dhimanAbhi"
      token: "mytokenorpassword"
      caCert: |
        -----BEGIN CERTIFICATE-----
         . . .
        -----END CERTIFICATE-----

      clientCert: |
        -----BEGIN CERTIFICATE-----
        . . .
        -----END CERTIFICATE-----

      clientKey: |
        -----BEGIN PRIVATE KEY-----
        . . .
        -----END PRIVATE KEY-----
  steps:
    - shell: "tree /data/repo"

Proof Manifests

I tested this feature by setting up a local Gitea Git server and placing an Nginx reverse proxy in front of it to enforce full mTLS. The server certificate and the client certificate used by Testkube were both signed by the same CA. I created a test repository containing a Cypress test suite (copied from test/cypress/cypress-12 in the official Testkube repo), and my TestWorkflow was able to successfully authenticate with mTLS and fetch the repository contents using the new certificate fields.

$ testkube watch twe 691dac15431b6486b3de27c2

Context:  (999.0.0-08c375f90)   Namespace: testkube
---------------------------------------------------
parsing server version 'dev': Invalid Semantic Version
Test Workflow Execution:
Name:                 new-twf
Execution ID:         691dac15431b6486b3de27c2
Execution name:       new-twf-1
Execution namespace:
Execution number:     1
Requested at:         2025-11-19 11:37:57.912 +0000 UTC
Disabled webhooks:    false
Status:               running
Queued at:            2025-11-19 11:37:58 +0000 UTC
Started at:           2025-11-19 11:37:58 +0000 UTC

Getting logs from test workflow job 691dac15431b6486b3de27c2
(SuccessfulCreate) Created pod: 691dac15431b6486b3de27c2-rsh5p
(Scheduled) Successfully assigned testkube/691dac15431b6486b3de27c2-rsh5p to kind-control-plane
(Pulled) Container image "docker.io/testworkflow-toolkit:certAuth" already present on machine
Creating state... done
Initializing state... done
Configuring init process... skipped
Configuring toolkit... done
Configuring shell... skipped

• passed in 2.971s

• (1/2) Clone Git repository
📦 Cloning into '/tmp/clone-1452658466'...
POST git-upload-pack (175 bytes)
POST git-upload-pack (244 bytes)
📥 Moving the contents to /data/repo...
📥 Adjusting access permissions...
🔎 Destination folder contains following files ...
/data/repo
/data/repo/.git
/data/repo/.git/HEAD
/data/repo/.git/branches
/data/repo/.git/config
/data/repo/.git/description
/data/repo/.git/hooks
/data/repo/.git/hooks/applypatch-msg.sample
/data/repo/.git/hooks/commit-msg.sample
/data/repo/.git/hooks/post-update.sample
/data/repo/.git/hooks/pre-applypatch.sample
/data/repo/.git/hooks/pre-commit.sample
/data/repo/.git/hooks/pre-merge-commit.sample
/data/repo/.git/hooks/pre-push.sample
/data/repo/.git/hooks/pre-rebase.sample
/data/repo/.git/hooks/pre-receive.sample
/data/repo/.git/hooks/prepare-commit-msg.sample
/data/repo/.git/hooks/push-to-checkout.sample
/data/repo/.git/hooks/sendemail-validate.sample
/data/repo/.git/hooks/update.sample
/data/repo/.git/index
/data/repo/.git/info
/data/repo/.git/info/exclude
/data/repo/.git/logs
/data/repo/.git/logs/HEAD
/data/repo/.git/logs/refs
/data/repo/.git/logs/refs/heads
/data/repo/.git/logs/refs/heads/master
/data/repo/.git/logs/refs/remotes
/data/repo/.git/logs/refs/remotes/origin
/data/repo/.git/logs/refs/remotes/origin/HEAD
/data/repo/.git/objects
/data/repo/.git/objects/info
/data/repo/.git/objects/pack
/data/repo/.git/objects/pack/pack-20fca878c367d8b1a1992997860ed19ec8aced71.idx
/data/repo/.git/objects/pack/pack-20fca878c367d8b1a1992997860ed19ec8aced71.pack
/data/repo/.git/objects/pack/pack-20fca878c367d8b1a1992997860ed19ec8aced71.rev
/data/repo/.git/packed-refs
/data/repo/.git/refs
/data/repo/.git/refs/heads
/data/repo/.git/refs/heads/master
/data/repo/.git/refs/remotes
/data/repo/.git/refs/remotes/origin
/data/repo/.git/refs/remotes/origin/HEAD
/data/repo/.git/refs/tags
/data/repo/.git/shallow
/data/repo/.gitignore
/data/repo/Dockerfile
/data/repo/README.md
/data/repo/cypress
/data/repo/cypress/e2e
/data/repo/cypress/e2e/smoke.cy.js
/data/repo/cypress/e2e/smoke2.cy.js
/data/repo/cypress/fixtures
/data/repo/cypress/fixtures/.gitkeep
/data/repo/cypress/support
/data/repo/cypress/support/.gitkeep
/data/repo/cypress/support/e2e.js
/data/repo/cypress.config.js
/data/repo/package-lock.json
/data/repo/package.json

• passed in 931ms
(Pulled) Container image "docker.io/testworkflow-toolkit:certAuth" already present on machine

• (2/2) Run shell command

Checklist (choose whats happened)

  • breaking change! (describe)
  • tested locally
  • tested on cluster
  • added new dependencies
  • updated the docs
  • added a test

Breaking changes

Changes

Fixes

Signed-off-by: Abhishek Dhiman <abhi2002dhiman@gmail.com>
@dhimanAbhi dhimanAbhi requested a review from a team as a code owner November 20, 2025 12:28
@dhimanAbhi dhimanAbhi changed the title feat: cert based git auth feat: add mTLS certificate-based Git authentication support for TestWorkflows Nov 20, 2025
Signed-off-by: Abhishek Dhiman <abhi2002dhiman@gmail.com>
@olensmar olensmar added the 👽 external-contribution External contribution label Jan 17, 2026
@vsukhin
Copy link
Copy Markdown
Collaborator

vsukhin commented Feb 20, 2026

@greptile

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 20, 2026

Greptile Summary

Added comprehensive mTLS certificate-based Git authentication support for TestWorkflows, enabling Testkube to clone repositories requiring mutual TLS authentication.

Key Changes:

  • Added six new fields to ContentGit (caCert, caCertFrom, clientCert, clientCertFrom, clientKey, clientKeyFrom) mapped to git config options (http.sslCAInfo, http.sslCert, http.sslKey)
  • Implemented secure temp file handling for certificates with proper cleanup
  • Integrated certificate data into sensitive variable tracking for log redaction
  • Updated CRDs, OpenAPI specs, and all mapper functions consistently across the codebase

Issues Found:

  • Missing validation in setupCertAuth when only one of clientCert/clientKey is provided (will cause git failure)
  • Generic error message in CreateTempFile incorrectly references "SSH key" for all file types

Confidence Score: 4/5

  • Safe to merge with one logical validation fix needed
  • Implementation is thorough and follows existing patterns consistently. The validation issue is important but doesn't affect security - it just means users will get a less helpful git error instead of an early validation error. The feature has been tested locally with a real mTLS setup.
  • Pay attention to cmd/testworkflow-toolkit/commands/clone.go line 255-269 - add validation for paired cert/key fields

Important Files Changed

Filename Overview
cmd/testworkflow-toolkit/commands/clone.go Added mTLS certificate support with temp file creation for CA cert, client cert, and client key; includes cleanup functions
api/testworkflows/v1/content_types.go Added six new fields for mTLS certificate configuration (CaCert, CaCertFrom, ClientCert, ClientCertFrom, ClientKey, ClientKeyFrom)
pkg/testworkflows/testworkflowprocessor/operations.go Added processing logic to pass certificate fields as command-line arguments to the clone toolkit command
pkg/testworkflows/testworkflowresolver/secrets.go Added credential extraction logic for the three new certificate-related fields (CaCertFrom, ClientCertFrom, ClientKeyFrom)

Sequence Diagram

sequenceDiagram
    participant User
    participant API
    participant Processor
    participant Resolver
    participant Toolkit
    participant Git

    User->>API: Submit TestWorkflow with mTLS certs
    API->>Resolver: Extract credentials from spec
    Resolver->>Resolver: Process CaCertFrom, ClientCertFrom, ClientKeyFrom
    Resolver->>API: Return externalized secrets
    API->>Processor: Process TestWorkflow
    Processor->>Processor: Build clone command with cert args
    Processor->>Toolkit: Execute clone with -c, -e, -k flags
    Toolkit->>Toolkit: Create temp files for certs/keys
    Toolkit->>Toolkit: Build git config args (http.sslCAInfo, http.sslCert, http.sslKey)
    Toolkit->>Git: Execute git clone with cert config
    Git->>Git: Perform TLS handshake with certificates
    Git-->>Toolkit: Clone complete
    Toolkit->>Toolkit: Cleanup temp cert files
    Toolkit-->>Processor: Success
    Processor-->>API: Workflow execution complete
Loading

Last reviewed commit: e62cdc2

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

14 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile


if err := os.WriteFile(keyPath, []byte(key), 0400); err != nil {
_ = os.Remove(keyPath)
return func() {}, "", fmt.Errorf("writing SSH key: %w", err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

error message says "writing SSH key" but this function is generic and used for certificates too

Suggested change
return func() {}, "", fmt.Errorf("writing SSH key: %w", err)
return func() {}, "", fmt.Errorf("writing key file: %w", err)

Comment on lines +255 to +269
if opts.ClientCert != "" && opts.ClientKey != "" {
cleanupclientCertFile, clientCertFilePath, err := CreateTempFile(opts.ClientCert, "client-cert-*")
if err != nil {
return nil, cleanupFuncs, fmt.Errorf("creating temp file for Client Certificate: %w", err)
}
certAuthArgs = append(certAuthArgs, "-c", fmt.Sprintf("http.sslCert=%s", clientCertFilePath))
cleanupFuncs = append(cleanupFuncs, cleanupclientCertFile)

cleanupclientKeyFile, clientKeyFilePath, err := CreateTempFile(opts.ClientKey, "client-key-*")
if err != nil {
return nil, cleanupFuncs, fmt.Errorf("creating temp file for Client Key: %w", err)
}
certAuthArgs = append(certAuthArgs, "-c", fmt.Sprintf("http.sslKey=%s", clientKeyFilePath))
cleanupFuncs = append(cleanupFuncs, cleanupclientKeyFile)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if only clientCert is provided without clientKey (or vice versa), git will fail but no validation error occurs

add validation:

} else if opts.ClientCert != "" || opts.ClientKey != "" {
    return nil, cleanupFuncs, fmt.Errorf("both clientCert and clientKey must be provided together")
}

Copy link
Copy Markdown
Collaborator

@vsukhin vsukhin left a comment

Choose a reason for hiding this comment

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

thank you @dhimanAbhi please check my and greptile comments. Also did you test it in k8s cluster? Is size still good enough for client CRDs?

// authorization type for the credentials
AuthType testsv3.GitAuthType `json:"authType,omitempty" expr:"template"`
// plain text CA certificate to verify repository TLS connection
CaCert string `json:"caCert,omitempty"`
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why didn't you add expr metatag there?

// plain text CA certificate to verify repository TLS connection
CaCert string `json:"caCert,omitempty"`
// external CA certificate to verify repository TLS connection
CaCertFrom *corev1.EnvVarSource `json:"caCertFrom,omitempty" expr:"force"`
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why do use force?

SshKey string `json:"sshKey,omitempty"`
SshKeyFrom *EnvVarSource `json:"sshKeyFrom,omitempty"`
AuthType *ContentGitAuthType `json:"authType,omitempty"`
// plain text CA certificate to verify repository TLS connection
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't see corresponding OpenAPI spec changes

@olensmar
Copy link
Copy Markdown
Member

olensmar commented Mar 3, 2026

@dhimanAbhi waiting for your feedback on review questions

@dhimanAbhi
Copy link
Copy Markdown
Author

Hi @olensmar, thanks for the reminder. I got pulled into another task earlier, but I’ll address the requested changes and update this PR by the end of the week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👽 external-contribution External contribution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants