Skip to content

Commit e8b562e

Browse files
Support for custom certificate upload using plain text (#36)
1 parent 5581a51 commit e8b562e

File tree

12 files changed

+298
-151
lines changed

12 files changed

+298
-151
lines changed

Makefile

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ SNAPSHOT_TAG_SUFFIX := $(if $(filter-out ,$(PR_ID)),$(if $(filter-out 0,$(PR_ID)
55

66
.prerequisites:
77
go work sync
8-
cd frontend/ && npm i
8+
cd frontend/ && npm ci
99

1010
.frontend-check:
1111
cd frontend/ && npm run check
@@ -85,6 +85,21 @@ format: .prerequisites
8585
go tool gofumpt -w .
8686
cd frontend/ && npx prettier --write .
8787

88+
update-dependencies:
89+
cd api && go get -u all
90+
cd application && go get -u all
91+
cd certificate/commons && go get -u all
92+
cd certificate/custom && go get -u all
93+
cd certificate/letsencrypt && go get -u all
94+
cd certificate/selfsigned && go get -u all
95+
cd core && go get -u all
96+
cd database && go get -u all
97+
cd integration/docker && go get -u all
98+
cd integration/truenas && go get -u all
99+
cd vpn/tailscale && go get -u all
100+
go work sync
101+
cd frontend && npm update
102+
88103
.build-prerequisites: .prerequisites .build-frontend .build-backend
89104

90105
build-release: .build-prerequisites .build-release-docker-image .build-distribution-files

api/go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ require (
3535
github.com/ugorji/go/codec v1.3.1 // indirect
3636
go.uber.org/mock v0.6.0 // indirect
3737
golang.org/x/arch v0.23.0 // indirect
38-
golang.org/x/crypto v0.43.0 // indirect
39-
golang.org/x/net v0.46.0 // indirect
38+
golang.org/x/crypto v0.44.0 // indirect
39+
golang.org/x/net v0.47.0 // indirect
4040
golang.org/x/sys v0.38.0 // indirect
41-
golang.org/x/text v0.30.0 // indirect
41+
golang.org/x/text v0.31.0 // indirect
4242
golang.org/x/time v0.14.0 // indirect
4343
google.golang.org/protobuf v1.36.10 // indirect
4444
)

certificate/custom/dynamic_fields.go

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,106 @@ package custom
22

33
import "dillmann.com.br/nginx-ignition/core/common/dynamicfields"
44

5+
const (
6+
textFieldUploadModeID = "textField"
7+
fileUploadModeID = "fileUpload"
8+
)
9+
510
var (
6-
publicKeyField = dynamicfields.DynamicField{
7-
ID: "publicKey",
11+
uploadModeField = dynamicfields.DynamicField{
12+
ID: "uploadMode",
813
Priority: 0,
9-
Description: "Certificate file (PEM encoded) with the public key",
14+
Description: "Upload mode",
1015
Required: true,
1116
Sensitive: true,
12-
Type: dynamicfields.FileType,
17+
Type: dynamicfields.EnumType,
18+
EnumOptions: &[]*dynamicfields.EnumOption{
19+
{
20+
ID: textFieldUploadModeID,
21+
Description: "PEM-encoded text",
22+
},
23+
{
24+
ID: fileUploadModeID,
25+
Description: "PEM-encoded file",
26+
},
27+
},
28+
}
29+
30+
publicKeyTextField = dynamicfields.DynamicField{
31+
ID: "publicKeyPem",
32+
Priority: 1,
33+
Description: "Public key",
34+
Required: true,
35+
Sensitive: true,
36+
Type: dynamicfields.MultiLineTextType,
37+
Condition: &dynamicfields.Condition{
38+
ParentField: uploadModeField.ID,
39+
Value: textFieldUploadModeID,
40+
},
41+
}
42+
43+
privateKeyTextField = dynamicfields.DynamicField{
44+
ID: "privateKeyPem",
45+
Priority: 2,
46+
Description: "Private key",
47+
Required: true,
48+
Sensitive: true,
49+
Type: dynamicfields.MultiLineTextType,
50+
Condition: &dynamicfields.Condition{
51+
ParentField: uploadModeField.ID,
52+
Value: textFieldUploadModeID,
53+
},
54+
}
55+
56+
certificationChainTextField = dynamicfields.DynamicField{
57+
ID: "certificationChainPem",
58+
Priority: 3,
59+
Description: "Certification chain",
60+
Required: false,
61+
Sensitive: true,
62+
Type: dynamicfields.MultiLineTextType,
63+
Condition: &dynamicfields.Condition{
64+
ParentField: uploadModeField.ID,
65+
Value: textFieldUploadModeID,
66+
},
1367
}
1468

15-
privateKeyField = dynamicfields.DynamicField{
16-
ID: "privateKey",
69+
publicKeyFileField = dynamicfields.DynamicField{
70+
ID: "publicKeyFile",
1771
Priority: 1,
18-
Description: "Certificate file (PEM encoded) with the private key",
72+
Description: "Public key",
1973
Required: true,
2074
Sensitive: true,
2175
Type: dynamicfields.FileType,
76+
Condition: &dynamicfields.Condition{
77+
ParentField: uploadModeField.ID,
78+
Value: fileUploadModeID,
79+
},
2280
}
2381

24-
certificationChainField = dynamicfields.DynamicField{
25-
ID: "certificationChain",
82+
privateKeyFileField = dynamicfields.DynamicField{
83+
ID: "privateKeyFile",
2684
Priority: 2,
27-
Description: "Certification chain file (PEM encoded)",
85+
Description: "Private key",
86+
Required: true,
87+
Sensitive: true,
88+
Type: dynamicfields.FileType,
89+
Condition: &dynamicfields.Condition{
90+
ParentField: uploadModeField.ID,
91+
Value: fileUploadModeID,
92+
},
93+
}
94+
95+
certificationChainFileField = dynamicfields.DynamicField{
96+
ID: "certificationChainFile",
97+
Priority: 3,
98+
Description: "Certification chain",
2899
Required: false,
29100
Sensitive: true,
30101
Type: dynamicfields.FileType,
102+
Condition: &dynamicfields.Condition{
103+
ParentField: uploadModeField.ID,
104+
Value: fileUploadModeID,
105+
},
31106
}
32107
)

certificate/custom/provider.go

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ func (p *Provider) Name() string {
3232

3333
func (p *Provider) DynamicFields() []*dynamicfields.DynamicField {
3434
return []*dynamicfields.DynamicField{
35-
&publicKeyField,
36-
&privateKeyField,
37-
&certificationChainField,
35+
&uploadModeField,
36+
&publicKeyTextField,
37+
&privateKeyTextField,
38+
&certificationChainTextField,
39+
&publicKeyFileField,
40+
&privateKeyFileField,
41+
&certificationChainFileField,
3842
}
3943
}
4044

@@ -48,24 +52,34 @@ func (p *Provider) Issue(_ context.Context, request *certificate.IssueRequest) (
4852
}
4953

5054
params := request.Parameters
51-
privateKeyStr, _ := params[privateKeyField.ID].(string)
52-
publicKeyStr, _ := request.Parameters[publicKeyField.ID].(string)
53-
54-
chainStr, chainPresent := request.Parameters[certificationChainField.ID].(string)
55+
fileUploadMode := params[uploadModeField.ID] == fileUploadModeID
56+
57+
var privateKeyStr, publicKeyStr, chainStr string
58+
var chainPresent bool
59+
60+
if fileUploadMode {
61+
privateKeyStr, _ = params[privateKeyFileField.ID].(string)
62+
publicKeyStr, _ = params[publicKeyFileField.ID].(string)
63+
chainStr, chainPresent = params[certificationChainFileField.ID].(string)
64+
} else {
65+
privateKeyStr, _ = params[privateKeyTextField.ID].(string)
66+
publicKeyStr, _ = params[publicKeyTextField.ID].(string)
67+
chainStr, chainPresent = params[certificationChainTextField.ID].(string)
68+
}
5569

56-
privateKey, err := parsePrivateKey(privateKeyStr)
70+
privateKey, err := parsePrivateKey(privateKeyStr, fileUploadMode)
5771
if err != nil {
5872
return nil, coreerror.New("Invalid private key", true)
5973
}
6074

61-
publicKey, err := parseCertificate(publicKeyStr)
75+
publicKey, err := parseCertificate(publicKeyStr, fileUploadMode)
6276
if err != nil {
6377
return nil, coreerror.New("Invalid public key", true)
6478
}
6579

6680
var chain []*x509.Certificate
6781
if chainPresent && chainStr != "" {
68-
chain, err = parseCertificateChain(chainStr)
82+
chain, err = parseCertificateChain(chainStr, fileUploadMode)
6983
if err != nil {
7084
return nil, coreerror.New("Invalid certification chain", true)
7185
}
@@ -91,8 +105,8 @@ func (p *Provider) Renew(_ context.Context, cert *certificate.Certificate) (*cer
91105
return cert, nil
92106
}
93107

94-
func parsePrivateKey(key string) ([]byte, error) {
95-
decodedKey, err := base64.StdEncoding.DecodeString(key)
108+
func parsePrivateKey(key string, base64Encoded bool) ([]byte, error) {
109+
decodedKey, err := stringToByteArray(key, base64Encoded)
96110
if err != nil {
97111
return nil, coreerror.New("Failed to decode key", true)
98112
}
@@ -105,8 +119,8 @@ func parsePrivateKey(key string) ([]byte, error) {
105119
return block.Bytes, nil
106120
}
107121

108-
func parseCertificate(cert string) (*x509.Certificate, error) {
109-
decodedCert, err := base64.StdEncoding.DecodeString(cert)
122+
func parseCertificate(cert string, base64Encoded bool) (*x509.Certificate, error) {
123+
decodedCert, err := stringToByteArray(cert, base64Encoded)
110124
if err != nil {
111125
return nil, coreerror.New("Failed to decode certificate", true)
112126
}
@@ -119,8 +133,8 @@ func parseCertificate(cert string) (*x509.Certificate, error) {
119133
return x509.ParseCertificate(block.Bytes)
120134
}
121135

122-
func parseCertificateChain(chain string) ([]*x509.Certificate, error) {
123-
decodedChain, err := base64.StdEncoding.DecodeString(chain)
136+
func parseCertificateChain(chain string, base64Encoded bool) ([]*x509.Certificate, error) {
137+
decodedChain, err := stringToByteArray(chain, base64Encoded)
124138
if err != nil {
125139
return nil, coreerror.New("Failed to decode chain", true)
126140
}
@@ -132,7 +146,7 @@ func parseCertificateChain(chain string) ([]*x509.Certificate, error) {
132146
}
133147

134148
cert += "-----END CERTIFICATE-----"
135-
parsedCert, err := parseCertificate(cert)
149+
parsedCert, err := parseCertificate(cert, false)
136150
if err != nil {
137151
return nil, err
138152
}
@@ -151,3 +165,11 @@ func encodeChain(chain []*x509.Certificate) []string {
151165

152166
return encodedChain
153167
}
168+
169+
func stringToByteArray(value string, base64Encoded bool) ([]byte, error) {
170+
if base64Encoded {
171+
return base64.StdEncoding.DecodeString(value)
172+
}
173+
174+
return []byte(value), nil
175+
}

core/host/validator.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ func (v *validator) validateExecuteCodeRoute(route *Route, index int) {
329329
}
330330

331331
func (v *validator) validateVPNs(ctx context.Context, host *Host) error {
332+
vpnNameUsage := make(map[uuid.UUID]map[string]int)
333+
332334
for index, value := range host.VPNs {
333335
basePath := "vpns[" + strconv.Itoa(index) + "]"
334336
vpnIdPath := basePath + ".vpnId"
@@ -343,6 +345,16 @@ func (v *validator) validateVPNs(ctx context.Context, host *Host) error {
343345
continue
344346
}
345347

348+
if vpnNameUsage[value.VPNID] == nil {
349+
vpnNameUsage[value.VPNID] = make(map[string]int)
350+
}
351+
352+
if vpnNameUsage[value.VPNID][value.Name] > 0 {
353+
v.delegate.Add(namePath, "Name was already used before")
354+
}
355+
356+
vpnNameUsage[value.VPNID][value.Name]++
357+
346358
vpnData, err := v.vpnCommands.Get(ctx, value.VPNID)
347359
if err != nil {
348360
return err
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
alter table host_vpn drop constraint pk_host_vpn;
2+
alter table host_vpn add constraint pk_host_vpn primary key (host_id, vpn_id, name);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
drop index idx_host_vpn_host_id;
2+
drop index idx_host_vpn_vpn_id;
3+
alter table host_vpn rename to host_vpn_old;
4+
5+
create table host_vpn (
6+
host_id uuid not null,
7+
vpn_id uuid not null,
8+
name varchar(256) not null,
9+
host varchar(256),
10+
constraint pk_host_vpn primary key (host_id, vpn_id, name),
11+
constraint fk_host_vpn_host foreign key (host_id) references host (id),
12+
constraint fk_host_vpn_vpn foreign key (vpn_id) references vpn (id)
13+
);
14+
15+
create index idx_host_vpn_host_id on host_vpn (host_id);
16+
create index idx_host_vpn_vpn_id on host_vpn (vpn_id);
17+
18+
insert into host_vpn (host_id, vpn_id, name, host)
19+
select host_id, vpn_id, name, host from host_vpn_old;
20+
21+
drop table host_vpn_old;

database/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ require (
3737
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
3838
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
3939
go.opentelemetry.io/otel/trace v1.38.0 // indirect
40-
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
40+
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
4141
golang.org/x/sync v0.18.0 // indirect
4242
golang.org/x/sys v0.38.0 // indirect
43-
modernc.org/libc v1.66.10 // indirect
43+
modernc.org/libc v1.67.0 // indirect
4444
modernc.org/mathutil v1.7.1 // indirect
4545
modernc.org/memory v1.11.0 // indirect
4646
)

0 commit comments

Comments
 (0)