forked from siderolabs/omni
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement CLI commands for user management
Add the UI for showing and editing service accounts. Fixes: siderolabs#197 Signed-off-by: Artem Chernyshev <artem.chernyshev@talos-systems.com>
- Loading branch information
Showing
22 changed files
with
984 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
package omnictl | ||
|
||
import "github.com/siderolabs/omni/client/pkg/omnictl/user" | ||
|
||
func init() { | ||
RootCmd.AddCommand(user.RootCmd()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
package user | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/cosi-project/runtime/pkg/safe" | ||
"github.com/cosi-project/runtime/pkg/state" | ||
"github.com/google/uuid" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/siderolabs/omni/client/pkg/client" | ||
"github.com/siderolabs/omni/client/pkg/omni/resources" | ||
"github.com/siderolabs/omni/client/pkg/omni/resources/auth" | ||
"github.com/siderolabs/omni/client/pkg/omnictl/internal/access" | ||
) | ||
|
||
var createCmdFlags struct { | ||
role string | ||
} | ||
|
||
// createCmd represents the user create command. | ||
var createCmd = &cobra.Command{ | ||
Use: "create [email]", | ||
Short: "Create a user.", | ||
Long: `Create a user with the specified email.`, | ||
Example: "", | ||
Args: cobra.ExactArgs(1), | ||
RunE: func(_ *cobra.Command, args []string) error { | ||
return access.WithClient(createUser(args[0])) | ||
}, | ||
} | ||
|
||
func createUser(email string) func(ctx context.Context, client *client.Client) error { | ||
return func(ctx context.Context, client *client.Client) error { | ||
user := auth.NewUser(resources.DefaultNamespace, uuid.NewString()) | ||
|
||
user.TypedSpec().Value.Role = createCmdFlags.role | ||
|
||
identity := auth.NewIdentity(resources.DefaultNamespace, email) | ||
|
||
identity.Metadata().Labels().Set(auth.LabelIdentityUserID, user.Metadata().ID()) | ||
|
||
identity.TypedSpec().Value.UserId = user.Metadata().ID() | ||
|
||
existing, err := safe.ReaderGetByID[*auth.Identity](ctx, client.Omni().State(), email) | ||
if err != nil && !state.IsNotFoundError(err) { | ||
return err | ||
} | ||
|
||
if existing != nil { | ||
return fmt.Errorf("identity with email %q already exists", email) | ||
} | ||
|
||
if err := client.Omni().State().Create(ctx, user); err != nil { | ||
return err | ||
} | ||
|
||
return client.Omni().State().Create(ctx, identity) | ||
} | ||
} | ||
|
||
func init() { | ||
createCmd.PersistentFlags().StringVarP(&createCmdFlags.role, "role", "r", "", "Role to use for the user creation") | ||
createCmd.MarkPersistentFlagRequired("role") //nolint:errcheck | ||
|
||
userCmd.AddCommand(createCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
package user | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/cosi-project/runtime/pkg/resource" | ||
"github.com/cosi-project/runtime/pkg/safe" | ||
"github.com/cosi-project/runtime/pkg/state" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/siderolabs/omni/client/pkg/client" | ||
"github.com/siderolabs/omni/client/pkg/omni/resources" | ||
"github.com/siderolabs/omni/client/pkg/omni/resources/auth" | ||
"github.com/siderolabs/omni/client/pkg/omnictl/internal/access" | ||
) | ||
|
||
// deleteCmd represents the user delete command. | ||
var deleteCmd = &cobra.Command{ | ||
Use: "delete [email1 email2]", | ||
Short: "Delete users.", | ||
Long: `Delete users with the specified emails.`, | ||
Example: "", | ||
Args: cobra.MinimumNArgs(1), | ||
RunE: func(_ *cobra.Command, args []string) error { | ||
return access.WithClient(deleteUsers(args...)) | ||
}, | ||
} | ||
|
||
func deleteUsers(emails ...string) func(ctx context.Context, client *client.Client) error { | ||
return func(ctx context.Context, client *client.Client) error { | ||
toDelete := make([]resource.Pointer, 0, len(emails)*2) | ||
|
||
for _, email := range emails { | ||
identity := auth.NewIdentity(resources.DefaultNamespace, email) | ||
|
||
existing, err := safe.ReaderGetByID[*auth.Identity](ctx, client.Omni().State(), email) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
toDelete = append(toDelete, identity.Metadata(), auth.NewUser(resources.DefaultNamespace, existing.TypedSpec().Value.UserId).Metadata()) | ||
} | ||
|
||
for _, md := range toDelete { | ||
fmt.Printf("tearing down %s %s\n", md.Type(), md.ID()) | ||
|
||
if _, err := client.Omni().State().Teardown(ctx, md); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
for _, md := range toDelete { | ||
_, err := client.Omni().State().WatchFor(ctx, md, state.WithFinalizerEmpty()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = client.Omni().State().Destroy(ctx, md) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fmt.Printf("destroy %s %s\n", md.Type(), md.ID()) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
func init() { | ||
userCmd.AddCommand(deleteCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
package user | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
"slices" | ||
"strings" | ||
"text/tabwriter" | ||
|
||
"github.com/cosi-project/runtime/pkg/resource" | ||
"github.com/cosi-project/runtime/pkg/safe" | ||
"github.com/cosi-project/runtime/pkg/state" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/siderolabs/omni/client/pkg/client" | ||
"github.com/siderolabs/omni/client/pkg/omni/resources/auth" | ||
"github.com/siderolabs/omni/client/pkg/omnictl/internal/access" | ||
) | ||
|
||
// listCmd represents the user list command. | ||
var listCmd = &cobra.Command{ | ||
Use: "list", | ||
Short: "List all users.", | ||
Long: `List all existing users on the Omni instance.`, | ||
Example: "", | ||
Args: cobra.ExactArgs(0), | ||
RunE: func(*cobra.Command, []string) error { | ||
return access.WithClient(listUsers) | ||
}, | ||
} | ||
|
||
func listUsers(ctx context.Context, client *client.Client) error { | ||
identities, err := safe.ReaderListAll[*auth.Identity](ctx, client.Omni().State(), state.WithLabelQuery( | ||
resource.LabelExists(auth.LabelIdentityTypeServiceAccount, resource.NotMatches), | ||
)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
type user struct { | ||
id string | ||
email string | ||
role string | ||
labels string | ||
} | ||
|
||
users, err := safe.ReaderListAll[*auth.User](ctx, client.Omni().State()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
userList := safe.ToSlice(identities, func(identity *auth.Identity) user { | ||
res := user{ | ||
id: identity.TypedSpec().Value.UserId, | ||
email: identity.Metadata().ID(), | ||
} | ||
|
||
u, found := users.Find(func(user *auth.User) bool { | ||
return user.Metadata().ID() == identity.TypedSpec().Value.UserId | ||
}) | ||
if !found { | ||
return res | ||
} | ||
|
||
res.role = u.TypedSpec().Value.Role | ||
|
||
allLabels := identity.Metadata().Labels().Raw() | ||
|
||
samlLabels := make([]string, 0, len(allLabels)) | ||
|
||
for key, value := range allLabels { | ||
if !strings.HasPrefix(key, auth.SAMLLabelPrefix) { | ||
continue | ||
} | ||
|
||
samlLabels = append(samlLabels, strings.TrimPrefix(key, auth.SAMLLabelPrefix)+"="+value) | ||
} | ||
|
||
slices.Sort(samlLabels) | ||
|
||
res.labels = strings.Join(samlLabels, ", ") | ||
|
||
return res | ||
}) | ||
|
||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) | ||
defer w.Flush() //nolint:errcheck | ||
|
||
_, err = fmt.Fprintln(w, "ID\tEMAIL\tROLE\tLABELS") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, user := range userList { | ||
_, err = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", user.id, user.email, user.role, user.labels) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func init() { | ||
userCmd.AddCommand(listCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
package user | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/cosi-project/runtime/pkg/safe" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/siderolabs/omni/client/pkg/client" | ||
"github.com/siderolabs/omni/client/pkg/omni/resources" | ||
"github.com/siderolabs/omni/client/pkg/omni/resources/auth" | ||
"github.com/siderolabs/omni/client/pkg/omnictl/internal/access" | ||
) | ||
|
||
var setRoleCmdFlags struct { | ||
role string | ||
} | ||
|
||
// setRoleCmd represents the user role set command. | ||
var setRoleCmd = &cobra.Command{ | ||
Use: "set-role [email]", | ||
Short: "Update the role of the user.", | ||
Long: `Update the user role.`, | ||
Example: "", | ||
Args: cobra.ExactArgs(1), | ||
RunE: func(_ *cobra.Command, args []string) error { | ||
return access.WithClient(setUserRole(args[0])) | ||
}, | ||
} | ||
|
||
func setUserRole(email string) func(ctx context.Context, client *client.Client) error { | ||
return func(ctx context.Context, client *client.Client) error { | ||
identity, err := safe.ReaderGetByID[*auth.Identity](ctx, client.Omni().State(), email) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, err = safe.StateUpdateWithConflicts(ctx, client.Omni().State(), | ||
auth.NewUser(resources.DefaultNamespace, identity.TypedSpec().Value.UserId).Metadata(), | ||
func(user *auth.User) error { | ||
user.TypedSpec().Value.Role = setRoleCmdFlags.role | ||
|
||
return nil | ||
}, | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
func init() { | ||
setRoleCmd.PersistentFlags().StringVarP(&setRoleCmdFlags.role, "role", "r", "", "Role to use") | ||
setRoleCmd.MarkPersistentFlagRequired("role") //nolint:errcheck | ||
|
||
userCmd.AddCommand(setRoleCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// This Source Code Form is subject to the terms of the Mozilla Public | ||
// License, v. 2.0. If a copy of the MPL was not distributed with this | ||
// file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
|
||
// Package user contains commands related to user operations. | ||
package user | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
// userCmd represents the cluster sub-command. | ||
var userCmd = &cobra.Command{ | ||
Use: "user", | ||
Aliases: []string{"u"}, | ||
Short: "User-related subcommands.", | ||
Long: `Commands to manage users.`, | ||
Example: "", | ||
} | ||
|
||
// RootCmd exposes root cluster command. | ||
func RootCmd() *cobra.Command { | ||
return userCmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.