diff --git a/client.go b/client.go index 2bc6a23..ecf98ff 100644 --- a/client.go +++ b/client.go @@ -83,9 +83,10 @@ var ErrInvalidCredentials = fmt.Errorf("invalid credentials") var ErrInvalidCode = fmt.Errorf("invalid enrollment code") type ConfigMeta struct { - Org ConfigOrg - Network ConfigNetwork - Host ConfigHost + Org ConfigOrg + Network ConfigNetwork + Host ConfigHost + EndpointOIDC *ConfigEndpointOIDC } type ConfigOrg struct { @@ -104,6 +105,10 @@ type ConfigHost struct { IPAddress string } +type ConfigEndpointOIDC struct { + Email string +} + // Enroll issues an enrollment request against the REST API using the given enrollment code, passing along a locally // generated DH X25519 public key to be signed by the CA, and an Ed 25519 public key for future API call authentication. // On success it returns the Nebula config generated by the server, a Nebula private key PEM to be inserted into the @@ -204,6 +209,12 @@ func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code str }, } + if r.Data.EndpointOIDCMeta != nil { + meta.EndpointOIDC = &ConfigEndpointOIDC{ + Email: r.Data.EndpointOIDCMeta.Email, + } + } + // Determine the private keys to save based on the network curve type var privkeyPEM []byte var privkey keys.PrivateKey @@ -382,6 +393,12 @@ func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte, }, } + if result.EndpointOIDCMeta != nil { + meta.EndpointOIDC = &ConfigEndpointOIDC{ + Email: result.EndpointOIDCMeta.Email, + } + } + return result.Config, nebulaPrivkeyPEM, newCreds, meta, nil } diff --git a/client_test.go b/client_test.go index 9d6b58a..2ed70e4 100644 --- a/client_test.go +++ b/client_test.go @@ -50,6 +50,7 @@ func TestEnroll(t *testing.T) { hostID := "foobar" hostName := "foo host" hostIP := "192.168.100.1" + oidcEmail := "demo@defined.net" counter := uint(5) ca, _ := dnapitest.NebulaCACert() caPEM, err := ca.MarshalToPEM() @@ -92,6 +93,9 @@ func TestEnroll(t *testing.T) { Name: hostName, IPAddress: hostIP, }, + EndpointOIDCMeta: &message.HostEndpointOIDCMetadata{ + Email: oidcEmail, + }, }, }) }) @@ -139,6 +143,7 @@ func TestEnroll(t *testing.T) { assert.Equal(t, hostID, meta.Host.ID) assert.Equal(t, hostName, meta.Host.Name) assert.Equal(t, hostIP, meta.Host.IPAddress) + assert.Equal(t, oidcEmail, meta.EndpointOIDC.Email) // Test error handling errorMsg := "invalid enrollment code" @@ -377,6 +382,7 @@ func TestDoUpdate(t *testing.T) { hostID := "foobar" hostName := "foo host" hostIP := "192.168.100.1" + oidcEmail := "demo@defined.net" // This time sign the response with the correct CA key. ts.ExpectDNClientRequest(message.DoUpdate, http.StatusOK, func(r message.RequestWrapper) []byte { @@ -400,6 +406,9 @@ func TestDoUpdate(t *testing.T) { Name: hostName, IPAddress: hostIP, }, + EndpointOIDCMeta: &message.HostEndpointOIDCMetadata{ + Email: oidcEmail, + }, } rawRes := jsonMarshal(newConfigResponse) @@ -427,6 +436,7 @@ func TestDoUpdate(t *testing.T) { assert.Equal(t, hostID, meta.Host.ID) assert.Equal(t, hostName, meta.Host.Name) assert.Equal(t, hostIP, meta.Host.IPAddress) + assert.Equal(t, oidcEmail, meta.EndpointOIDC.Email) } @@ -727,7 +737,7 @@ func TestCommandResponse(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() - config, pkey, creds, _, err := c.Enroll(ctx, testutil.NewTestLogger(), "foobar") + config, pkey, creds, meta, err := c.Enroll(ctx, testutil.NewTestLogger(), "foobar") require.NoError(t, err) // make sure all credential values were set @@ -740,6 +750,9 @@ func TestCommandResponse(t *testing.T) { assert.NotEmpty(t, config) assert.NotEmpty(t, pkey) + // no EndpointOIDC for standard host enrollments + assert.Nil(t, meta.EndpointOIDC) + // This time sign the response with the correct CA key. responseToken := "abc123" res := map[string]any{"msg": "Hello, world!"} diff --git a/message/message.go b/message/message.go index a490ab3..cf953e1 100644 --- a/message/message.go +++ b/message/message.go @@ -73,13 +73,14 @@ type DoUpdateRequest struct { // DoUpdateResponse is the response generated for a DoUpdate request. type DoUpdateResponse struct { - Config []byte `json:"config"` - Counter uint `json:"counter"` - Nonce []byte `json:"nonce"` - TrustedKeys []byte `json:"trustedKeys"` - Organization HostOrgMetadata `json:"organization"` - Network HostNetworkMetadata `json:"network"` - Host HostHostMetadata `json:"host"` + Config []byte `json:"config"` + Counter uint `json:"counter"` + Nonce []byte `json:"nonce"` + TrustedKeys []byte `json:"trustedKeys"` + Organization HostOrgMetadata `json:"organization"` + Network HostNetworkMetadata `json:"network"` + Host HostHostMetadata `json:"host"` + EndpointOIDCMeta *HostEndpointOIDCMetadata `json:"endpointOIDC"` } // LongPollWaitResponseWrapper contains a response to LongPollWait inside "data." @@ -152,13 +153,14 @@ type EnrollResponse struct { // EnrollResponseData is included in the EnrollResponse. type EnrollResponseData struct { - Config []byte `json:"config"` - HostID string `json:"hostID"` - Counter uint `json:"counter"` - TrustedKeys []byte `json:"trustedKeys"` - Organization HostOrgMetadata `json:"organization"` - Network HostNetworkMetadata `json:"network"` - Host HostHostMetadata `json:"host"` + Config []byte `json:"config"` + HostID string `json:"hostID"` + Counter uint `json:"counter"` + TrustedKeys []byte `json:"trustedKeys"` + Organization HostOrgMetadata `json:"organization"` + Network HostNetworkMetadata `json:"network"` + Host HostHostMetadata `json:"host"` + EndpointOIDCMeta *HostEndpointOIDCMetadata `json:"endpointOIDC"` } // HostOrgMetadata is included in EnrollResponseData. @@ -182,6 +184,11 @@ type HostHostMetadata struct { IPAddress string `json:"ipAddress"` } +// HostEndpointOIDCMetadata is included in EnrollResponseData. +type HostEndpointOIDCMetadata struct { + Email string `json:"email"` +} + // APIError represents a single error returned in an API error response. type APIError struct { Code string `json:"code"`