Skip to content

Commit 79defb0

Browse files
committed
Add support for IPv6 addresses
1 parent 7c7bf8f commit 79defb0

File tree

3 files changed

+223
-17
lines changed

3 files changed

+223
-17
lines changed

client.go

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,27 @@ type ConfigNetwork struct {
102102
}
103103

104104
type ConfigHost struct {
105-
ID string
106-
Name string
107-
IPAddress string
105+
ID string
106+
Name string
107+
IPAddresses []string
108108
}
109109

110110
type ConfigEndpointOIDC struct {
111111
Email string
112112
ExpiresAt *time.Time
113113
}
114114

115+
// mergeIPAddresses returns the plural field if populated, otherwise wraps the singular value.
116+
func mergeIPAddresses(plural []string, singular string) []string {
117+
if len(plural) > 0 {
118+
return plural
119+
}
120+
if singular != "" {
121+
return []string{singular}
122+
}
123+
return nil
124+
}
125+
115126
// Enroll issues an enrollment request against the REST API using the given enrollment code, passing along a locally
116127
// generated DH X25519 public key to be signed by the CA, and an Ed 25519 public key for future API call authentication.
117128
// On success it returns the Nebula config generated by the server, a Nebula private key PEM to be inserted into the
@@ -178,9 +189,9 @@ func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code str
178189
Name: r.Network.Name,
179190
},
180191
Host: ConfigHost{
181-
ID: r.HostID,
182-
Name: r.Host.Name,
183-
IPAddress: r.Host.IPAddress,
192+
ID: r.HostID,
193+
Name: r.Host.Name,
194+
IPAddresses: mergeIPAddresses(r.Host.IPAddresses, r.Host.IPAddress),
184195
},
185196
}
186197

@@ -352,9 +363,9 @@ func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte,
352363
Name: result.Network.Name,
353364
},
354365
Host: ConfigHost{
355-
ID: result.Host.ID,
356-
Name: result.Host.Name,
357-
IPAddress: result.Host.IPAddress,
366+
ID: result.Host.ID,
367+
Name: result.Host.Name,
368+
IPAddresses: mergeIPAddresses(result.Host.IPAddresses, result.Host.IPAddress),
358369
},
359370
}
360371

@@ -460,9 +471,9 @@ func (c *Client) DoConfigUpdate(ctx context.Context, creds keys.Credentials) ([]
460471
Name: result.Network.Name,
461472
},
462473
Host: ConfigHost{
463-
ID: result.Host.ID,
464-
Name: result.Host.Name,
465-
IPAddress: result.Host.IPAddress,
474+
ID: result.Host.ID,
475+
Name: result.Host.Name,
476+
IPAddresses: mergeIPAddresses(result.Host.IPAddresses, result.Host.IPAddress),
466477
},
467478
}
468479

client_test.go

Lines changed: 195 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ func TestEnroll(t *testing.T) {
143143
assert.Equal(t, netName, meta.Network.Name)
144144
assert.Equal(t, hostID, meta.Host.ID)
145145
assert.Equal(t, hostName, meta.Host.Name)
146-
assert.Equal(t, hostIP, meta.Host.IPAddress)
146+
assert.Equal(t, []string{hostIP}, meta.Host.IPAddresses)
147147
assert.Equal(t, oidcEmail, meta.EndpointOIDC.Email)
148148
assert.WithinDuration(t, oidcExpiresAt, *meta.EndpointOIDC.ExpiresAt, 1*time.Second)
149149

@@ -438,7 +438,7 @@ func TestDoUpdate(t *testing.T) {
438438
assert.Equal(t, netName, meta.Network.Name)
439439
assert.Equal(t, hostID, meta.Host.ID)
440440
assert.Equal(t, hostName, meta.Host.Name)
441-
assert.Equal(t, hostIP, meta.Host.IPAddress)
441+
assert.Equal(t, []string{hostIP}, meta.Host.IPAddresses)
442442
assert.Equal(t, oidcEmail, meta.EndpointOIDC.Email)
443443
assert.Nil(t, meta.EndpointOIDC.ExpiresAt)
444444

@@ -1206,6 +1206,199 @@ func TestDownloads(t *testing.T) {
12061206
assert.Equal(t, "0.5.1", resp.VersionInfo.Latest.Mobile)
12071207
}
12081208

1209+
func TestEnroll_PluralMeta(t *testing.T) {
1210+
t.Parallel()
1211+
1212+
useragent := "testClient"
1213+
ts := dnapitest.NewServer(useragent)
1214+
client := NewClient(useragent, ts.URL)
1215+
t.Cleanup(func() { ts.Close() })
1216+
1217+
code := "abcdef"
1218+
orgID := "foobaz"
1219+
orgName := "foobar's foo org"
1220+
netID := "qux"
1221+
netName := "the best network"
1222+
netCIDRs := []string{"192.168.100.0/24", "10.0.0.0/16"}
1223+
hostID := "foobar"
1224+
hostName := "foo host"
1225+
hostIPs := []string{"192.168.100.1", "10.0.0.1"}
1226+
counter := uint(5)
1227+
ca, _ := dnapitest.NebulaCACert()
1228+
caPEM, err := ca.MarshalPEM()
1229+
require.NoError(t, err)
1230+
1231+
ts.ExpectEnrollment(code, message.NetworkCurve25519, func(req message.EnrollRequest) []byte {
1232+
cfg, err := yaml.Marshal(m{
1233+
"pki": m{"ca": string(caPEM)},
1234+
})
1235+
if err != nil {
1236+
return jsonMarshal(message.APIResponse[message.EnrollResponseData]{
1237+
Errors: message.APIResponseErrors{{
1238+
Code: "ERR_FAILED_TO_MARSHAL_YAML",
1239+
Message: "failed to marshal test response config",
1240+
}},
1241+
})
1242+
}
1243+
1244+
return jsonMarshal(message.APIResponse[message.EnrollResponseData]{
1245+
Data: message.EnrollResponseData{
1246+
HostID: hostID,
1247+
Counter: counter,
1248+
Config: cfg,
1249+
TrustedKeys: ca.MarshalPublicKeyPEM(),
1250+
Organization: message.HostOrgMetadata{
1251+
ID: orgID,
1252+
Name: orgName,
1253+
},
1254+
Network: message.HostNetworkMetadata{
1255+
ID: netID,
1256+
Name: netName,
1257+
Curve: message.NetworkCurve25519,
1258+
CIDRs: netCIDRs,
1259+
},
1260+
Host: message.HostHostMetadata{
1261+
ID: hostID,
1262+
Name: hostName,
1263+
IPAddresses: hostIPs,
1264+
},
1265+
},
1266+
})
1267+
})
1268+
1269+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
1270+
defer cancel()
1271+
_, _, _, meta, err := client.Enroll(ctx, testutil.NewTestLogger(), code)
1272+
require.NoError(t, err)
1273+
assert.Empty(t, ts.Errors())
1274+
assert.Equal(t, 0, ts.RequestsRemaining())
1275+
1276+
// test meta
1277+
assert.Equal(t, orgID, meta.Org.ID)
1278+
assert.Equal(t, orgName, meta.Org.Name)
1279+
assert.Equal(t, netID, meta.Network.ID)
1280+
assert.Equal(t, netName, meta.Network.Name)
1281+
assert.Equal(t, hostID, meta.Host.ID)
1282+
assert.Equal(t, hostName, meta.Host.Name)
1283+
assert.Equal(t, hostIPs, meta.Host.IPAddresses)
1284+
}
1285+
1286+
func TestDoUpdate_PluralMeta(t *testing.T) {
1287+
t.Parallel()
1288+
1289+
useragent := "testClient"
1290+
ts := dnapitest.NewServer(useragent)
1291+
t.Cleanup(func() { ts.Close() })
1292+
1293+
ca, caPrivkey := dnapitest.NebulaCACert()
1294+
caPEM, err := ca.MarshalPEM()
1295+
require.NoError(t, err)
1296+
1297+
c := NewClient(useragent, ts.URL)
1298+
1299+
code := "foobar"
1300+
ts.ExpectEnrollment(code, message.NetworkCurve25519, func(req message.EnrollRequest) []byte {
1301+
cfg, err := yaml.Marshal(m{
1302+
"pki": m{"ca": string(caPEM)},
1303+
})
1304+
if err != nil {
1305+
return jsonMarshal(message.APIResponse[message.EnrollResponseData]{
1306+
Errors: message.APIResponseErrors{{
1307+
Code: "ERR_FAILED_TO_MARSHAL_YAML",
1308+
Message: "failed to marshal test response config",
1309+
}},
1310+
})
1311+
}
1312+
1313+
return jsonMarshal(message.APIResponse[message.EnrollResponseData]{
1314+
Data: message.EnrollResponseData{
1315+
HostID: "foobar",
1316+
Counter: 1,
1317+
Config: cfg,
1318+
TrustedKeys: ca.MarshalPublicKeyPEM(),
1319+
Organization: message.HostOrgMetadata{
1320+
ID: "foobaz",
1321+
Name: "foobar's foo org",
1322+
},
1323+
Network: message.HostNetworkMetadata{
1324+
ID: "qux",
1325+
Name: "the best network",
1326+
Curve: message.NetworkCurve25519,
1327+
CIDR: "192.168.100.0/24",
1328+
},
1329+
Host: message.HostHostMetadata{
1330+
ID: "foobar",
1331+
Name: "foo host",
1332+
IPAddress: "192.168.100.2",
1333+
},
1334+
},
1335+
})
1336+
})
1337+
1338+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
1339+
defer cancel()
1340+
_, _, creds, _, err := c.Enroll(ctx, testutil.NewTestLogger(), code)
1341+
require.NoError(t, err)
1342+
1343+
orgID := "foobaz"
1344+
orgName := "foobar's foo org"
1345+
netID := "qux"
1346+
netName := "the best network"
1347+
netCIDRs := []string{"192.168.100.0/24", "10.0.0.0/16"}
1348+
hostID := "foobar"
1349+
hostName := "foo host"
1350+
hostIPs := []string{"192.168.100.1", "10.0.0.1"}
1351+
1352+
ts.ExpectDNClientRequest(message.DoUpdate, http.StatusOK, func(r message.RequestWrapper) []byte {
1353+
newConfigResponse := message.DoUpdateResponse{
1354+
Config: dnapitest.NebulaCfg(caPEM),
1355+
Counter: 3,
1356+
Nonce: dnapitest.GetNonce(r),
1357+
TrustedKeys: ca.MarshalPublicKeyPEM(),
1358+
Organization: message.HostOrgMetadata{
1359+
ID: orgID,
1360+
Name: orgName,
1361+
},
1362+
Network: message.HostNetworkMetadata{
1363+
ID: netID,
1364+
Name: netName,
1365+
Curve: message.NetworkCurve25519,
1366+
CIDRs: netCIDRs,
1367+
},
1368+
Host: message.HostHostMetadata{
1369+
ID: hostID,
1370+
Name: hostName,
1371+
IPAddresses: hostIPs,
1372+
},
1373+
}
1374+
rawRes := jsonMarshal(newConfigResponse)
1375+
1376+
return jsonMarshal(message.SignedResponseWrapper{
1377+
Data: message.SignedResponse{
1378+
Version: 1,
1379+
Message: rawRes,
1380+
Signature: ed25519.Sign(caPrivkey, rawRes),
1381+
},
1382+
})
1383+
})
1384+
1385+
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
1386+
defer cancel()
1387+
_, _, _, meta, err := c.DoUpdate(ctx, *creds)
1388+
require.NoError(t, err)
1389+
assert.Empty(t, ts.Errors())
1390+
assert.Equal(t, 0, ts.RequestsRemaining())
1391+
1392+
// test meta
1393+
assert.Equal(t, orgID, meta.Org.ID)
1394+
assert.Equal(t, orgName, meta.Org.Name)
1395+
assert.Equal(t, netID, meta.Network.ID)
1396+
assert.Equal(t, netName, meta.Network.Name)
1397+
assert.Equal(t, hostID, meta.Host.ID)
1398+
assert.Equal(t, hostName, meta.Host.Name)
1399+
assert.Equal(t, hostIPs, meta.Host.IPAddresses)
1400+
}
1401+
12091402
func TestNebulaPemBanners(t *testing.T) {
12101403
const NebulaECDSAP256PublicKeyBanner = "NEBULA ECDSA P256 PUBLIC KEY"
12111404
const NebulaEd25519PublicKeyBanner = "NEBULA ED25519 PUBLIC KEY"

message/message.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,13 +228,15 @@ type HostNetworkMetadata struct {
228228
Name string `json:"name"`
229229
Curve NetworkCurve `json:"curve"`
230230
CIDR string `json:"cidr"`
231+
CIDRs []string `json:"cidrs"`
231232
}
232233

233234
// HostHostMetadata is included in EnrollResponseData.
234235
type HostHostMetadata struct {
235-
ID string `json:"id"`
236-
Name string `json:"name"`
237-
IPAddress string `json:"ipAddress"`
236+
ID string `json:"id"`
237+
Name string `json:"name"`
238+
IPAddress string `json:"ipAddress"`
239+
IPAddresses []string `json:"ipAddresses"`
238240
}
239241

240242
// HostEndpointOIDCMetadata is included in EnrollResponseData.

0 commit comments

Comments
 (0)