From 92d0ae08edda35f0c83cde8cb241223c31d978a6 Mon Sep 17 00:00:00 2001 From: Bella Khizgiyaev Date: Sun, 8 Feb 2026 19:05:33 +0200 Subject: [PATCH 1/2] Add insecure flag for OIDC flow Signed-off-by: Bella Khizgiyaev --- cmd/caib/auth/config.go | 8 +++-- cmd/caib/auth/oidc.go | 21 ++++++++----- cmd/caib/auth/wrapper.go | 13 +++++--- cmd/caib/catalog/add.go | 4 +-- cmd/caib/catalog/catalog.go | 46 +++++++++++++++++++++++++++ cmd/caib/catalog/get.go | 4 +-- cmd/caib/catalog/list.go | 4 +-- cmd/caib/catalog/publish.go | 4 +-- cmd/caib/catalog/remove.go | 4 +-- cmd/caib/catalog/verify.go | 4 +-- cmd/caib/main.go | 63 +++++++++++++++++++++++++++++-------- 11 files changed, 136 insertions(+), 39 deletions(-) diff --git a/cmd/caib/auth/config.go b/cmd/caib/auth/config.go index 68b6d8f4..375597b7 100644 --- a/cmd/caib/auth/config.go +++ b/cmd/caib/auth/config.go @@ -14,13 +14,17 @@ import ( ) // GetOIDCConfigFromAPI fetches OIDC configuration from the Build API server. -func GetOIDCConfigFromAPI(serverURL string) (*OIDCConfig, error) { +func GetOIDCConfigFromAPI(serverURL string, insecureSkipTLS bool) (*OIDCConfig, error) { pool, err := x509.SystemCertPool() if err != nil { pool = x509.NewCertPool() } + tlsConfig := &tls.Config{RootCAs: pool} + if insecureSkipTLS { + tlsConfig.InsecureSkipVerify = true + } transport := &http.Transport{ - TLSClientConfig: &tls.Config{RootCAs: pool}, + TLSClientConfig: tlsConfig, } client := &http.Client{ Timeout: 30 * time.Second, diff --git a/cmd/caib/auth/oidc.go b/cmd/caib/auth/oidc.go index 826eac28..29b3169c 100644 --- a/cmd/caib/auth/oidc.go +++ b/cmd/caib/auth/oidc.go @@ -5,6 +5,7 @@ import ( "context" "crypto/rand" "crypto/sha256" + "crypto/tls" "encoding/base64" "encoding/json" "fmt" @@ -43,13 +44,14 @@ type OIDCConfig struct { // OIDCAuth handles OIDC authentication flow and token management. type OIDCAuth struct { - config OIDCConfig - tokenCache *TokenCache - cachePath string + config OIDCConfig + tokenCache *TokenCache + cachePath string + insecureSkipTLS bool } // NewOIDCAuth creates a new OIDC authenticator instance. -func NewOIDCAuth(issuerURL, clientID string, scopes []string) *OIDCAuth { +func NewOIDCAuth(issuerURL, clientID string, scopes []string, insecureSkipTLS bool) *OIDCAuth { if issuerURL == "" || clientID == "" { return nil } @@ -70,7 +72,8 @@ func NewOIDCAuth(issuerURL, clientID string, scopes []string) *OIDCAuth { ClientID: clientID, Scopes: scopes, }, - cachePath: cachePath, + cachePath: cachePath, + insecureSkipTLS: insecureSkipTLS, } } @@ -275,7 +278,9 @@ func (a *OIDCAuth) exchangeCodeForToken(ctx context.Context, tokenEndpoint, code req.Header.Set("Content-Type", "application/x-www-form-urlencoded") transport := &http.Transport{} - // Use default TLS settings + if a.insecureSkipTLS { + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } client := &http.Client{ Timeout: 30 * time.Second, @@ -323,7 +328,9 @@ type DiscoveryDocument struct { func (a *OIDCAuth) getDiscovery(discoveryURL string) (*DiscoveryDocument, error) { transport := &http.Transport{} - // Use default TLS settings + if a.insecureSkipTLS { + transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } client := &http.Client{ Timeout: 10 * time.Second, diff --git a/cmd/caib/auth/wrapper.go b/cmd/caib/auth/wrapper.go index eeb608d7..a44b9b8d 100644 --- a/cmd/caib/auth/wrapper.go +++ b/cmd/caib/auth/wrapper.go @@ -24,10 +24,10 @@ func IsAuthError(err error) bool { // Returns empty string if no OIDC config is available (auth is optional). // The boolean return indicates whether a fresh auth flow was performed. // Returns an error if OIDC is configured but config fetch fails (network/server errors). -func GetTokenWithReauth(ctx context.Context, serverURL string, currentToken string) (string, bool, error) { +func GetTokenWithReauth(ctx context.Context, serverURL string, currentToken string, insecureSkipTLS bool) (string, bool, error) { // Prefer API config over local: server is source of truth (OperatorConfig). // When server has OIDC disabled or init failed, API returns empty JWT and we should not use local OIDC. - config, err := GetOIDCConfigFromAPI(serverURL) + config, err := GetOIDCConfigFromAPI(serverURL, insecureSkipTLS) if err != nil { // Error fetching config - this is a real error, not "not configured" return "", false, fmt.Errorf("failed to fetch OIDC configuration: %w", err) @@ -37,7 +37,7 @@ func GetTokenWithReauth(ctx context.Context, serverURL string, currentToken stri return "", false, nil } - oidcAuth := NewOIDCAuth(config.IssuerURL, config.ClientID, config.Scopes) + oidcAuth := NewOIDCAuth(config.IssuerURL, config.ClientID, config.Scopes, insecureSkipTLS) if oidcAuth == nil { return "", false, fmt.Errorf("failed to initialize OIDC authenticator") } @@ -60,7 +60,7 @@ func GetTokenWithReauth(ctx context.Context, serverURL string, currentToken stri // CreateClientWithReauth creates a client and handles re-authentication on auth errors. // If authToken is nil, it will be treated as empty and OIDC will be attempted. // OIDC errors are logged but do not prevent client creation (auth is optional). -func CreateClientWithReauth(ctx context.Context, serverURL string, authToken *string) (*buildapiclient.Client, error) { +func CreateClientWithReauth(ctx context.Context, serverURL string, authToken *string, insecureSkipTLS bool) (*buildapiclient.Client, error) { // Guard against nil pointer tokenValue := "" if authToken != nil { @@ -70,7 +70,7 @@ func CreateClientWithReauth(ctx context.Context, serverURL string, authToken *st // Try to get token from OIDC if needed if tokenValue == "" { // Try OIDC auth - token, _, err := GetTokenWithReauth(ctx, serverURL, "") + token, _, err := GetTokenWithReauth(ctx, serverURL, "", insecureSkipTLS) if err != nil { // OIDC fetch failed - log but continue (auth is optional, kubeconfig may work) fmt.Printf("Warning: OIDC authentication failed: %v\n", err) @@ -85,6 +85,9 @@ func CreateClientWithReauth(ctx context.Context, serverURL string, authToken *st // Configure TLS options var opts []buildapiclient.Option opts = append(opts, buildapiclient.WithAuthToken(tokenValue)) + if insecureSkipTLS { + opts = append(opts, buildapiclient.WithInsecureTLS()) + } return buildapiclient.New(serverURL, opts...) } diff --git a/cmd/caib/catalog/add.go b/cmd/caib/catalog/add.go index 7859ae8c..ebe04297 100644 --- a/cmd/caib/catalog/add.go +++ b/cmd/caib/catalog/add.go @@ -90,7 +90,7 @@ type targetInfo struct { Verified bool `json:"verified"` } -func runAdd(_ *cobra.Command, args []string) error { +func runAdd(cmd *cobra.Command, args []string) error { name := args[0] registryURL := args[1] @@ -147,7 +147,7 @@ func runAdd(_ *cobra.Command, args []string) error { req.Header.Set("Authorization", "Bearer "+token) } - client := &http.Client{} + client := newHTTPClient(getInsecureSkipTLS(cmd)) resp, err := client.Do(req) if err != nil { return fmt.Errorf("failed to make request: %w", err) diff --git a/cmd/caib/catalog/catalog.go b/cmd/caib/catalog/catalog.go index e95ef817..89125139 100644 --- a/cmd/caib/catalog/catalog.go +++ b/cmd/caib/catalog/catalog.go @@ -17,6 +17,12 @@ limitations under the License. package catalog import ( + "crypto/tls" + "net/http" + "os" + "strconv" + "strings" + "github.com/spf13/cobra" ) @@ -57,3 +63,43 @@ func addCommonFlags(cmd *cobra.Command) { cmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Kubernetes namespace") cmd.Flags().StringVarP(&outputFormat, "output", "o", "table", "Output format (table, json, yaml)") } + +// getInsecureSkipTLS returns whether to skip TLS verification +// Checks the --insecure flag from the root command or CAIB_INSECURE env var +func getInsecureSkipTLS(cmd *cobra.Command) bool { + // Try to get from root command's persistent flag + if cmd.Root() != nil { + if flag := cmd.Root().PersistentFlags().Lookup("insecure"); flag != nil { + if val, err := strconv.ParseBool(flag.Value.String()); err == nil { + return val + } + } + } + // Fall back to env var + return envBool("CAIB_INSECURE") +} + +// envBool parses a boolean from environment variable +func envBool(key string) bool { + v := strings.TrimSpace(os.Getenv(key)) + if v == "" { + return false + } + b, err := strconv.ParseBool(v) + if err != nil { + return false + } + return b +} + +// newHTTPClient creates an HTTP client with optional insecure TLS +func newHTTPClient(insecureSkipTLS bool) *http.Client { + if insecureSkipTLS { + return &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + } + return &http.Client{} +} diff --git a/cmd/caib/catalog/get.go b/cmd/caib/catalog/get.go index e7567074..20929433 100644 --- a/cmd/caib/catalog/get.go +++ b/cmd/caib/catalog/get.go @@ -41,7 +41,7 @@ func newGetCmd() *cobra.Command { return cmd } -func runGet(_ *cobra.Command, args []string) error { +func runGet(cmd *cobra.Command, args []string) error { name := args[0] server := serverURL @@ -73,7 +73,7 @@ func runGet(_ *cobra.Command, args []string) error { req.Header.Set("Authorization", "Bearer "+token) } - client := &http.Client{} + client := newHTTPClient(getInsecureSkipTLS(cmd)) resp, err := client.Do(req) if err != nil { return fmt.Errorf("failed to make request: %w", err) diff --git a/cmd/caib/catalog/list.go b/cmd/caib/catalog/list.go index 60780a8a..5cb59948 100644 --- a/cmd/caib/catalog/list.go +++ b/cmd/caib/catalog/list.go @@ -89,7 +89,7 @@ type Target struct { Name string `json:"name"` } -func runList(_ *cobra.Command, _ []string) error { +func runList(cmd *cobra.Command, _ []string) error { // Get server URL server := serverURL if server == "" { @@ -144,7 +144,7 @@ func runList(_ *cobra.Command, _ []string) error { req.Header.Set("Authorization", "Bearer "+token) } - client := &http.Client{} + client := newHTTPClient(getInsecureSkipTLS(cmd)) resp, err := client.Do(req) if err != nil { return fmt.Errorf("failed to make request: %w", err) diff --git a/cmd/caib/catalog/publish.go b/cmd/caib/catalog/publish.go index 8ddcba72..26590b3d 100644 --- a/cmd/caib/catalog/publish.go +++ b/cmd/caib/catalog/publish.go @@ -58,7 +58,7 @@ type publishRequest struct { Tags []string `json:"tags,omitempty"` } -func runPublish(_ *cobra.Command, args []string) error { +func runPublish(cmd *cobra.Command, args []string) error { imageBuildName := args[0] server := serverURL @@ -104,7 +104,7 @@ func runPublish(_ *cobra.Command, args []string) error { req.Header.Set("Authorization", "Bearer "+token) } - client := &http.Client{} + client := newHTTPClient(getInsecureSkipTLS(cmd)) resp, err := client.Do(req) if err != nil { return fmt.Errorf("failed to make request: %w", err) diff --git a/cmd/caib/catalog/remove.go b/cmd/caib/catalog/remove.go index 3e956448..0da0d6c5 100644 --- a/cmd/caib/catalog/remove.go +++ b/cmd/caib/catalog/remove.go @@ -47,7 +47,7 @@ func newRemoveCmd() *cobra.Command { return cmd } -func runRemove(_ *cobra.Command, args []string) error { +func runRemove(cmd *cobra.Command, args []string) error { name := args[0] server := serverURL @@ -91,7 +91,7 @@ func runRemove(_ *cobra.Command, args []string) error { req.Header.Set("Authorization", "Bearer "+token) } - client := &http.Client{} + client := newHTTPClient(getInsecureSkipTLS(cmd)) resp, err := client.Do(req) if err != nil { return fmt.Errorf("failed to make request: %w", err) diff --git a/cmd/caib/catalog/verify.go b/cmd/caib/catalog/verify.go index 067fff4e..30abaa89 100644 --- a/cmd/caib/catalog/verify.go +++ b/cmd/caib/catalog/verify.go @@ -51,7 +51,7 @@ type verifyResponse struct { Triggered bool `json:"triggered"` } -func runVerify(_ *cobra.Command, args []string) error { +func runVerify(cmd *cobra.Command, args []string) error { name := args[0] server := serverURL @@ -84,7 +84,7 @@ func runVerify(_ *cobra.Command, args []string) error { req.Header.Set("Authorization", "Bearer "+token) } - client := &http.Client{} + client := newHTTPClient(getInsecureSkipTLS(cmd)) resp, err := client.Do(req) if err != nil { return fmt.Errorf("failed to make request: %w", err) diff --git a/cmd/caib/main.go b/cmd/caib/main.go index 2984d4c6..7a2df46b 100644 --- a/cmd/caib/main.go +++ b/cmd/caib/main.go @@ -5,6 +5,7 @@ import ( "bufio" "compress/gzip" "context" + "crypto/tls" "encoding/base64" "encoding/json" "fmt" @@ -15,6 +16,7 @@ import ( "os/exec" "path/filepath" "runtime" + "strconv" "strings" "text/tabwriter" "time" @@ -94,8 +96,24 @@ var ( useInternalRegistry bool internalRegistryImageName string internalRegistryTag string + + // TLS options + insecureSkipTLS bool ) +// envBool parses a boolean from environment variable +func envBool(key string) bool { + v := strings.TrimSpace(os.Getenv(key)) + if v == "" { + return false + } + b, err := strconv.ParseBool(v) + if err != nil { + return false + } + return b +} + // createBuildAPIClient creates a build API client with authentication token from flags or kubeconfig // It will attempt OIDC re-authentication if token is missing or expired func createBuildAPIClient(serverURL string, authToken *string) (*buildapiclient.Client, error) { @@ -105,7 +123,7 @@ func createBuildAPIClient(serverURL string, authToken *string) (*buildapiclient. // If no explicit token, try OIDC if config is available if !explicitToken { - token, didAuth, err := auth.GetTokenWithReauth(ctx, serverURL, "") + token, didAuth, err := auth.GetTokenWithReauth(ctx, serverURL, "", insecureSkipTLS) if err != nil { // OIDC is configured but failed - don't silently fall back to kubeconfig // This indicates a real authentication failure that should be reported @@ -146,6 +164,9 @@ func createBuildAPIClient(serverURL string, authToken *string) (*buildapiclient. } // Configure TLS + if insecureSkipTLS { + opts = append(opts, buildapiclient.WithInsecureTLS()) + } // Check for custom CA certificate if caCertFile := os.Getenv("SSL_CERT_FILE"); caCertFile != "" { opts = append(opts, buildapiclient.WithCACertificate(caCertFile)) @@ -177,7 +198,7 @@ func executeWithReauth(serverURL string, authToken *string, fn func(*buildapicli // Auth error (401) - try re-authentication; token may be rejected, not necessarily expired fmt.Println("Authentication failed (401), re-authenticating...") - newToken, _, err := auth.GetTokenWithReauth(ctx, serverURL, *authToken) + newToken, _, err := auth.GetTokenWithReauth(ctx, serverURL, *authToken, insecureSkipTLS) if err != nil { return fmt.Errorf("re-authentication failed: %w", err) } @@ -300,6 +321,14 @@ func main() { rootCmd.InitDefaultVersionFlag() rootCmd.SetVersionTemplate("caib version: {{.Version}}\n") + // Global flags + rootCmd.PersistentFlags().BoolVar( + &insecureSkipTLS, + "insecure", + envBool("CAIB_INSECURE"), + "skip TLS certificate verification (insecure, for testing only; env: CAIB_INSECURE)", + ) + // Main build command (bootc - the default, future-focused approach) buildCmd := &cobra.Command{ Use: "build ", @@ -591,7 +620,7 @@ func runLogin(_ *cobra.Command, args []string) { fmt.Printf("Server saved: %s\n", server) ctx := context.Background() - token, didAuth, err := auth.GetTokenWithReauth(ctx, server, "") + token, didAuth, err := auth.GetTokenWithReauth(ctx, server, "", insecureSkipTLS) if err != nil { fmt.Printf("Warning: authentication failed (you may need --token or kubeconfig for API calls): %v\n", err) return @@ -1326,12 +1355,16 @@ func waitForBuildCompletion(ctx context.Context, api *buildapiclient.Client, nam pendingWarningShown := false retryLimitWarningShown := false + logTransport := &http.Transport{ + ResponseHeaderTimeout: 30 * time.Second, + IdleConnTimeout: 2 * time.Minute, + } + if insecureSkipTLS { + logTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } logClient := &http.Client{ - Timeout: 10 * time.Minute, - Transport: &http.Transport{ - ResponseHeaderTimeout: 30 * time.Second, - IdleConnTimeout: 2 * time.Minute, - }, + Timeout: 10 * time.Minute, + Transport: logTransport, } streamState := &logStreamState{} @@ -2000,12 +2033,16 @@ func waitForFlashCompletion(ctx context.Context, api *buildapiclient.Client, nam var lastPhase, lastMessage string pendingWarningShown := false + flashLogTransport := &http.Transport{ + ResponseHeaderTimeout: 30 * time.Second, + IdleConnTimeout: 2 * time.Minute, + } + if insecureSkipTLS { + flashLogTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } logClient := &http.Client{ - Timeout: 10 * time.Minute, - Transport: &http.Transport{ - ResponseHeaderTimeout: 30 * time.Second, - IdleConnTimeout: 2 * time.Minute, - }, + Timeout: 10 * time.Minute, + Transport: flashLogTransport, } streamState := &logStreamState{} From 035bfd3bf5d8ef6c5e3933e0ca850124c109b733 Mon Sep 17 00:00:00 2001 From: Bella Khizgiyaev Date: Sun, 8 Feb 2026 19:07:16 +0200 Subject: [PATCH 2/2] Modify caib unit tests to support --insecure flag Signed-off-by: Bella Khizgiyaev --- cmd/caib/auth/config_test.go | 14 +++++++------- cmd/caib/auth/wrapper_test.go | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/caib/auth/config_test.go b/cmd/caib/auth/config_test.go index 5619a9a4..6bb01a88 100644 --- a/cmd/caib/auth/config_test.go +++ b/cmd/caib/auth/config_test.go @@ -43,7 +43,7 @@ var _ = Describe("GetOIDCConfigFromAPI", func() { _ = json.NewEncoder(w).Encode(response) })) - config, err := GetOIDCConfigFromAPI(server.URL) + config, err := GetOIDCConfigFromAPI(server.URL, false) Expect(err).NotTo(HaveOccurred()) Expect(config).NotTo(BeNil()) Expect(config.IssuerURL).To(Equal("https://issuer.example.com")) @@ -56,7 +56,7 @@ var _ = Describe("GetOIDCConfigFromAPI", func() { w.WriteHeader(http.StatusNotFound) })) - config, err := GetOIDCConfigFromAPI(server.URL) + config, err := GetOIDCConfigFromAPI(server.URL, false) Expect(err).NotTo(HaveOccurred()) Expect(config).To(BeNil()) }) @@ -66,7 +66,7 @@ var _ = Describe("GetOIDCConfigFromAPI", func() { w.WriteHeader(http.StatusInternalServerError) })) - config, err := GetOIDCConfigFromAPI(server.URL) + config, err := GetOIDCConfigFromAPI(server.URL, false) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("HTTP 500")) Expect(config).To(BeNil()) @@ -82,7 +82,7 @@ var _ = Describe("GetOIDCConfigFromAPI", func() { _ = json.NewEncoder(w).Encode(response) })) - config, err := GetOIDCConfigFromAPI(server.URL) + config, err := GetOIDCConfigFromAPI(server.URL, false) Expect(err).NotTo(HaveOccurred()) Expect(config).To(BeNil()) }) @@ -103,14 +103,14 @@ var _ = Describe("GetOIDCConfigFromAPI", func() { _ = json.NewEncoder(w).Encode(response) })) - config, err := GetOIDCConfigFromAPI(server.URL) + config, err := GetOIDCConfigFromAPI(server.URL, false) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("client ID is required")) Expect(config).To(BeNil()) }) It("should return error when network request fails", func() { - config, err := GetOIDCConfigFromAPI("http://invalid-host-that-does-not-exist:9999") + config, err := GetOIDCConfigFromAPI("http://invalid-host-that-does-not-exist:9999", false) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to fetch OIDC config from API")) Expect(config).To(BeNil()) @@ -122,7 +122,7 @@ var _ = Describe("GetOIDCConfigFromAPI", func() { _, _ = w.Write([]byte("invalid json")) })) - config, err := GetOIDCConfigFromAPI(server.URL) + config, err := GetOIDCConfigFromAPI(server.URL, false) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to decode")) Expect(config).To(BeNil()) diff --git a/cmd/caib/auth/wrapper_test.go b/cmd/caib/auth/wrapper_test.go index 8f164d53..79b26f49 100644 --- a/cmd/caib/auth/wrapper_test.go +++ b/cmd/caib/auth/wrapper_test.go @@ -10,7 +10,7 @@ import ( var _ = Describe("CreateClientWithReauth", func() { It("should handle nil authToken pointer safely", func() { ctx := context.Background() - client, err := CreateClientWithReauth(ctx, "https://api.example.com", nil) + client, err := CreateClientWithReauth(ctx, "https://api.example.com", nil, false) Expect(err).NotTo(HaveOccurred()) Expect(client).NotTo(BeNil()) }) @@ -18,7 +18,7 @@ var _ = Describe("CreateClientWithReauth", func() { It("should create client with empty token when authToken is empty string", func() { ctx := context.Background() emptyToken := "" - client, err := CreateClientWithReauth(ctx, "https://api.example.com", &emptyToken) + client, err := CreateClientWithReauth(ctx, "https://api.example.com", &emptyToken, false) Expect(err).NotTo(HaveOccurred()) Expect(client).NotTo(BeNil()) }) @@ -26,7 +26,7 @@ var _ = Describe("CreateClientWithReauth", func() { It("should create client with provided token", func() { ctx := context.Background() token := "test-token" - client, err := CreateClientWithReauth(ctx, "https://api.example.com", &token) + client, err := CreateClientWithReauth(ctx, "https://api.example.com", &token, false) Expect(err).NotTo(HaveOccurred()) Expect(client).NotTo(BeNil()) }) @@ -35,7 +35,7 @@ var _ = Describe("CreateClientWithReauth", func() { ctx := context.Background() emptyToken := "" // Use invalid server URL to trigger OIDC error - client, err := CreateClientWithReauth(ctx, "http://invalid-server:9999", &emptyToken) + client, err := CreateClientWithReauth(ctx, "http://invalid-server:9999", &emptyToken, false) // Should still create client even if OIDC fails (auth is optional) Expect(err).NotTo(HaveOccurred()) Expect(client).NotTo(BeNil())