Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 66 additions & 23 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,33 @@ func (e InvalidCredentialsError) Error() string {
return "invalid credentials"
}

type EnrollMeta struct {
OrganizationID string
OrganizationName string
type ConfigMeta struct {
Org ConfigOrg
Network ConfigNetwork
Host ConfigHost
}

type ConfigOrg struct {
ID string
Name string
}

type ConfigNetwork struct {
ID string
Name string
}

type ConfigHost struct {
ID string
Name string
IPAddress 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
// config (see api.InsertConfigPrivateKey), credentials to be used in DNClient API requests, and a meta object
// containing organization info.
func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code string) ([]byte, []byte, *keys.Credentials, *EnrollMeta, error) {
// config (see api.InsertConfigPrivateKey), credentials to be used in DNClient API requests, and a meta object.
func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code string) ([]byte, []byte, *keys.Credentials, *ConfigMeta, error) {
logger.WithFields(logrus.Fields{"server": c.dnServer}).Debug("Making enrollment request to API")

// Generate newKeys for the enrollment request
Expand Down Expand Up @@ -168,9 +184,20 @@ func (c *Client) Enroll(ctx context.Context, logger logrus.FieldLogger, code str
return nil, nil, nil, nil, &APIError{e: fmt.Errorf("unexpected error during enrollment: %v", err), ReqID: reqID}
}

meta := &EnrollMeta{
OrganizationID: r.Data.Organization.ID,
OrganizationName: r.Data.Organization.Name,
meta := &ConfigMeta{
Org: ConfigOrg{
ID: r.Data.Organization.ID,
Name: r.Data.Organization.Name,
},
Network: ConfigNetwork{
ID: r.Data.Network.ID,
Name: r.Data.Network.Name,
},
Host: ConfigHost{
ID: r.Data.HostID,
Name: r.Data.Host.Name,
IPAddress: r.Data.Host.IPAddress,
},
}

// Determine the private keys to save based on the network curve type
Expand Down Expand Up @@ -239,17 +266,17 @@ func (c *Client) LongPollWait(ctx context.Context, creds keys.Credentials, suppo

// DoUpdate sends a signed message to the DNClient API to fetch the new configuration update. During this call new keys
// are generated both for Nebula and DNClient API communication. If the API response is successful, the new configuration
// is returned along with the new Nebula private key PEM and new DNClient API credentials.
// is returned along with the new Nebula private key PEM, new DNClient API credentials, and a meta object.
//
// See dnapi.InsertConfigPrivateKey for how to insert the new Nebula private key into the configuration.
func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte, []byte, *keys.Credentials, error) {
func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte, []byte, *keys.Credentials, *ConfigMeta, error) {
// Rotate keys
var nebulaPrivkeyPEM []byte // ECDH
var hostPrivkey keys.PrivateKey // ECDSA

newKeys, err := keys.New()
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to generate new keys: %s", err)
return nil, nil, nil, nil, fmt.Errorf("failed to generate new keys: %s", err)
}

msg := message.DoUpdateRequest{
Expand All @@ -261,7 +288,7 @@ func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte,
case ed25519.PrivateKey:
hostPubkeyPEM, err := newKeys.HostEd25519PublicKey.MarshalPEM()
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to marshal Ed25519 public key: %s", err)
return nil, nil, nil, nil, fmt.Errorf("failed to marshal Ed25519 public key: %s", err)
}
hostPrivkey = newKeys.HostEd25519PrivateKey
nebulaPrivkeyPEM = newKeys.NebulaX25519PrivateKeyPEM
Expand All @@ -270,7 +297,7 @@ func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte,
case *ecdsa.PrivateKey:
hostPubkeyPEM, err := newKeys.HostP256PublicKey.MarshalPEM()
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to marshal P256 public key: %s", err)
return nil, nil, nil, nil, fmt.Errorf("failed to marshal P256 public key: %s", err)
}
hostPrivkey = newKeys.HostP256PrivateKey
nebulaPrivkeyPEM = newKeys.NebulaP256PrivateKeyPEM
Expand All @@ -280,18 +307,18 @@ func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte,

blob, err := json.Marshal(msg)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to marshal DNClient message: %s", err)
return nil, nil, nil, nil, fmt.Errorf("failed to marshal DNClient message: %s", err)
}

// Make API call
resp, err := c.postDNClient(ctx, message.DoUpdate, blob, creds.HostID, creds.Counter, creds.PrivateKey)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to make API call to Defined Networking: %w", err)
return nil, nil, nil, nil, fmt.Errorf("failed to make API call to Defined Networking: %w", err)
}
resultWrapper := message.SignedResponseWrapper{}
err = json.Unmarshal(resp, &resultWrapper)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to unmarshal signed response wrapper: %s", err)
return nil, nil, nil, nil, fmt.Errorf("failed to unmarshal signed response wrapper: %s", err)
}

// Verify the signature
Expand All @@ -303,29 +330,29 @@ func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte,
}
}
if !valid {
return nil, nil, nil, fmt.Errorf("failed to verify signed API result")
return nil, nil, nil, nil, fmt.Errorf("failed to verify signed API result")
}

// Consume the verified message
result := message.DoUpdateResponse{}
err = json.Unmarshal(resultWrapper.Data.Message, &result)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to unmarshal response (%s): %s", resultWrapper.Data.Message, err)
return nil, nil, nil, nil, fmt.Errorf("failed to unmarshal response (%s): %s", resultWrapper.Data.Message, err)
}

// Verify the nonce
if !bytes.Equal(result.Nonce, msg.Nonce) {
return nil, nil, nil, fmt.Errorf("nonce mismatch between request (%s) and response (%s)", msg.Nonce, result.Nonce)
return nil, nil, nil, nil, fmt.Errorf("nonce mismatch between request (%s) and response (%s)", msg.Nonce, result.Nonce)
}

// Verify the counter
if result.Counter <= creds.Counter {
return nil, nil, nil, fmt.Errorf("counter in request (%d) should be less than counter in response (%d)", creds.Counter, result.Counter)
return nil, nil, nil, nil, fmt.Errorf("counter in request (%d) should be less than counter in response (%d)", creds.Counter, result.Counter)
}

trustedKeys, err := keys.TrustedKeysFromPEM(result.TrustedKeys)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to load trusted keys from bundle: %s", err)
return nil, nil, nil, nil, fmt.Errorf("failed to load trusted keys from bundle: %s", err)
}

newCreds := &keys.Credentials{
Expand All @@ -335,7 +362,23 @@ func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte,
TrustedKeys: trustedKeys,
}

return result.Config, nebulaPrivkeyPEM, newCreds, nil
meta := &ConfigMeta{
Org: ConfigOrg{
ID: result.Organization.ID,
Name: result.Organization.Name,
},
Network: ConfigNetwork{
ID: result.Network.ID,
Name: result.Network.Name,
},
Host: ConfigHost{
ID: result.Host.ID,
Name: result.Host.Name,
IPAddress: result.Host.IPAddress,
},
}

return result.Config, nebulaPrivkeyPEM, newCreds, meta, nil
}

func (c *Client) CommandResponse(ctx context.Context, creds keys.Credentials, responseToken string, response any) error {
Expand Down
Loading
Loading