diff --git a/device-discovery-agent/README.md b/device-discovery-agent/README.md index cdf37dc8..c537acc8 100644 --- a/device-discovery-agent/README.md +++ b/device-discovery-agent/README.md @@ -142,17 +142,17 @@ The Device Discovery Agent can be configured using multiple sources. Configurati The configuration file uses KEY=VALUE format (not YAML): ```bash -# Service Endpoints +# Service Endpoints (Required) OBM_SVC=localhost OBS_SVC=localhost OBM_PORT=50051 KEYCLOAK_URL=keycloak.example.com -CA_CERT=/etc/intel_edge_node/orch-ca-cert/orch-ca.pem # Auto-detection AUTO_DETECT=true # Optional Settings +# CA_CERT=/etc/intel_edge_node/orch-ca-cert/orch-ca.pem # Optional - uses system CAs if not provided DEBUG=false TIMEOUT=5m DISABLE_INTERACTIVE=false @@ -194,7 +194,6 @@ The following flags are required unless specified in a configuration file: - `-obs-svc` - Onboarding stream service address (hostname or IP) - `-obm-port` - Onboarding manager port (default: 50051) - `-keycloak-url` - Keycloak authentication URL (hostname or IP) -- `-ca-cert` - Path to CA certificate (required for TLS) - `-mac` - MAC address of the device (required unless using `-auto-detect`) ### Optional Flags @@ -202,6 +201,9 @@ The following flags are required unless specified in a configuration file: **Configuration File:** - `-config` - Path to configuration file in KEY=VALUE format (e.g., `/etc/edge-node/node/confs/device-discovery-agent.env`) +**TLS Configuration:** +- `-ca-cert` - Path to CA certificate (optional, uses system default CAs if not provided) + **Device Information (auto-detected if not provided):** - `-serial` - Serial number (auto-detected using dmidecode) - `-uuid` - System UUID (auto-detected using dmidecode) @@ -224,18 +226,17 @@ The following flags are required unless specified in a configuration file: ./device-discovery-agent -config /etc/edge-node/node/confs/device-discovery-agent.env ``` -#### 2. Auto-detect all system information +#### 2. Auto-detect all system information (using system default CAs) ```bash ./device-discovery-agent \ -obm-svc obm.example.com \ -obs-svc obs.example.com \ -obm-port 50051 \ -keycloak-url keycloak.example.com \ - -ca-cert /etc/intel_edge_node/orch-ca-cert/orch-ca.pem \ -auto-detect ``` -#### 3. Specify MAC address, auto-detect other info +#### 3. Auto-detect with custom CA certificate ```bash ./device-discovery-agent \ -obm-svc obm.example.com \ @@ -243,24 +244,33 @@ The following flags are required unless specified in a configuration file: -obm-port 50051 \ -keycloak-url keycloak.example.com \ -ca-cert /etc/intel_edge_node/orch-ca-cert/orch-ca.pem \ + -auto-detect +``` + +#### 4. Specify MAC address, auto-detect other info +```bash +./device-discovery-agent \ + -obm-svc obm.example.com \ + -obs-svc obs.example.com \ + -obm-port 50051 \ + -keycloak-url keycloak.example.com \ -mac 00:11:22:33:44:55 ``` -#### 4. Fully manual configuration +#### 5. Fully manual configuration ```bash ./device-discovery-agent \ -obm-svc obm.example.com \ -obs-svc obs.example.com \ -obm-port 50051 \ -keycloak-url keycloak.example.com \ - -ca-cert /etc/intel_edge_node/orch-ca-cert/orch-ca.pem \ -mac 00:11:22:33:44:55 \ -serial ABC123 \ -uuid 12345678-1234-1234-1234-123456789012 \ -ip 192.168.1.100 ``` -#### 5. With debug mode and extra hosts +#### 6. With debug mode and extra hosts ```bash ./device-discovery-agent \ -obm-svc obm.example.com \ @@ -274,7 +284,7 @@ The following flags are required unless specified in a configuration file: -extra-hosts "registry.local:10.0.0.1,api.local:10.0.0.2" ``` -#### 6. Override config file values with CLI flags +#### 7. Override config file values with CLI flags ```bash # Config file has OBM_SVC=localhost, but we override it ./device-discovery-agent \ diff --git a/device-discovery-agent/cmd/device-discovery/main.go b/device-discovery-agent/cmd/device-discovery/main.go index 6188cd2a..0bbe620e 100644 --- a/device-discovery-agent/cmd/device-discovery/main.go +++ b/device-discovery-agent/cmd/device-discovery/main.go @@ -140,7 +140,7 @@ func parseFinalFlags(cfg *config.Config) { // Optional configuration - use current values as defaults flag.StringVar(&cfg.ExtraHosts, "extra-hosts", cfg.ExtraHosts, "Additional host mappings (comma-separated: 'host1:ip1,host2:ip2')") - flag.StringVar(&cfg.CaCertPath, "ca-cert", cfg.CaCertPath, "Path to CA certificate (required)") + flag.StringVar(&cfg.CaCertPath, "ca-cert", cfg.CaCertPath, "Path to CA certificate (optional, uses system CAs if not provided)") flag.BoolVar(&cfg.Debug, "debug", cfg.Debug, "Enable debug mode with timeout") flag.DurationVar(&cfg.Timeout, "timeout", cfg.Timeout, "Timeout duration for debug mode") flag.BoolVar(&cfg.DisableInteractiveMode, "disable-interactive", cfg.DisableInteractiveMode, "Disable interactive mode fallback") @@ -167,10 +167,11 @@ func printUsage() { fmt.Fprintf(os.Stderr, " Onboarding manager port\n") fmt.Fprintf(os.Stderr, " -keycloak-url string\n") fmt.Fprintf(os.Stderr, " Keycloak authentication URL\n") - fmt.Fprintf(os.Stderr, " -ca-cert string\n") - fmt.Fprintf(os.Stderr, " Path to CA certificate\n") fmt.Fprintf(os.Stderr, " -mac string\n") fmt.Fprintf(os.Stderr, " MAC address of the device (required unless -auto-detect is used)\n") + fmt.Fprintf(os.Stderr, "\nOptional Configuration:\n") + fmt.Fprintf(os.Stderr, " -ca-cert string\n") + fmt.Fprintf(os.Stderr, " Path to CA certificate (optional, uses system CAs if not provided)\n") fmt.Fprintf(os.Stderr, "\nOptional Device Information:\n") fmt.Fprintf(os.Stderr, " -serial string\n") fmt.Fprintf(os.Stderr, " Serial number (auto-detected if not provided)\n") diff --git a/device-discovery-agent/configs/device-discovery-agent.env b/device-discovery-agent/configs/device-discovery-agent.env index c57423fc..be22f5ba 100644 --- a/device-discovery-agent/configs/device-discovery-agent.env +++ b/device-discovery-agent/configs/device-discovery-agent.env @@ -29,10 +29,6 @@ OBM_PORT=50051 # CLI flag: -keycloak-url KEYCLOAK_URL=keycloak.example.com -# CA Certificate Path (required for TLS verification) -# CLI flag: -ca-cert -CA_CERT=/etc/intel_edge_node/orch-ca-cert/orch-ca.pem - # ============================================================================ # REQUIRED (unless AUTO_DETECT=true): Device Information # ============================================================================ @@ -75,6 +71,11 @@ AUTO_DETECT=true # OPTIONAL: Additional Configuration # ============================================================================ +# CA Certificate Path (optional for TLS verification) +# CLI flag: -ca-cert +# Note: If not provided, system default CA certificates will be used +# CA_CERT=/etc/intel_edge_node/orch-ca-cert/orch-ca.pem + # Additional host-to-IP mappings for /etc/hosts # CLI flag: -extra-hosts # Format: host1:ip1,host2:ip2,host3:ip3 @@ -120,9 +121,13 @@ USE_KERNEL_ARGS=false # PRIORITY ORDER (highest to lowest) # ============================================================================ # 1. CLI flags (highest priority) -# 2. Kernel command line arguments (if -use-kernel-args is passed) -# 3. This configuration file (loaded via -config flag) -# 4. Default values hardcoded in the application +# 2. This configuration file (loaded via -config flag) +# 3. Kernel command line arguments (if -use-kernel-args is passed) +# 4. Default values hardcoded in the application (lowest priority) +# +# Note: The config file overrides kernel arguments because the config file +# is reloaded after kernel args are parsed (unless -use-kernel-args was +# specified directly on CLI, in which case kernel args take precedence). # ============================================================================ # USAGE EXAMPLES diff --git a/device-discovery-agent/internal/auth/auth.go b/device-discovery-agent/internal/auth/auth.go index 98beea54..f8e770ed 100644 --- a/device-discovery-agent/internal/auth/auth.go +++ b/device-discovery-agent/internal/auth/auth.go @@ -44,18 +44,29 @@ func fetchAccessToken(keycloakURL string, clientID string, clientSecret string, } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - // Load the CA certificate - caCertPool, err := loadCACertPool(caCertPath) - if err != nil { - return "", fmt.Errorf("error loading CA certificate: %v", err) - } - - // Create an HTTP client with the CA certificate + // Configure TLS + var tlsConfig *tls.Config + if caCertPath != "" { + // Load the CA certificate from provided path + caCertPool, err := loadCACertPool(caCertPath) + if err != nil { + return "", fmt.Errorf("error loading CA certificate: %v", err) + } + tlsConfig = &tls.Config{ + RootCAs: caCertPool, + MinVersion: tls.VersionTLS12, + } + } else { + // Use system default CA certificates + tlsConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + } + } + + // Create an HTTP client with TLS configuration client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: caCertPool, - }, + TLSClientConfig: tlsConfig, }, } @@ -92,10 +103,23 @@ func fetchReleaseToken(releaseServerURL string, accessToken string, caCertPath s return "", fmt.Errorf("access token is required") } - // Load CA certificate - caCertPool, err := loadCACertPool(caCertPath) - if err != nil { - return "", fmt.Errorf("error loading CA certificate: %v", err) + // Configure TLS + var tlsConfig *tls.Config + if caCertPath != "" { + // Load CA certificate from provided path + caCertPool, err := loadCACertPool(caCertPath) + if err != nil { + return "", fmt.Errorf("error loading CA certificate: %v", err) + } + tlsConfig = &tls.Config{ + RootCAs: caCertPool, + MinVersion: tls.VersionTLS12, + } + } else { + // Use system default CA certificates + tlsConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + } } // Construct the HTTP request @@ -107,12 +131,10 @@ func fetchReleaseToken(releaseServerURL string, accessToken string, caCertPath s // Add the authorization header with the bearer token req.Header.Set("Authorization", "Bearer "+accessToken) - // Create an HTTP client with CA certificate + // Create an HTTP client with TLS configuration client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: caCertPool, - }, + TLSClientConfig: tlsConfig, }, } diff --git a/device-discovery-agent/internal/config/config.go b/device-discovery-agent/internal/config/config.go index f8f41bfa..37c8e473 100644 --- a/device-discovery-agent/internal/config/config.go +++ b/device-discovery-agent/internal/config/config.go @@ -318,9 +318,7 @@ func Validate(cfg *Config) error { if cfg.KeycloakURL == "" { missing = append(missing, "KEYCLOAK_URL") } - if cfg.CaCertPath == "" { - missing = append(missing, "CA_CERT") - } + // CA_CERT is optional - if not provided, system default CAs will be used if cfg.MacAddr == "" { missing = append(missing, "MAC") } diff --git a/device-discovery-agent/internal/config/config_fuzz_test.go b/device-discovery-agent/internal/config/config_fuzz_test.go index 2a42d88a..70d4a899 100644 --- a/device-discovery-agent/internal/config/config_fuzz_test.go +++ b/device-discovery-agent/internal/config/config_fuzz_test.go @@ -214,10 +214,8 @@ func FuzzValidate(f *testing.F) { if cfg.KeycloakURL == "" { t.Error("Validation passed but KeycloakURL is empty") } - if cfg.CaCertPath == "" { - t.Error("Validation passed but CaCertPath is empty") - } - // Note: MAC, SERIAL, UUID, IPAddress can be auto-detected + // CaCertPath is optional - system default CAs will be used if empty + // MAC, SERIAL, UUID, IPAddress can be auto-detected // so validation may pass even if they're empty } }) diff --git a/device-discovery-agent/internal/config/config_test.go b/device-discovery-agent/internal/config/config_test.go index f1506f14..327f0735 100644 --- a/device-discovery-agent/internal/config/config_test.go +++ b/device-discovery-agent/internal/config/config_test.go @@ -419,32 +419,20 @@ func TestValidate_MissingCriticalFields(t *testing.T) { { name: "missing KeycloakURL", cfg: &Config{ - ObmSvc: "obm.example.com", - ObsSvc: "obs.example.com", - ObmPort: 50051, - CaCertPath: "/path/to/cert", + ObmSvc: "obm.example.com", + ObsSvc: "obs.example.com", + ObmPort: 50051, }, wantErr: true, }, { - name: "missing CaCertPath", + name: "missing non-critical fields (Serial, UUID, IP, MAC, CaCertPath)", cfg: &Config{ ObmSvc: "obm.example.com", ObsSvc: "obs.example.com", ObmPort: 50051, KeycloakURL: "keycloak.example.com", - }, - wantErr: true, - }, - { - name: "missing non-critical fields (Serial, UUID, IP, MAC)", - cfg: &Config{ - ObmSvc: "obm.example.com", - ObsSvc: "obs.example.com", - ObmPort: 50051, - KeycloakURL: "keycloak.example.com", - CaCertPath: "/path/to/cert", - // SerialNumber, UUID, IPAddress, MacAddr can be auto-detected + // SerialNumber, UUID, IPAddress, MacAddr, CaCertPath can be auto-detected or use system defaults }, wantErr: false, }, diff --git a/device-discovery-agent/internal/mode/noninteractive/client.go b/device-discovery-agent/internal/mode/noninteractive/client.go index 3b0e1625..24cb3249 100644 --- a/device-discovery-agent/internal/mode/noninteractive/client.go +++ b/device-discovery-agent/internal/mode/noninteractive/client.go @@ -5,6 +5,7 @@ package noninteractive import ( "context" + "crypto/tls" "crypto/x509" "fmt" "io" @@ -55,20 +56,29 @@ func NewClient(address string, port int, mac, uuid, serial, ipAddress, caCertPat // createSecureConnection creates a secure gRPC connection with TLS. func createSecureConnection(target string, caCertPath string) (*grpc.ClientConn, error) { - // Load the CA certificate - caCert, err := os.ReadFile(caCertPath) - if err != nil { - return nil, fmt.Errorf("failed to read CA certificate: %v", err) - } + var creds credentials.TransportCredentials - // Create a certificate pool from the CA certificate - certPool := x509.NewCertPool() - if !certPool.AppendCertsFromPEM(caCert) { - return nil, fmt.Errorf("failed to append CA certificate to cert pool") - } + if caCertPath != "" { + // Load the CA certificate from the provided path + caCert, err := os.ReadFile(caCertPath) + if err != nil { + return nil, fmt.Errorf("failed to read CA certificate: %v", err) + } + + // Create a certificate pool from the CA certificate + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(caCert) { + return nil, fmt.Errorf("failed to append CA certificate to cert pool") + } - // Create the credentials using the certificate pool - creds := credentials.NewClientTLSFromCert(certPool, "") + // Create the credentials using the certificate pool + creds = credentials.NewClientTLSFromCert(certPool, "") + } else { + // Use system default CA certificates + creds = credentials.NewTLS(&tls.Config{ + MinVersion: tls.VersionTLS12, + }) + } // Create the gRPC connection with TLS credentials conn, err := grpc.NewClient(