Skip to content

Commit a0f43dc

Browse files
Improvement of the NetBird integration (#97)
1 parent a18830d commit a0f43dc

22 files changed

Lines changed: 1395 additions & 27 deletions

core/host/validator.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,15 @@ func (v *validator) validateVPNCertificate(
442442
index int,
443443
vpnDrivers []vpn.AvailableDriver,
444444
) error {
445+
fieldPath := fmt.Sprintf("vpns[%d].certificateId", index)
446+
if hostVpn.CertificateID != nil && !hostVpn.EnableHTTPS {
447+
v.delegate.Add(
448+
fieldPath,
449+
i18n.M(ctx, i18n.K.CoreHostVpnCertificateCannotBeInformedIfDisabled),
450+
)
451+
return nil
452+
}
453+
445454
if !hostVpn.EnableHTTPS {
446455
return nil
447456
}
@@ -451,7 +460,6 @@ func (v *validator) validateVPNCertificate(
451460
return nil
452461
}
453462

454-
fieldPath := fmt.Sprintf("vpns[%d].certificateId", index)
455463
if err := v.validateManagedSSLCertificate(ctx, driver, hostVpn, fieldPath); err != nil {
456464
return err
457465
}

core/host/validator_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,33 @@ func Test_validator(t *testing.T) {
437437
})
438438

439439
t.Run("vpn certificate validation", func(t *testing.T) {
440+
t.Run("certificate informed but https disabled", func(t *testing.T) {
441+
hostValidator, mocks := setupValidator(t)
442+
h := newHost()
443+
vpnID := uuid.New()
444+
certID := uuid.New()
445+
h.VPNs = []VPN{
446+
{VPNID: vpnID, Name: "vpn1", EnableHTTPS: false, CertificateID: &certID},
447+
}
448+
449+
mocks.binding.EXPECT().
450+
Validate(t.Context(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
451+
Return(nil).AnyTimes()
452+
mocks.vpn.EXPECT().
453+
Get(t.Context(), vpnID).
454+
Return(&vpn.VPN{Enabled: true, Driver: "driver1"}, nil)
455+
mocks.vpn.EXPECT().
456+
GetAvailableDrivers(t.Context()).
457+
Return(nil, nil).AnyTimes()
458+
459+
err := hostValidator.validate(t.Context(), h)
460+
assertViolations(
461+
t,
462+
err,
463+
i18n.K.CoreHostVpnCertificateCannotBeInformedIfDisabled,
464+
)
465+
})
466+
440467
t.Run("driver managed - certificate required", func(t *testing.T) {
441468
hostValidator, mocks := setupValidator(t)
442469
h := newHost()

core/nginx/vpn_manager.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package nginx
33
import (
44
"context"
55
"fmt"
6+
"strconv"
67

78
"github.com/google/uuid"
89

@@ -224,7 +225,12 @@ func (a *endpointAdapter) Hash() string {
224225
domainNameStr = *a.domainName
225226
}
226227

227-
return a.vpnID.String() + a.name + domainNameStr
228+
var certIDStr string
229+
if a.certDetails != nil {
230+
certIDStr = a.certDetails.ID.String()
231+
}
232+
233+
return a.vpnID.String() + a.name + domainNameStr + strconv.FormatBool(a.enableHTTPS) + certIDStr
228234
}
229235

230236
func (a *endpointAdapter) VPNID() uuid.UUID {

core/nginx/vpn_manager_test.go

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,77 @@ func Test_endpointAdapter(t *testing.T) {
2222

2323
t.Run("generates consistent hash", func(t *testing.T) {
2424
adapter := &endpointAdapter{
25-
vpnID: id,
26-
name: name,
27-
domainName: &domain,
25+
vpnID: id,
26+
name: name,
27+
domainName: &domain,
28+
enableHTTPS: false,
2829
}
29-
assert.Equal(t, id.String()+name+domain, adapter.Hash())
30+
assert.Equal(t, id.String()+name+domain+"false", adapter.Hash())
3031
})
3132

3233
t.Run("handles nil domain", func(t *testing.T) {
3334
adapter := &endpointAdapter{
34-
vpnID: id,
35-
name: name,
36-
domainName: nil,
35+
vpnID: id,
36+
name: name,
37+
domainName: nil,
38+
enableHTTPS: false,
39+
}
40+
assert.Equal(t, id.String()+name+"false", adapter.Hash())
41+
})
42+
43+
t.Run("generates different hash when enableHTTPS changes", func(t *testing.T) {
44+
adapterHTTPSOn := &endpointAdapter{
45+
vpnID: id,
46+
name: name,
47+
domainName: &domain,
48+
enableHTTPS: true,
49+
}
50+
adapterHTTPSOff := &endpointAdapter{
51+
vpnID: id,
52+
name: name,
53+
domainName: &domain,
54+
enableHTTPS: false,
55+
}
56+
assert.NotEqual(t, adapterHTTPSOn.Hash(), adapterHTTPSOff.Hash())
57+
})
58+
59+
t.Run("generates different hash when certificate changes", func(t *testing.T) {
60+
certID1 := uuid.New()
61+
certID2 := uuid.New()
62+
adapterCert1 := &endpointAdapter{
63+
vpnID: id,
64+
name: name,
65+
domainName: &domain,
66+
enableHTTPS: true,
67+
certDetails: &certificate.Certificate{ID: certID1},
68+
}
69+
adapterCert2 := &endpointAdapter{
70+
vpnID: id,
71+
name: name,
72+
domainName: &domain,
73+
enableHTTPS: true,
74+
certDetails: &certificate.Certificate{ID: certID2},
75+
}
76+
assert.NotEqual(t, adapterCert1.Hash(), adapterCert2.Hash())
77+
})
78+
79+
t.Run("generates same hash when nothing changes", func(t *testing.T) {
80+
certID := uuid.New()
81+
adapter1 := &endpointAdapter{
82+
vpnID: id,
83+
name: name,
84+
domainName: &domain,
85+
enableHTTPS: true,
86+
certDetails: &certificate.Certificate{ID: certID},
87+
}
88+
adapter2 := &endpointAdapter{
89+
vpnID: id,
90+
name: name,
91+
domainName: &domain,
92+
enableHTTPS: true,
93+
certDetails: &certificate.Certificate{ID: certID},
3794
}
38-
assert.Equal(t, id.String()+name, adapter.Hash())
95+
assert.Equal(t, adapter1.Hash(), adapter2.Hash())
3996
})
4097
})
4198

frontend/src/domain/host/HostConverter.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,14 @@ class HostConverter {
107107

108108
private async vpnToFormValues(data: HostVpn): Promise<HostFormVpn> {
109109
const vpn = await this.vpnService.getById(data.vpnId!!)
110+
const certificate = this.notNull(data.certificateId)
111+
? await this.certificateService.getById(data.certificateId!!)
112+
: undefined
110113

111114
return {
112115
...data,
113116
vpn: vpn!!,
117+
certificate,
114118
}
115119
}
116120

@@ -195,6 +199,8 @@ class HostConverter {
195199
vpnId: vpn.vpn?.id,
196200
name: vpn.name,
197201
host: vpn.host,
202+
enableHttps: vpn.enableHttps ?? false,
203+
certificateId: vpn.certificate?.id,
198204
}
199205

200206
if (!vpn.host || vpn.host.trim() === "") {

frontend/src/domain/host/HostFormPage.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import HostRoutes from "./components/HostRoutes"
1616
import HostBindings from "./components/HostBindings"
1717
import "./HostFormPage.css"
1818
import ReloadNginxAction from "../nginx/actions/ReloadNginxAction"
19-
import HostFormValues from "./model/HostFormValues"
19+
import HostFormValues, { HostFormVpn } from "./model/HostFormValues"
2020
import HostConverter from "./HostConverter"
2121
import If from "../../core/components/flowcontrol/If"
2222
import CommonNotifications from "../../core/components/notification/CommonNotifications"
@@ -201,6 +201,16 @@ export default class HostFormPage extends React.Component<any, HostFormPageState
201201
})
202202
}
203203

204+
private handleVpnsChange(vpns: HostFormVpn[]) {
205+
const { formValues } = this.state
206+
this.setState(
207+
{
208+
formValues: { ...formValues, vpns },
209+
},
210+
() => this.formRef.current?.setFieldsValue({ vpns }),
211+
)
212+
}
213+
204214
private fetchAccessLists(
205215
pageSize: number,
206216
pageNumber: number,
@@ -424,7 +434,7 @@ export default class HostFormPage extends React.Component<any, HostFormPageState
424434
<HostVpns
425435
vpns={formValues.vpns}
426436
validationResult={validationResult}
427-
onChange={() => this.formRef.current?.setFieldsValue({ vpns: formValues.vpns })}
437+
onChange={vpns => this.handleVpnsChange(vpns)}
428438
/>
429439
</Form>
430440
)

frontend/src/domain/host/components/HostVpns.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface HostVpnsProps {
2222
vpns: HostFormVpn[]
2323
validationResult: ValidationResult
2424
className?: string
25-
onChange: () => void
25+
onChange: (vpns: HostFormVpn[]) => void
2626
}
2727

2828
interface HostVpnsState {
@@ -41,20 +41,32 @@ export default class HostVpns extends React.Component<HostVpnsProps, HostVpnsSta
4141
}
4242

4343
private handleVpnChange(index: number, vpn?: VpnResponse) {
44+
if (!vpn) return
45+
4446
const { vpns, onChange } = this.props
45-
if (vpn) {
46-
vpns[index].vpn = vpn
47-
vpns[index].enableHttps = vpn.driverEndpointSslSupport === EndpointSSLSupport.PROVIDER_MANAGED
48-
vpns[index].certificate = undefined
49-
onChange()
47+
const updatedVpns = [...vpns]
48+
49+
updatedVpns[index] = {
50+
...vpns[index],
51+
vpn,
52+
enableHttps: vpn.driverEndpointSslSupport === EndpointSSLSupport.PROVIDER_MANAGED,
53+
certificate: undefined,
5054
}
55+
56+
onChange(updatedVpns)
5157
}
5258

5359
private handleCertificateChange(index: number, certificate?: CertificateResponse) {
5460
const { vpns, onChange } = this.props
55-
vpns[index].certificate = certificate
56-
vpns[index].enableHttps = certificate !== undefined
57-
onChange()
61+
const updatedVpns = [...vpns]
62+
63+
updatedVpns[index] = {
64+
...vpns[index],
65+
certificate,
66+
enableHttps: certificate !== undefined,
67+
}
68+
69+
onChange(updatedVpns)
5870
}
5971

6072
private openVpnSettingsModal(index: number) {

frontend/src/domain/host/model/HostRequest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export interface HostVpn {
7676
vpnId: string
7777
name: string
7878
host?: string
79+
enableHttps: boolean
80+
certificateId?: string
7981
}
8082

8183
export default interface HostRequest {

0 commit comments

Comments
 (0)