mirror of
https://github.com/juanfont/headscale.git
synced 2026-10-04 05:43:37 +09:00
cli: accept a user name in preauthkeys create --user
Resolved through lookupUser like the users commands, so scripts skip the users list round trip. Digit-only values stay IDs.
This commit is contained in:
committed by
Kristoffer Dalby
parent
715c5a4a1c
commit
62f89ca25d
@@ -94,6 +94,7 @@ removed on this schedule:
|
|||||||
- Fix `headscale users destroy`/`rename` reporting "multiple users match query" when no user matches; an ambiguous match now lists the matching users [#3476](https://github.com/juanfont/headscale/pull/3476)
|
- Fix `headscale users destroy`/`rename` reporting "multiple users match query" when no user matches; an ambiguous match now lists the matching users [#3476](https://github.com/juanfont/headscale/pull/3476)
|
||||||
- Deleting a user that still owns nodes now lists the nodes (ID and hostname) that must be deleted first [#3475](https://github.com/juanfont/headscale/pull/3475)
|
- Deleting a user that still owns nodes now lists the nodes (ID and hostname) that must be deleted first [#3475](https://github.com/juanfont/headscale/pull/3475)
|
||||||
- Headscale now requires Go 1.27 to build
|
- Headscale now requires Go 1.27 to build
|
||||||
|
- `headscale preauthkeys create --user` accepts a user name as well as an ID
|
||||||
- `derp.paths` files may be Tailscale JSON or HuJSON DERP maps as well as YAML
|
- `derp.paths` files may be Tailscale JSON or HuJSON DERP maps as well as YAML
|
||||||
- `dns.extra_records_path` files may be HuJSON or YAML as well as JSON
|
- `dns.extra_records_path` files may be HuJSON or YAML as well as JSON
|
||||||
- A `derp.paths` region set to `null` removes that region again, as documented
|
- A `derp.paths` region set to `null` removes that region again, as documented
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func init() {
|
|||||||
StringP("expiration", "e", DefaultPreAuthKeyExpiry, "Human-readable expiration of the key (e.g. 30m, 24h)")
|
StringP("expiration", "e", DefaultPreAuthKeyExpiry, "Human-readable expiration of the key (e.g. 30m, 24h)")
|
||||||
createPreAuthKeyCmd.Flags().
|
createPreAuthKeyCmd.Flags().
|
||||||
StringSlice("tags", []string{}, "Tags to automatically assign to node")
|
StringSlice("tags", []string{}, "Tags to automatically assign to node")
|
||||||
createPreAuthKeyCmd.PersistentFlags().Uint64P("user", "u", 0, "User identifier (ID)")
|
createPreAuthKeyCmd.PersistentFlags().StringP("user", "u", "", "User ID, or name if not a number")
|
||||||
expirePreAuthKeyCmd.PersistentFlags().Uint64P("id", "i", 0, "Authkey ID")
|
expirePreAuthKeyCmd.PersistentFlags().Uint64P("id", "i", 0, "Authkey ID")
|
||||||
deletePreAuthKeyCmd.PersistentFlags().Uint64P("id", "i", 0, "Authkey ID")
|
deletePreAuthKeyCmd.PersistentFlags().Uint64P("id", "i", 0, "Authkey ID")
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ var createPreAuthKeyCmd = &cobra.Command{
|
|||||||
Short: "Creates a new preauthkey",
|
Short: "Creates a new preauthkey",
|
||||||
Aliases: []string{"c", cmdNew},
|
Aliases: []string{"c", cmdNew},
|
||||||
RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error {
|
RunE: clientRunE(func(ctx context.Context, client *clientv1.ClientWithResponses, cmd *cobra.Command, args []string) error {
|
||||||
user, _ := cmd.Flags().GetUint64("user")
|
userArg, _ := cmd.Flags().GetString("user")
|
||||||
reusable, _ := cmd.Flags().GetBool("reusable")
|
reusable, _ := cmd.Flags().GetBool("reusable")
|
||||||
ephemeral, _ := cmd.Flags().GetBool("ephemeral")
|
ephemeral, _ := cmd.Flags().GetBool("ephemeral")
|
||||||
tags, _ := cmd.Flags().GetStringSlice("tags")
|
tags, _ := cmd.Flags().GetStringSlice("tags")
|
||||||
@@ -112,7 +112,10 @@ var createPreAuthKeyCmd = &cobra.Command{
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
userStr := strconv.FormatUint(user, util.Base10)
|
userStr, err := userIDFromArg(ctx, client, userArg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
request := clientv1.CreatePreAuthKeyJSONRequestBody{
|
request := clientv1.CreatePreAuthKeyJSONRequestBody{
|
||||||
User: &userStr,
|
User: &userStr,
|
||||||
|
|||||||
@@ -67,6 +67,30 @@ func resolveSingleUser(
|
|||||||
return lookupUser(ctx, client, id, username)
|
return lookupUser(ctx, client, id, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// userIDFromArg resolves a --user value: an ID, or a user name when it is not
|
||||||
|
// a number, so a user whose name is all digits must be given by ID.
|
||||||
|
func userIDFromArg(
|
||||||
|
ctx context.Context,
|
||||||
|
client *clientv1.ClientWithResponses,
|
||||||
|
arg string,
|
||||||
|
) (string, error) {
|
||||||
|
if arg == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := strconv.ParseUint(arg, util.Base10, 64)
|
||||||
|
if err == nil {
|
||||||
|
return arg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
id, _, err := lookupUser(ctx, client, 0, arg)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("--user %q: %w", arg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
// lookupUser resolves exactly one user by ID and/or name (0 and "" are unset),
|
// lookupUser resolves exactly one user by ID and/or name (0 and "" are unset),
|
||||||
// returning the identifier of the matched user and the user itself.
|
// returning the identifier of the matched user and the user itself.
|
||||||
func lookupUser(
|
func lookupUser(
|
||||||
|
|||||||
@@ -184,3 +184,54 @@ func TestResolveSingleUser(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUserIDFromArg(t *testing.T) {
|
||||||
|
alice := clientv1.User{Id: "3", Name: "alice"}
|
||||||
|
digits := clientv1.User{Id: "7", Name: "42"}
|
||||||
|
aliceDup := clientv1.User{Id: "8", Name: "alice"}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
users []clientv1.User
|
||||||
|
arg string
|
||||||
|
wantID string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "unset stays unset", arg: "", wantID: ""},
|
||||||
|
{name: "number is an ID", users: []clientv1.User{alice}, arg: "3", wantID: "3"},
|
||||||
|
// Numbers never hit the name lookup, so user "42" needs its ID.
|
||||||
|
{name: "digit-only name is an ID", users: []clientv1.User{digits}, arg: "42", wantID: "42"},
|
||||||
|
{name: "name resolves to its ID", users: []clientv1.User{alice, digits}, arg: "alice", wantID: "3"},
|
||||||
|
{name: "unknown name is an error", users: []clientv1.User{alice}, arg: "bob", wantErr: true},
|
||||||
|
{name: "ambiguous name is an error", users: []clientv1.User{alice, aliceDup}, arg: "alice", wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
server := filterUsersServer(t, tt.users)
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := clientv1.NewClientWithResponses(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("creating client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := userIDFromArg(context.Background(), client, tt.arg)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("userIDFromArg(%q) error = nil, want error", tt.arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("userIDFromArg(%q) error = %v", tt.arg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id != tt.wantID {
|
||||||
|
t.Errorf("userIDFromArg(%q) = %q, want %q", tt.arg, id, tt.wantID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -105,10 +105,10 @@ Its best suited for automation.
|
|||||||
headscale users create <USER>
|
headscale users create <USER>
|
||||||
```
|
```
|
||||||
|
|
||||||
Use the `headscale user list` command to learn its `<USER_ID>` and create a new pre authenticated key for your user:
|
Create a new pre authenticated key for your user, by name or by the `<USER_ID>` that `headscale user list` shows:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
headscale preauthkeys create --user <USER_ID>
|
headscale preauthkeys create --user <USER>
|
||||||
```
|
```
|
||||||
|
|
||||||
The above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use
|
The above prints a pre authenticated key with the default settings (can be used once and is valid for one hour). Use
|
||||||
|
|||||||
@@ -133,14 +133,14 @@ headscale instance. By default, the key is valid for one hour and can only be us
|
|||||||
=== "Native"
|
=== "Native"
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
headscale preauthkeys create --user <USER_ID>
|
headscale preauthkeys create --user <USER>
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Container"
|
=== "Container"
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker exec -it headscale \
|
docker exec -it headscale \
|
||||||
headscale preauthkeys create --user <USER_ID>
|
headscale preauthkeys create --user <USER>
|
||||||
```
|
```
|
||||||
|
|
||||||
The command returns the preauthkey on success which is used to connect a node to the headscale instance via the
|
The command returns the preauthkey on success which is used to connect a node to the headscale instance via the
|
||||||
|
|||||||
Reference in New Issue
Block a user