Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add node identity #3125

Merged
merged 39 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7caedf3
Remove code duplication
islamaliev Oct 8, 2024
a1ea081
Assign identity to a node
islamaliev Oct 8, 2024
f916707
WIP
islamaliev Oct 8, 2024
d916c33
Return RawIdentity, add test
islamaliev Oct 10, 2024
1238aba
Fix lint
islamaliev Oct 10, 2024
1b92c70
Update docs
islamaliev Oct 10, 2024
61c8fb0
Update mocks
islamaliev Oct 10, 2024
82dc3b2
Minor refactor
islamaliev Oct 10, 2024
30a028f
PR fixup
islamaliev Oct 12, 2024
2bd361a
Polish
islamaliev Oct 12, 2024
940177d
Update mocks
islamaliev Oct 12, 2024
55413a3
PR fixup
islamaliev Oct 15, 2024
5ecf6ab
Polish
islamaliev Oct 15, 2024
a32a1f4
PR fixup
islamaliev Oct 15, 2024
62e38c5
PR fixup
islamaliev Oct 15, 2024
ed20e57
PR fixup
islamaliev Oct 17, 2024
a3396bc
Update docs
islamaliev Oct 17, 2024
3f03aa5
Rename command to node-identity
islamaliev Oct 18, 2024
5b2f935
Add assign-node-identity command
islamaliev Oct 20, 2024
b9ebd23
Update docs
islamaliev Oct 20, 2024
d158f83
Lint fix
islamaliev Oct 20, 2024
ab5dc33
Update mocks
islamaliev Oct 20, 2024
4c73fb4
Create parent command node-identity
islamaliev Oct 20, 2024
5946638
PR fixup
islamaliev Oct 21, 2024
8e39ec3
Merge remote-tracking branch 'upstream/develop' into feat/node-identity
islamaliev Oct 21, 2024
0530ab7
Make identity token updatable
islamaliev Oct 22, 2024
ab3a9ea
Update docs
islamaliev Oct 22, 2024
0e0a252
Fix lint
islamaliev Oct 22, 2024
5b3a5c4
Merge remote-tracking branch 'upstream/develop' into feat/node-identity
islamaliev Oct 22, 2024
a0f173f
Turn 2d array of identities into 1d (WIP)
islamaliev Oct 12, 2024
b6d148b
Add clear distinction between user and node identity
islamaliev Oct 13, 2024
95fc645
Pass ctx explicitly
islamaliev Oct 22, 2024
b154dbe
Remove duration from node's identity
islamaliev Oct 23, 2024
af4e2f9
Remove node-identity assign command
islamaliev Oct 23, 2024
2869d87
Polish
islamaliev Oct 24, 2024
3210fbf
Merge remote-tracking branch 'upstream/develop' into feat/node-identity
islamaliev Oct 24, 2024
e4fc548
Make identityRef optional
islamaliev Oct 25, 2024
7fc8e3d
Rename UserIdentity to ClientIdentity
islamaliev Oct 25, 2024
f3ca301
Merge remote-tracking branch 'upstream/develop' into feat/node-identity
islamaliev Oct 25, 2024
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
31 changes: 4 additions & 27 deletions acp/identity/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,42 +50,19 @@ type Identity struct {
}

// FromPrivateKey returns a new identity using the given private key.
//
// - duration: The [time.Duration] that this identity is valid for.
// - audience: The audience that this identity is valid for. This is required
// by the Defra http client. For example `github.com/sourcenetwork/defradb`
// - authorizedAccount: An account that this identity is authorizing to make
// SourceHub calls on behalf of this actor. This is currently required when
// using SourceHub ACP.
// - skipTokenGeneration: If true, BearerToken will not be set. This parameter is
// provided as generating and signing the token is relatively slow, and only required
// by remote Defra clients (CLI, http), or if using SourceHub ACP.
func FromPrivateKey(
privateKey *secp256k1.PrivateKey,
duration time.Duration,
audience immutable.Option[string],
authorizedAccount immutable.Option[string],
skipTokenGeneration bool,
) (Identity, error) {
// In order to generate a fresh token for this identity, use the [UpdateToken]
func FromPrivateKey(privateKey *secp256k1.PrivateKey) (Identity, error) {
publicKey := privateKey.PubKey()
did, err := DIDFromPublicKey(publicKey)
if err != nil {
return Identity{}, err
}

identity := Identity{
return Identity{
DID: did,
PrivateKey: privateKey,
PublicKey: publicKey,
}

if !skipTokenGeneration {
err = identity.UpdateToken(duration, audience, authorizedAccount)
if err != nil {
return Identity{}, err
}
}
return identity, nil
}, nil
}

// FromToken constructs a new `Identity` from a bearer token.
Expand Down
8 changes: 5 additions & 3 deletions cli/node_identity_assign.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,14 @@ Example to assign an identity to the node:
return err
}
privKey := secp256k1.PrivKeyFromBytes(data)
identity, err := acpIdentity.FromPrivateKey(
privKey,
identity, err := acpIdentity.FromPrivateKey(privKey)
if err != nil {
return err
}
err = identity.UpdateToken(
getDefaultNodeIdentityDuration(),
immutable.Some(cfg.GetString("api.address")),
immutable.None[string](),
false,
)
if err != nil {
return err
Expand Down
17 changes: 2 additions & 15 deletions cli/start.go
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"os"
"os/signal"
"syscall"
"time"

"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/sourcenetwork/immutable"
Expand Down Expand Up @@ -307,13 +306,7 @@ func getOrCreateIdentity(kr keyring.Keyring, opts []node.Option) ([]node.Option,
}
}

nodeIdentity, err := identity.FromPrivateKey(
secp256k1.PrivKeyFromBytes(identityBytes),
time.Duration(0),
immutable.None[string](),
immutable.None[string](),
false,
)
nodeIdentity, err := identity.FromPrivateKey(secp256k1.PrivKeyFromBytes(identityBytes))
if err != nil {
return nil, err
}
Expand All @@ -327,13 +320,7 @@ func addEphemeralIdentity(opts []node.Option) ([]node.Option, error) {
return nil, err
}

nodeIdentity, err := identity.FromPrivateKey(
secp256k1.PrivKeyFromBytes(privateKey.Serialize()),
time.Duration(0),
immutable.None[string](),
immutable.None[string](),
false,
)
nodeIdentity, err := identity.FromPrivateKey(secp256k1.PrivKeyFromBytes(privateKey.Serialize()))
if err != nil {
return nil, err
}
Expand Down
11 changes: 6 additions & 5 deletions cli/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,14 @@ func setContextIdentity(cmd *cobra.Command, privateKeyHex string) error {
}

privKey := secp256k1.PrivKeyFromBytes(data)
ident, err := acpIdentity.FromPrivateKey(
privKey,
ident, err := acpIdentity.FromPrivateKey(privKey)
if err != nil {
return err
}
err = ident.UpdateToken(
authTokenExpiration,
immutable.Some(cfg.GetString("api.address")),
sourcehubAddress,
false,
)
sourcehubAddress)
if err != nil {
return err
}
Expand Down
35 changes: 13 additions & 22 deletions http/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,10 @@ func TestVerifyAuthToken(t *testing.T) {
privKey, err := crypto.GenerateSecp256k1()
require.NoError(t, err)

identity, err := acpIdentity.FromPrivateKey(
privKey,
time.Hour,
immutable.Some(audience),
immutable.None[string](),
false,
)
identity, err := acpIdentity.FromPrivateKey(privKey)
require.NoError(t, err)

err = identity.UpdateToken(time.Hour, immutable.Some(audience), immutable.None[string]())
require.NoError(t, err)

err = verifyAuthToken(identity, audience)
Expand All @@ -45,13 +42,10 @@ func TestVerifyAuthTokenErrorsWithNonMatchingAudience(t *testing.T) {
privKey, err := crypto.GenerateSecp256k1()
require.NoError(t, err)

identity, err := acpIdentity.FromPrivateKey(
privKey,
time.Hour,
immutable.Some("valid"),
immutable.None[string](),
false,
)
identity, err := acpIdentity.FromPrivateKey(privKey)
require.NoError(t, err)

err = identity.UpdateToken(time.Hour, immutable.Some("valid"), immutable.None[string]())
require.NoError(t, err)

err = verifyAuthToken(identity, "invalid")
Expand All @@ -64,14 +58,11 @@ func TestVerifyAuthTokenErrorsWithExpired(t *testing.T) {
privKey, err := crypto.GenerateSecp256k1()
require.NoError(t, err)

identity, err := acpIdentity.FromPrivateKey(
privKey,
// negative expiration
-time.Hour,
immutable.Some(audience),
immutable.None[string](),
false,
)
identity, err := acpIdentity.FromPrivateKey(privKey)
require.NoError(t, err)

// negative expiration
err = identity.UpdateToken(-time.Hour, immutable.Some(audience), immutable.None[string]())
require.NoError(t, err)

err = verifyAuthToken(identity, "123abc")
Expand Down
126 changes: 17 additions & 109 deletions tests/integration/acp.go
islamaliev marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ import (
"github.com/sourcenetwork/immutable"
"github.com/stretchr/testify/require"

acpIdentity "github.com/sourcenetwork/defradb/acp/identity"
identity "github.com/sourcenetwork/defradb/acp/identity"
"github.com/sourcenetwork/defradb/keyring"
"github.com/sourcenetwork/defradb/node"
"github.com/sourcenetwork/defradb/tests/clients/cli"
Expand Down Expand Up @@ -90,7 +88,7 @@ type AddPolicy struct {
Policy string

// The policy creator identity, i.e. actor creating the policy.
Identity immutable.Option[int]
Identity identRef

// The expected policyID generated based on the Policy loaded in to the ACP system.
ExpectedPolicyID string
Expand All @@ -112,9 +110,9 @@ func addPolicyACP(
require.Fail(s.t, "Expected error should not have an expected policyID with it.", s.testCase.Description)
}

_, nodes := getNodesWithIDs(action.NodeID, s.nodes)
for _, node := range nodes {
ctx := identity.WithContext(s.ctx, getIdentity(s, action.Identity))
nodeIDs, nodes := getNodesWithIDs(action.NodeID, s.nodes)
for index, node := range nodes {
ctx := getContextWithIdentity(s, action.Identity, nodeIDs[index])
policyResult, err := node.AddPolicy(ctx, action.Policy)

expectedErrorRaised := AssertError(s.t, s.testCase.Description, err, action.ExpectedError)
Expand Down Expand Up @@ -161,13 +159,13 @@ type AddDocActorRelationship struct {
// The target public identity, i.e. the identity of the actor to tie the document's relation with.
//
// This is a required field. To test the invalid usage of not having this arg, use -1 index.
TargetIdentity int
TargetIdentity identRef
islamaliev marked this conversation as resolved.
Show resolved Hide resolved

// The requestor identity, i.e. identity of the actor creating the relationship.
// Note: This identity must either own or have managing access defined in the policy.
//
// This is a required field. To test the invalid usage of not having this arg, use -1 index.
RequestorIdentity int
RequestorIdentity identRef

// Result returns true if it was a no-op due to existing before, and false if a new relationship was made.
ExpectedExistence bool
Expand All @@ -188,14 +186,13 @@ func addDocActorRelationshipACP(
nodeID := nodeIDs[index]

collectionName, docID := getCollectionAndDocInfo(s, action.CollectionID, action.DocID, nodeID)
requestorIdentity := getRequestorIdentity(s, action.RequestorIdentity)

exists, err := node.AddDocActorRelationship(
identity.WithContext(s.ctx, requestorIdentity),
getContextWithIdentity(s, action.RequestorIdentity, nodeID),
collectionName,
docID,
action.Relation,
getTargetIdentity(s, action.TargetIdentity),
getIdentityDID(s, action.TargetIdentity),
)

expectedErrorRaised := AssertError(s.t, s.testCase.Description, err, action.ExpectedError)
Expand Down Expand Up @@ -242,13 +239,13 @@ type DeleteDocActorRelationship struct {
// The target public identity, i.e. the identity of the actor with whom the relationship is with.
//
// This is a required field. To test the invalid usage of not having this arg, use -1 index.
TargetIdentity int
TargetIdentity identRef

// The requestor identity, i.e. identity of the actor deleting the relationship.
// Note: This identity must either own or have managing access defined in the policy.
//
// This is a required field. To test the invalid usage of not having this arg, use -1 index.
RequestorIdentity int
RequestorIdentity identRef

// Result returns true if the relationship record was expected to be found and deleted,
// and returns false if no matching relationship record was found (no-op).
Expand All @@ -270,14 +267,13 @@ func deleteDocActorRelationshipACP(
nodeID := nodeIDs[index]

collectionName, docID := getCollectionAndDocInfo(s, action.CollectionID, action.DocID, nodeID)
requestorIdentity := getRequestorIdentity(s, action.RequestorIdentity)

deleteDocActorRelationshipResult, err := node.DeleteDocActorRelationship(
identity.WithContext(s.ctx, requestorIdentity),
getContextWithIdentity(s, action.RequestorIdentity, nodeID),
collectionName,
docID,
action.Relation,
getTargetIdentity(s, action.TargetIdentity),
getIdentityDID(s, action.TargetIdentity),
)

expectedErrorRaised := AssertError(s.t, s.testCase.Description, err, action.ExpectedError)
Expand Down Expand Up @@ -313,28 +309,6 @@ func getCollectionAndDocInfo(s *state, collectionID, docInd, nodeID int) (string
return collectionName, docID
}

func getTargetIdentity(s *state, targetIdent int) string {
if targetIdent != -1 {
optionalTargetIdentity := getIdentity(s, immutable.Some(targetIdent))
if !optionalTargetIdentity.HasValue() {
require.Fail(s.t, "Expected non-empty target identity, but it was empty.", s.testCase.Description)
}
return optionalTargetIdentity.Value().DID
}
return ""
}

func getRequestorIdentity(s *state, requestorIdent int) immutable.Option[acpIdentity.Identity] {
if requestorIdent != -1 {
requestorIdentity := getIdentity(s, immutable.Some(requestorIdent))
if !requestorIdentity.HasValue() {
require.Fail(s.t, "Expected non-empty requestor identity, but it was empty.", s.testCase.Description)
}
return requestorIdentity
}
return acpIdentity.None
}

func setupSourceHub(s *state) ([]node.ACPOpt, error) {
var isACPTest bool
for _, a := range s.testCase.Actions {
Expand Down Expand Up @@ -611,12 +585,11 @@ func crossLock(port uint16) (func(), error) {
nil
}

func getNodeAudience(s *state) immutable.Option[string] {
//if nodeIndex >= len(s.nodes) {
//return immutable.None[string]()
//}
//switch client := s.nodes[nodeIndex].(type) {
switch client := s.nodes[0].(type) {
func getNodeAudience(s *state, nodeIndex int) immutable.Option[string] {
if nodeIndex >= len(s.nodes) {
return immutable.None[string]()
}
switch client := s.nodes[nodeIndex].(type) {
case *http.Wrapper:
return immutable.Some(strings.TrimPrefix(client.Host(), "http://"))
case *cli.Wrapper:
Expand All @@ -626,71 +599,6 @@ func getNodeAudience(s *state) immutable.Option[string] {
return immutable.None[string]()
}

// Generate the keys using the index as the seed so that multiple
// runs yield the same private key. This is important for stuff like
// the change detector.
func generateIdentity(s *state, seedIndex int, audience immutable.Option[string]) (acpIdentity.Identity, error) {
source := rand.NewSource(int64(seedIndex))
r := rand.New(source)

privateKey, err := secp256k1.GeneratePrivateKeyFromRand(r)
require.NoError(s.t, err)

identity, err := acpIdentity.FromPrivateKey(
privateKey,
authTokenExpiration,
audience,
immutable.Some(s.sourcehubAddress),
// Creating and signing the bearer token is slow, so we skip it if it not
// required.
!(acpType == SourceHubACPType || audience.HasValue()),
)

return identity, err
}

func getIdentity(s *state, index immutable.Option[int]) immutable.Option[acpIdentity.Identity] {
if !index.HasValue() {
return immutable.None[acpIdentity.Identity]()
}

nodeIdentities := s.identities

if len(nodeIdentities) <= index.Value() {
identities := make([]acpIdentity.Identity, index.Value()+1)
// Fill any empty identities up to the index.
for i := range identities {
if i < len(nodeIdentities) && nodeIdentities[i] != (acpIdentity.Identity{}) {
identities[i] = nodeIdentities[i]
continue
}
newIdentity, err := generateIdentity(s, i, getNodeAudience(s))
require.NoError(s.t, err)
identities[i] = newIdentity
}
s.identities = identities
return immutable.Some(identities[index.Value()])
} else {
return immutable.Some(nodeIdentities[index.Value()])
}
}

func getNodeIdentity(s *state, nodeIndex int) acpIdentity.Identity {
// TODO: check if it makes sense to create a wrapper over the node to store client and identity
identity, err := generateIdentity(s, s.nextNodeIdentityGenSeed, immutable.None[string]())
require.NoError(s.t, err)

s.nextNodeIdentityGenSeed--

if len(s.identities) == 0 {
s.identities = append(s.identities, identity)
} else {
s.identities[0] = identity
}

return identity
}

// testBuffer is a very simple, thread-safe (--race flag friendly), io.Writer
// implementation that allows us to easily access the out/err outputs of CLI commands.
//
Expand Down
Loading
Loading