Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions cmd/aepcli/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func configCmd(configFile string) *cobra.Command {
var serverURL string
var headers []string
var pathPrefix string
var caCertPath string

configCmd := &cobra.Command{
Use: "config",
Expand All @@ -111,6 +112,7 @@ func configCmd(configFile string) *cobra.Command {
ServerURL: serverURL,
Headers: headers,
PathPrefix: pathPrefix,
CACertPath: caCertPath,
}
if err := config.WriteAPIWithName(configFile, api, overwrite); err != nil {
fmt.Printf("Error writing API config: %v\n", err)
Expand All @@ -124,6 +126,7 @@ func configCmd(configFile string) *cobra.Command {
addCmd.Flags().StringArrayVar(&headers, "header", []string{}, "Headers in format key=value")
addCmd.Flags().StringVar(&serverURL, "server-url", "", "Server URL")
addCmd.Flags().StringVar(&pathPrefix, "path-prefix", "", "Path prefix")
addCmd.Flags().StringVar(&caCertPath, "ca-cert", "", "Path to custom CA certificate file (PEM format)")
addCmd.Flags().BoolVar(&overwrite, "overwrite", false, "Overwrite existing configuration")

readCmd := &cobra.Command{
Expand All @@ -148,6 +151,7 @@ func configCmd(configFile string) *cobra.Command {
fmt.Printf("Server URL: %s\n", api.ServerURL)
fmt.Printf("Headers: %v\n", api.Headers)
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
fmt.Printf("CA Certificate Path: %s\n", api.CACertPath)
},
}

Expand All @@ -173,6 +177,7 @@ func configCmd(configFile string) *cobra.Command {
fmt.Printf("Server URL: %s\n", api.ServerURL)
fmt.Printf("Headers: %v\n", api.Headers)
fmt.Printf("Path Prefix: %s\n", api.PathPrefix)
fmt.Printf("CA Certificate Path: %s\n", api.CACertPath)
fmt.Println()
}
},
Expand Down
10 changes: 9 additions & 1 deletion cmd/aepcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func aepcli(args []string) (int, error) {
var dryRun bool
var logHTTP bool
var insecure bool
var caCertPath string
var logLevel string
var fileAliasOrCore string
var additionalArgs []string
Expand Down Expand Up @@ -65,6 +66,7 @@ func aepcli(args []string) (int, error) {
rootCmd.PersistentFlags().BoolVar(&logHTTP, "log-http", false, "Set to true to log HTTP requests. This can be helpful when attempting to write your own code or debug.")
rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Set to true to not make any changes. This can be helpful when paired with log-http to just view http requests instead of perform them.")
rootCmd.PersistentFlags().BoolVar(&insecure, "insecure", false, "Set to true to skip TLS certificate verification. Use with caution.")
rootCmd.PersistentFlags().StringVar(&caCertPath, "ca-cert", "", "Path to custom CA certificate file (PEM format) to add to the trusted certificate pool")
rootCmd.PersistentFlags().StringVar(&pathPrefix, "path-prefix", "", "Specify a path prefix that is prepended to all paths in the openapi schema. This will strip them when evaluating the resource hierarchy paths.")
rootCmd.PersistentFlags().StringVar(&serverURL, "server-url", "", "Specify a URL to use for the server. If not specified, the first server URL in the OpenAPI definition will be used.")
rootCmd.PersistentFlags().StringVar(&configFileVar, "config", "", "Path to config file")
Expand Down Expand Up @@ -104,6 +106,9 @@ func aepcli(args []string) (int, error) {
if pathPrefix == "" {
pathPrefix = api.PathPrefix
}
if caCertPath == "" {
caCertPath = api.CACertPath
}
headers = append(headers, api.Headers...)
serverURL = api.ServerURL
}
Expand All @@ -121,7 +126,10 @@ func aepcli(args []string) (int, error) {
return CODE_ERR, fmt.Errorf("unable to parse headers: %w", err)
}

s = service.NewServiceCommand(api, headersMap, dryRun, logHTTP, insecure)
s, err = service.NewServiceCommand(api, headersMap, dryRun, logHTTP, insecure, caCertPath)
if err != nil {
return CODE_ERR, fmt.Errorf("unable to create service command: %w", err)
}

result, err := s.Execute(additionalArgs)
returnCode := CODE_OK
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type API struct {
ServerURL string
Headers []string
PathPrefix string
CACertPath string
}

func ReadConfigFromFile(file string) (*Config, error) {
Expand Down
2 changes: 2 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func TestConfigEndToEnd(t *testing.T) {
ServerURL: "https://api.example.com",
Headers: []string{"Authorization=Bearer token"},
PathPrefix: "/v1",
CACertPath: "/path/to/ca.pem",
}

// Write API config to file
Expand Down Expand Up @@ -54,6 +55,7 @@ func TestWriteAPIWithEmptyName(t *testing.T) {
ServerURL: "https://api.example.com",
Headers: []string{"Authorization=Bearer token"},
PathPrefix: "/v1",
CACertPath: "/path/to/ca.pem",
}

err := WriteAPIWithName(testFile, testAPI, false)
Expand Down
61 changes: 61 additions & 0 deletions internal/service/ca.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package service

import (
"crypto/x509"
"encoding/pem"
"fmt"
"log/slog"
"os"
)

// loadCACertificate loads a CA certificate from the specified file path and returns a *x509.CertPool
// that includes the system CA certificates plus the custom CA certificate.
func loadCACertificate(caCertPath string) (*x509.CertPool, error) {
if caCertPath == "" {
// Return system CA pool when no custom CA is specified
return x509.SystemCertPool()
}

slog.Debug("Loading custom CA certificate", "path", caCertPath)

// Read the CA certificate file
caCertData, err := os.ReadFile(caCertPath)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("Failed to read CA certificate from %s: file does not exist\n\nTo fix this issue:\n 1. Verify the file path is correct\n 2. Ensure the file exists and is readable", caCertPath)
}
if os.IsPermission(err) {
return nil, fmt.Errorf("Failed to read CA certificate from %s: permission denied\n\nTo fix this issue:\n 1. Verify you have read permissions for the file\n 2. Check file permissions with: ls -l %s", caCertPath, caCertPath)
}
return nil, fmt.Errorf("Failed to read CA certificate from %s: %v", caCertPath, err)
}

// Start with system CA certificates
caCertPool, err := x509.SystemCertPool()
if err != nil {
slog.Warn("Failed to load system CA certificates, using empty pool", "error", err)
caCertPool = x509.NewCertPool()
} else {
slog.Debug("System CA certificates loaded from system trust store")
}

// Parse the PEM block first to validate format
block, _ := pem.Decode(caCertData)
if block == nil || block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("Failed to parse CA certificate from %s: not valid PEM format\n\nExpected format:\n -----BEGIN CERTIFICATE-----\n ...\n -----END CERTIFICATE-----\n\nUse 'openssl x509 -in %s -text -noout' to verify the certificate.", caCertPath, caCertPath)
}

// Parse the certificate to ensure it's valid
_, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("Failed to parse CA certificate from %s: invalid certificate data: %v\n\nUse 'openssl x509 -in %s -text -noout' to verify the certificate.", caCertPath, err, caCertPath)
}

// Add the custom CA certificate
if !caCertPool.AppendCertsFromPEM(caCertData) {
return nil, fmt.Errorf("Failed to add CA certificate from %s to certificate pool", caCertPath)
}

slog.Debug("Custom CA certificate loaded", "path", caCertPath)
return caCertPool, nil
}
Loading
Loading