Skip to content

Commit 1dc41cd

Browse files
committed
refactor: continue cira
1 parent 1f5de8c commit 1dc41cd

File tree

7 files changed

+593
-7
lines changed

7 files changed

+593
-7
lines changed

cmd/app/main.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/device-management-toolkit/console/config"
1414
"github.com/device-management-toolkit/console/internal/app"
15+
"github.com/device-management-toolkit/console/internal/certificates"
1516
"github.com/device-management-toolkit/console/internal/controller/openapi"
1617
"github.com/device-management-toolkit/console/internal/usecase"
1718
"github.com/device-management-toolkit/console/pkg/logger"
@@ -42,11 +43,15 @@ func main() {
4243
log.Fatalf("App init error: %s", err)
4344
}
4445

45-
// root, privateKey, err := certificates.GenerateRootCertificate(true, cfg.CommonName, "US", "open-amt-cloud-toolkit", true)
46-
// if err != nil {
47-
// log.Fatalf("Error generating root certificate: %s", err)
48-
// }
49-
// certificates.IssueWebServerCertificate(certificates.CertAndKeyType{Cert: root, Key: privateKey}, false, cfg.CommonName, "US", "open-amt-cloud-toolkit", true)
46+
root, privateKey, err := certificates.CheckAndLoadOrGenerateRootCertificate(true, cfg.CommonName, "US", "device-management-toolkit", true)
47+
if err != nil {
48+
log.Fatalf("Error loading or generating root certificate: %s", err)
49+
}
50+
51+
_, _, err = certificates.CheckAndLoadOrGenerateWebServerCertificate(certificates.CertAndKeyType{Cert: root, Key: privateKey}, false, cfg.CommonName, "US", "device-management-toolkit", true)
52+
if err != nil {
53+
log.Fatalf("Error loading or generating web server certificate: %s", err)
54+
}
5055

5156
handleEncryptionKey(cfg)
5257

config/config.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
"errors"
55
"flag"
6+
"net"
67
"os"
78
"path/filepath"
89
"time"
@@ -94,14 +95,36 @@ type (
9495
}
9596
)
9697

98+
// getPreferredIPAddress detects the most likely candidate IP address for this machine.
99+
// It prefers non-loopback IPv4 addresses and excludes link-local addresses.
100+
func getPreferredIPAddress() string {
101+
addrs, err := net.InterfaceAddrs()
102+
if err != nil {
103+
return "localhost"
104+
}
105+
106+
for _, addr := range addrs {
107+
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
108+
if ipNet.IP.To4() != nil {
109+
// Exclude link-local addresses (169.254.x.x)
110+
if !ipNet.IP.IsLinkLocalUnicast() {
111+
return ipNet.IP.String()
112+
}
113+
}
114+
}
115+
}
116+
117+
return "localhost"
118+
}
119+
97120
// defaultConfig constructs the in-memory default configuration.
98121
func defaultConfig() *Config {
99122
return &Config{
100123
App: App{
101124
Name: "console",
102125
Repo: "device-management-toolkit/console",
103126
Version: "DEVELOPMENT",
104-
CommonName: "localhost",
127+
CommonName: getPreferredIPAddress(),
105128
EncryptionKey: "",
106129
AllowInsecureCiphers: false,
107130
},

internal/app/app.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,11 @@ func Run(cfg *config.Config) {
7878

7979
wsv1.RegisterRoutes(handler, log, usecases.Devices, upgrader)
8080

81-
ciraServer, err := cira.NewServer("config/cert.pem", "config/key.pem", usecases.Devices)
81+
// Use the same certificates that were generated in main.go
82+
ciraCertFile := fmt.Sprintf("config/%s_cert.pem", cfg.CommonName)
83+
ciraKeyFile := fmt.Sprintf("config/%s_key.pem", cfg.CommonName)
84+
85+
ciraServer, err := cira.NewServer(ciraCertFile, ciraKeyFile, usecases.Devices)
8286
if err != nil {
8387
log.Fatal("CIRA Server failed: %v", err)
8488
}

internal/certificates/generate.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,95 @@ import (
1414
"time"
1515
)
1616

17+
// LoadCertificateFromFile loads a certificate and private key from PEM files
18+
func LoadCertificateFromFile(certPath, keyPath string) (*x509.Certificate, *rsa.PrivateKey, error) {
19+
// Read certificate file
20+
certPEM, err := os.ReadFile(certPath)
21+
if err != nil {
22+
return nil, nil, fmt.Errorf("failed to read certificate file: %w", err)
23+
}
24+
25+
// Decode PEM block
26+
certBlock, _ := pem.Decode(certPEM)
27+
if certBlock == nil {
28+
return nil, nil, fmt.Errorf("failed to decode certificate PEM")
29+
}
30+
31+
// Parse certificate
32+
cert, err := x509.ParseCertificate(certBlock.Bytes)
33+
if err != nil {
34+
return nil, nil, fmt.Errorf("failed to parse certificate: %w", err)
35+
}
36+
37+
// Read private key file
38+
keyPEM, err := os.ReadFile(keyPath)
39+
if err != nil {
40+
return nil, nil, fmt.Errorf("failed to read private key file: %w", err)
41+
}
42+
43+
// Decode PEM block
44+
keyBlock, _ := pem.Decode(keyPEM)
45+
if keyBlock == nil {
46+
return nil, nil, fmt.Errorf("failed to decode private key PEM")
47+
}
48+
49+
// Parse private key
50+
privateKey, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
51+
if err != nil {
52+
return nil, nil, fmt.Errorf("failed to parse private key: %w", err)
53+
}
54+
55+
return cert, privateKey, nil
56+
}
57+
58+
// CheckAndLoadOrGenerateRootCertificate checks if root certificate files exist,
59+
// loads them if they do, or generates new ones if they don't
60+
func CheckAndLoadOrGenerateRootCertificate(addThumbPrintToName bool, commonName, country, organization string, strong bool) (*x509.Certificate, *rsa.PrivateKey, error) {
61+
certPath := "config/root_cert.pem"
62+
keyPath := "config/root_key.pem"
63+
64+
// Check if both files exist
65+
_, certErr := os.Stat(certPath)
66+
_, keyErr := os.Stat(keyPath)
67+
68+
if certErr == nil && keyErr == nil {
69+
// Files exist, try to load them
70+
cert, key, err := LoadCertificateFromFile(certPath, keyPath)
71+
if err == nil {
72+
return cert, key, nil
73+
}
74+
// If loading fails, fall through to generation
75+
fmt.Printf("Warning: Failed to load existing certificates: %v. Generating new ones...\n", err)
76+
}
77+
78+
// Files don't exist or loading failed, generate new certificates
79+
return GenerateRootCertificate(addThumbPrintToName, commonName, country, organization, strong)
80+
}
81+
82+
// CheckAndLoadOrGenerateWebServerCertificate checks if web server certificate files exist,
83+
// loads them if they do, or generates new ones if they don't
84+
func CheckAndLoadOrGenerateWebServerCertificate(rootCert CertAndKeyType, addThumbPrintToName bool, commonName, country, organization string, strong bool) (*x509.Certificate, *rsa.PrivateKey, error) {
85+
certPath := "config/" + commonName + "_cert.pem"
86+
keyPath := "config/" + commonName + "_key.pem"
87+
88+
// Check if both files exist
89+
_, certErr := os.Stat(certPath)
90+
_, keyErr := os.Stat(keyPath)
91+
92+
if certErr == nil && keyErr == nil {
93+
// Files exist, try to load them
94+
cert, key, err := LoadCertificateFromFile(certPath, keyPath)
95+
if err == nil {
96+
return cert, key, nil
97+
}
98+
// If loading fails, fall through to generation
99+
fmt.Printf("Warning: Failed to load existing certificates: %v. Generating new ones...\n", err)
100+
}
101+
102+
// Files don't exist or loading failed, generate new certificates
103+
return IssueWebServerCertificate(rootCert, addThumbPrintToName, commonName, country, organization, strong)
104+
}
105+
17106
func GenerateRootCertificate(addThumbPrintToName bool, commonName, country, organization string, strong bool) (*x509.Certificate, *rsa.PrivateKey, error) {
18107
keyLength := 2048
19108
if strong {

internal/controller/http/router.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ func NewRouter(handler *gin.Engine, l logger.Interface, t usecase.Usecases, cfg
9797
{
9898
v1.NewDeviceRoutes(h2, t.Devices, l)
9999
v1.NewAmtRoutes(h2, t.Devices, t.AMTExplorer, t.Exporter, l)
100+
v1.NewCIRACertRoutes(h2, l)
101+
100102
}
101103

102104
h := protected.Group("/v1/admin")
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package v1
2+
3+
import (
4+
"encoding/pem"
5+
"net/http"
6+
"os"
7+
"strings"
8+
9+
"github.com/gin-gonic/gin"
10+
11+
"github.com/device-management-toolkit/console/pkg/logger"
12+
)
13+
14+
type ciraCertRoutes struct {
15+
l logger.Interface
16+
}
17+
18+
func NewCIRACertRoutes(handler *gin.RouterGroup, l logger.Interface) {
19+
r := &ciraCertRoutes{l}
20+
21+
h := handler.Group("/ciracert")
22+
{
23+
h.GET("", r.getCIRACert)
24+
}
25+
}
26+
27+
func (r *ciraCertRoutes) getCIRACert(c *gin.Context) {
28+
// Read the root certificate file
29+
certData, err := os.ReadFile("config/root_cert.pem")
30+
if err != nil {
31+
r.l.Error(err, "http - CIRA cert - v1 - getCIRACert - failed to read certificate file")
32+
c.String(http.StatusInternalServerError, "Failed to read certificate file")
33+
return
34+
}
35+
36+
// Decode the PEM block
37+
block, _ := pem.Decode(certData)
38+
if block == nil {
39+
r.l.Error(nil, "http - CIRA cert - v1 - getCIRACert - failed to decode PEM")
40+
c.String(http.StatusInternalServerError, "Failed to decode certificate")
41+
return
42+
}
43+
44+
// Extract just the base64-encoded certificate data (strip PEM headers/footers)
45+
pemString := string(certData)
46+
lines := strings.Split(pemString, "\n")
47+
var certContent strings.Builder
48+
49+
for _, line := range lines {
50+
trimmed := strings.TrimSpace(line)
51+
// Skip empty lines and PEM headers/footers
52+
if trimmed == "" || strings.HasPrefix(trimmed, "-----") {
53+
continue
54+
}
55+
certContent.WriteString(trimmed)
56+
}
57+
58+
// Return as plain text
59+
c.String(http.StatusOK, certContent.String())
60+
}

0 commit comments

Comments
 (0)