Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions pkg/apiclient/ssl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package apiclient

import (
"crypto/tls"
"net/http"
)

// ApplySSLIgnoreConfiguration configures the HTTP client to ignore SSL errors
// by setting InsecureSkipVerify on the underlying transport. This function
// handles multiple transport types:
// - Direct *http.Transport
// - *SpinnerRoundTripper wrapping *http.Transport
// - Any other transport type (fallback replacement)
func ApplySSLIgnoreConfiguration(httpClient *http.Client) {
if httpClient.Transport == nil {
httpClient.Transport = &http.Transport{}
}

// Handle both direct http.Transport and SpinnerRoundTripper wrapping http.Transport
switch transport := httpClient.Transport.(type) {
case *http.Transport:
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
case *SpinnerRoundTripper:
// If the SpinnerRoundTripper's Next is an http.Transport, configure it
if httpTransport, ok := transport.Next.(*http.Transport); ok {
httpTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
Comment thread
conradj3 marked this conversation as resolved.
Outdated
Copy link

Copilot AI Oct 5, 2025

Choose a reason for hiding this comment

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

Same as above - consider preserving existing TLS configuration when possible. If httpTransport.TLSClientConfig already exists, you should modify only the InsecureSkipVerify field rather than replacing the entire config.

Suggested change
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
case *SpinnerRoundTripper:
// If the SpinnerRoundTripper's Next is an http.Transport, configure it
if httpTransport, ok := transport.Next.(*http.Transport); ok {
httpTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
if transport.TLSClientConfig != nil {
transport.TLSClientConfig.InsecureSkipVerify = true
} else {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
case *SpinnerRoundTripper:
// If the SpinnerRoundTripper's Next is an http.Transport, configure it
if httpTransport, ok := transport.Next.(*http.Transport); ok {
if httpTransport.TLSClientConfig != nil {
httpTransport.TLSClientConfig.InsecureSkipVerify = true
} else {
httpTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Modified and tested

} else {
// If Next is not an http.Transport, replace it with one that has SSL verification disabled
transport.Next = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
default:
// Fallback: replace the transport entirely with one that ignores SSL errors
httpClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
}
7 changes: 1 addition & 6 deletions pkg/cmd/login/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package login

import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -126,11 +125,7 @@ func loginRun(cmd *cobra.Command, f factory.Factory, isPromptEnabled bool, ask q
}

if inputs.ignoreSslErrors {
if httpClient.Transport == nil {
httpClient.Transport = &http.Transport{}
}

httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
apiclient.ApplySSLIgnoreConfiguration(httpClient)
}

if inputs.apiKey != "" {
Expand Down
85 changes: 85 additions & 0 deletions pkg/cmd/login/login_ssl_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package login_test

import (
"net/http"
"testing"

"github.com/OctopusDeploy/cli/pkg/apiclient"
"github.com/stretchr/testify/assert"
)

// TestSSLIgnoreHandling tests that our SSL ignore logic works with both
// direct http.Transport and SpinnerRoundTripper scenarios
func TestSSLIgnoreHandling(t *testing.T) {
tests := []struct {
name string
transport http.RoundTripper
expectPanic bool
}{
{
name: "Direct http.Transport should work",
transport: &http.Transport{},
expectPanic: false,
},
{
name: "SpinnerRoundTripper with http.Transport should work",
transport: &apiclient.SpinnerRoundTripper{Next: &http.Transport{}},
expectPanic: false,
},
{
name: "SpinnerRoundTripper with default transport should work",
transport: apiclient.NewSpinnerRoundTripper(),
expectPanic: false,
},
{
name: "nil transport should work",
transport: nil,
expectPanic: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &http.Client{Transport: tt.transport}

// This simulates the SSL ignore logic from loginRun function
defer func() {
if r := recover(); r != nil {
if !tt.expectPanic {
t.Errorf("Unexpected panic: %v", r)
}
}
}()

// Apply the SSL ignore logic using the shared utility
apiclient.ApplySSLIgnoreConfiguration(client)

// Verify the SSL configuration was applied correctly
verifySSLConfig(t, client)
})
}
}

// verifySSLConfig checks that the SSL configuration was applied correctly
func verifySSLConfig(t *testing.T, httpClient *http.Client) {
assert.NotNil(t, httpClient.Transport, "Transport should not be nil")

switch transport := httpClient.Transport.(type) {
case *http.Transport:
assert.NotNil(t, transport.TLSClientConfig, "TLS config should be set")
assert.True(t, transport.TLSClientConfig.InsecureSkipVerify, "InsecureSkipVerify should be true")

case *apiclient.SpinnerRoundTripper:
assert.NotNil(t, transport.Next, "SpinnerRoundTripper.Next should not be nil")

if httpTransport, ok := transport.Next.(*http.Transport); ok {
assert.NotNil(t, httpTransport.TLSClientConfig, "Underlying TLS config should be set")
assert.True(t, httpTransport.TLSClientConfig.InsecureSkipVerify, "Underlying InsecureSkipVerify should be true")
} else {
t.Errorf("SpinnerRoundTripper.Next should be *http.Transport, got %T", transport.Next)
}

default:
t.Errorf("Unexpected transport type: %T", transport)
}
}