Repository: netbirdio/netbird

Issue:
/api/setup-keys List all Setup Keys does not return Setup Keys anymore
**Describe the problem**

We use the Netbird API in our Setup Routine for a custom Ubuntu Install to get a list of all the Setup Keys to choose which one to use for the Installation of the Netbird Client.

In the Past we got a list of the Full key like it is discribed here https://docs.netbird.io/api/resources/setup-keys

Now after Updating to 0.32.0 we do not get the whole API Key anymore. Just the Five first Digits. The Rest is masked out by Stars.

**Are you using NetBird Cloud?**

No This is a self hosted installation

**Additional context**

The Answer we get from the API Call:

```
[
...
  {
...
    "key": "0FE28****",
...
  }
]

```

Is this related to https://github.com/netbirdio/netbird/issues/2763 and https://github.com/netbirdio/netbird/pull/2775 ?
If yes, is there now an diffrent way to get the Setup Keys once they are already created?

Hints from issue comments:
@NightHammer1000: In addition to that, /api/setup-keys/{keyId} - Retrieve a Setup Key does also not work at all in our setup. No matter what ID i try to get we always get the Response

{"message":"setup key: cqaj8pd6m1gc7385f0a0 not found","code":404}

Its always the same. No matter what ID we throw at the API. I could input random Numbers and Letters and it will also return this exact response@pascal-fischer: Hi @NightHammer1000,
the behaviour of the list setup-keys api is correct. From now on all setup-keys will be hashed in the database and only the 5 digit secret will be stored. As the keys are now hashed there is no way of retrieving existing keys after they have been created. This is done to improve security so even with access to the database no one will be able to get a hold of the key except the user creating it.

Regarding the /api/setup-keys/{keyId} always returning not found, for me it is the same. I will have a look into whats going wrong here. Thanks for reporting this!@NightHammer1000: Okay. This is kinda bad for us. Thanks for the Info anyway!

As we Previously used the Setup Keys to place the Devices in their respective Groups Automatically to make use of the Policys.

I guess we can work around it in our Setup Script by first pulling a List of Groups from which our Install Engineer can choose the correct one from and then creating a one time use Key for that Group instead of pulling the Multi-Use Key from API.

Will Create Setup Key return the full key? Or also just the Hashed one?

Also. This change should definetly be reflected at https://docs.netbird.io/api/resources/setup-keys before more people waste their expensive time ^^@pascal-fischer: Yes when creating a new setup key it will still return the full key for you to copy. The hashed one is only used in the database itself and for other GET calls we will return only the first 5 digits of the key for you to compare with your copy.

Sorry that you lost time on this, we missed the API docs update. The PR I created also includes an update for the openAPI specs so that the docs generation will be updated once the PR is merged.@nazarewk: Hello @NightHammer1000,

We're currently reviewing our open issues and would like to verify if this problem still exists in the [latest NetBird version](https://github.com/netbirdio/netbird/releases).

Could you please confirm if the issue is still there?

We may close this issue temporarily if we don't hear back from you within **2 weeks**, but feel free to reopen it with updated information.

Thanks for your contribution to improving the project!
@NightHammer1000: Well calling it an Issue when this has become a feature is not quite right.
We worked around it with way more API work. The way we do it now works for us. So I guess we can close this.

Solution patch:
diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml
index bfb37527..2e084f6e 100644
--- a/management/server/http/api/openapi.yml
+++ b/management/server/http/api/openapi.yml
@@ -439,17 +439,13 @@ components:
               example: 5
           required:
             - accessible_peers_count
-    SetupKey:
+    SetupKeyBase:
       type: object
       properties:
         id:
           description: Setup Key ID
           type: string
           example: 2531583362
-        key:
-          description: Setup Key value
-          type: string
-          example: A616097E-FCF0-48FA-9354-CA4A61142761
         name:
           description: Setup key name identifier
           type: string
@@ -518,6 +514,28 @@ components:
         - updated_at
         - usage_limit
         - ephemeral
+    SetupKeyClear:
+      allOf:
+        - $ref: '#/components/schemas/SetupKeyBase'
+        - type: object
+          properties:
+            key:
+              description: Setup Key as plain text
+              type: string
+              example: A616097E-FCF0-48FA-9354-CA4A61142761
+          required:
+            - key
+    SetupKey:
+      allOf:
+        - $ref: '#/components/schemas/SetupKeyBase'
+        - type: object
+          properties:
+            key:
+              description: Setup Key as secret
+              type: string
+              example: A6160****
+          required:
+            - key
     SetupKeyRequest:
       type: object
       properties:
@@ -1918,7 +1936,7 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/SetupKey'
+                $ref: '#/components/schemas/SetupKeyClear'
         '400':
           "$ref": "#/components/responses/bad_request"
         '401':
diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go
index f219c457..321395d2 100644
--- a/management/server/http/api/types.gen.go
+++ b/management/server/http/api/types.gen.go
@@ -1062,7 +1062,94 @@ type SetupKey struct {
 	// Id Setup Key ID
 	Id string `json:"id"`
 
-	// Key Setup Key value
+	// Key Setup Key as secret
+	Key string `json:"key"`
+
+	// LastUsed Setup key last usage date
+	LastUsed time.Time `json:"last_used"`
+
+	// Name Setup key name identifier
+	Name string `json:"name"`
+
+	// Revoked Setup key revocation status
+	Revoked bool `json:"revoked"`
+
+	// State Setup key status, "valid", "overused","expired" or "revoked"
+	State string `json:"state"`
+
+	// Type Setup key type, one-off for single time usage and reusable
+	Type string `json:"type"`
+
+	// UpdatedAt Setup key last update date
+	UpdatedAt time.Time `json:"updated_at"`
+
+	// UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage.
+	UsageLimit int `json:"usage_limit"`
+
+	// UsedTimes Usage count of setup key
+	UsedTimes int `json:"used_times"`
+
+	// Valid Setup key validity status
+	Valid bool `json:"valid"`
+}
+
+// SetupKeyBase defines model for SetupKeyBase.
+type SetupKeyBase struct {
+	// AutoGroups List of group IDs to auto-assign to peers registered with this key
+	AutoGroups []string `json:"auto_groups"`
+
+	// Ephemeral Indicate that the peer will be ephemeral or not
+	Ephemeral bool `json:"ephemeral"`
+
+	// Expires Setup Key expiration date
+	Expires time.Time `json:"expires"`
+
+	// Id Setup Key ID
+	Id string `json:"id"`
+
+	// LastUsed Setup key last usage date
+	LastUsed time.Time `json:"last_used"`
+
+	// Name Setup key name identifier
+	Name string `json:"name"`
+
+	// Revoked Setup key revocation status
+	Revoked bool `json:"revoked"`
+
+	// State Setup key status, "valid", "overused","expired" or "revoked"
+	State string `json:"state"`
+
+	// Type Setup key type, one-off for single time usage and reusable
+	Type string `json:"type"`
+
+	// UpdatedAt Setup key last update date
+	UpdatedAt time.Time `json:"updated_at"`
+
+	// UsageLimit A number of times this key can be used. The value of 0 indicates the unlimited usage.
+	UsageLimit int `json:"usage_limit"`
+
+	// UsedTimes Usage count of setup key
+	UsedTimes int `json:"used_times"`
+
+	// Valid Setup key validity status
+	Valid bool `json:"valid"`
+}
+
+// SetupKeyClear defines model for SetupKeyClear.
+type SetupKeyClear struct {
+	// AutoGroups List of group IDs to auto-assign to peers registered with this key
+	AutoGroups []string `json:"auto_groups"`
+
+	// Ephemeral Indicate that the peer will be ephemeral or not
+	Ephemeral bool `json:"ephemeral"`
+
+	// Expires Setup Key expiration date
+	Expires time.Time `json:"expires"`
+
+	// Id Setup Key ID
+	Id string `json:"id"`
+
+	// Key Setup Key as plain text
 	Key string `json:"key"`
 
 	// LastUsed Setup key last usage date
diff --git a/management/server/setupkey.go b/management/server/setupkey.go
index cae0dfec..ef431d3a 100644
--- a/management/server/setupkey.go
+++ b/management/server/setupkey.go
@@ -379,7 +379,7 @@ func (am *DefaultAccountManager) GetSetupKey(ctx context.Context, accountID, use
 		return nil, status.NewAdminPermissionError()
 	}
 
-	setupKey, err := am.Store.GetSetupKeyByID(ctx, LockingStrengthShare, keyID, accountID)
+	setupKey, err := am.Store.GetSetupKeyByID(ctx, LockingStrengthShare, accountID, keyID)
 	if err != nil {
 		return nil, err
 	}



Test patch:
diff --git a/management/server/setupkey_test.go b/management/server/setupkey_test.go
index 94ed022f..7c820070 100644
--- a/management/server/setupkey_test.go
+++ b/management/server/setupkey_test.go
@@ -210,22 +210,41 @@ func TestGetSetupKeys(t *testing.T) {
 		t.Fatal(err)
 	}
 
-	err = manager.SaveGroup(context.Background(), account.Id, userID, &nbgroup.Group{
-		ID:    "group_1",
-		Name:  "group_name_1",
-		Peers: []string{},
-	})
+	plainKey, err := manager.CreateSetupKey(context.Background(), account.Id, "key1", SetupKeyReusable, time.Hour, nil, SetupKeyUnlimitedUsage, userID, false)
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	err = manager.SaveGroup(context.Background(), account.Id, userID, &nbgroup.Group{
-		ID:    "group_2",
-		Name:  "group_name_2",
-		Peers: []string{},
-	})
-	if err != nil {
-		t.Fatal(err)
+	type testCase struct {
+		name            string
+		keyId           string
+		expectedFailure bool
+	}
+
+	testCase1 := testCase{
+		name:            "Should get existing Setup Key",
+		keyId:           plainKey.Id,
+		expectedFailure: false,
+	}
+	testCase2 := testCase{
+		name:            "Should fail to get non-existent Setup Key",
+		keyId:           "some key",
+		expectedFailure: true,
+	}
+
+	for _, tCase := range []testCase{testCase1, testCase2} {
+		t.Run(tCase.name, func(t *testing.T) {
+			key, err := manager.GetSetupKey(context.Background(), account.Id, userID, tCase.keyId)
+
+			if tCase.expectedFailure {
+				if err == nil {
+					t.Fatal("expected to fail")
+				}
+				return
+			}
+
+			assert.NotEqual(t, plainKey.Key, key.Key)
+		})
 	}
 }