-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcert.go
More file actions
126 lines (107 loc) · 3.39 KB
/
cert.go
File metadata and controls
126 lines (107 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package ngrokd
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
)
type certProvisioner struct {
store CertStore
apiClient *apiClient
endpointSelectors []string
}
func newCertProvisioner(store CertStore, apiClient *apiClient, endpointSelectors []string) *certProvisioner {
return &certProvisioner{
store: store,
apiClient: apiClient,
endpointSelectors: endpointSelectors,
}
}
func (p *certProvisioner) EnsureCertificate(ctx context.Context) (cert tls.Certificate, operatorID string, err error) {
// Check if certificate exists in store
exists, err := p.store.Exists(ctx)
if err != nil {
return tls.Certificate{}, "", fmt.Errorf("failed to check store: %w", err)
}
if exists {
keyPEM, certPEM, opID, err := p.store.Load(ctx)
if err == nil {
cert, err = tls.X509KeyPair(certPEM, keyPEM)
if err == nil {
return cert, opID, nil
}
}
// Fall through to provision if load failed
}
// Provision new certificate
return p.provisionCertificate(ctx)
}
func (p *certProvisioner) provisionCertificate(ctx context.Context) (tls.Certificate, string, error) {
// Validate store is writable before creating operator to prevent
// orphaned operators on permission errors during crash loops
if err := p.store.CanWrite(ctx); err != nil {
return tls.Certificate{}, "", fmt.Errorf("certificate store not writable: %w", err)
}
// Generate ECDSA P-384 private key
privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
if err != nil {
return tls.Certificate{}, "", fmt.Errorf("failed to generate key: %w", err)
}
privateKeyBytes, err := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return tls.Certificate{}, "", err
}
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: privateKeyBytes,
})
// Create CSR
template := x509.CertificateRequest{
Subject: pkix.Name{
Organization: []string{"ngrokd-sdk"},
},
SignatureAlgorithm: x509.ECDSAWithSHA384,
}
csrDER, err := x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
if err != nil {
return tls.Certificate{}, "", err
}
csrPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: csrDER,
})
// Register with ngrok API
operator, err := p.apiClient.CreateOperator(ctx, &operatorCreateRequest{
Description: "ngrokd-sdk",
Metadata: `{"type":"sdk"}`,
EnabledFeatures: []string{"bindings"},
Region: "global",
Binding: &operatorBindingCreate{
EndpointSelectors: p.endpointSelectors,
CSR: string(csrPEM),
},
})
if err != nil {
return tls.Certificate{}, "", fmt.Errorf("failed to register: %w", err)
}
if operator.Binding == nil || operator.Binding.Cert.Cert == "" {
return tls.Certificate{}, "", fmt.Errorf("no certificate in response")
}
certPEM := []byte(operator.Binding.Cert.Cert)
// Save to store - if this fails, clean up the operator we just created
if err := p.store.Save(ctx, privateKeyPEM, certPEM, operator.ID); err != nil {
// Best-effort cleanup to prevent orphaned operators
_ = p.apiClient.DeleteOperator(ctx, operator.ID)
return tls.Certificate{}, "", fmt.Errorf("failed to save certificate: %w", err)
}
cert, err := tls.X509KeyPair(certPEM, privateKeyPEM)
if err != nil {
return tls.Certificate{}, "", err
}
return cert, operator.ID, nil
}