diff --git a/cmd/authctl/group/set-gid.go b/cmd/authctl/group/set-gid.go index 0ee9b01b50..1d3f3172ad 100644 --- a/cmd/authctl/group/set-gid.go +++ b/cmd/authctl/group/set-gid.go @@ -20,22 +20,19 @@ var setGIDCmd = &cobra.Command{ Short: "Set the GID of a group managed by authd", Long: `Set the GID of a group managed by authd to the specified value. -The new GID value must be unique and non-negative. +The new GID must be unique and non-negative. The command must be run as root. -When a group's GID is changed, any users whose primary group is set to this group -will have their primary group GID updated. The home directories of these users and -files within them owned by the group will be updated to the new GID. +When a group's GID is changed, any users whose primary group is set to this +group will have the GID of their primary group updated. The home directories of +these users, and any files within these directories that are owned by the group, +will be updated to the new GID. Files outside users' home directories are not updated and must be changed manually. Note that changing a GID can be unsafe if files on the system are still owned by the original GID: those files may become accessible to a -different group that is later assigned that GID. - -This command requires root privileges. - -Examples: - authctl group set-gid staff 30000 - authctl group set-gid developers 40000`, +different group that is later assigned that GID.`, + Example: ` # Set the GID of group "staff" to 30000 + authctl group set-gid staff 30000`, Args: cobra.ExactArgs(2), ValidArgsFunction: completion.Groups, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/authctl/group/set-gid_test.go b/cmd/authctl/group/set-gid_test.go index 97d8456a74..d26382f1df 100644 --- a/cmd/authctl/group/set-gid_test.go +++ b/cmd/authctl/group/set-gid_test.go @@ -57,7 +57,7 @@ func TestSetGIDCommand(t *testing.T) { expectedExitCode: int(codes.Unknown), }, "Error_when_gid_is_negative": { - args: []string{"set-gid", "group1", "-1000"}, + args: []string{"set-gid", "group1", "--", "-1000"}, expectedExitCode: 1, }, "Error_when_authd_is_unavailable": { diff --git a/cmd/authctl/group/testdata/golden/TestSetGIDCommand/Error_when_gid_is_negative b/cmd/authctl/group/testdata/golden/TestSetGIDCommand/Error_when_gid_is_negative index bc3e50c790..91f9e8c76a 100644 --- a/cmd/authctl/group/testdata/golden/TestSetGIDCommand/Error_when_gid_is_negative +++ b/cmd/authctl/group/testdata/golden/TestSetGIDCommand/Error_when_gid_is_negative @@ -1,7 +1 @@ -Usage: - authctl group set-gid [flags] - -Flags: - -h, --help help for set-gid - -unknown shorthand flag: '1' in -1000 +failed to parse GID "-1000": invalid syntax diff --git a/cmd/authctl/internal/docgen/main.go b/cmd/authctl/internal/docgen/main.go new file mode 100644 index 0000000000..d35b16048f --- /dev/null +++ b/cmd/authctl/internal/docgen/main.go @@ -0,0 +1,59 @@ +// Package main generates CLI reference documentation. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "strings" + + "github.com/canonical/authd/cmd/authctl/root" + "github.com/spf13/cobra/doc" +) + +func main() { + out := flag.String("out", "./docs/cli", "output directory") + format := flag.String("format", "markdown", "markdown|man|rest") + front := flag.Bool("frontmatter", false, "prepend simple YAML front matter to markdown") + flag.Parse() + + if err := os.MkdirAll(*out, 0o750); err != nil { + log.Fatal(err) + } + + rootCmd := root.RootCmd + rootCmd.DisableAutoGenTag = true // stable, reproducible files (no timestamp footer) + + switch *format { + case "markdown": + if *front { + prep := func(filename string) string { + base := filepath.Base(filename) + name := strings.TrimSuffix(base, filepath.Ext(base)) + title := strings.ReplaceAll(name, "_", " ") + return fmt.Sprintf("---\ntitle: %q\nslug: %q\ndescription: \"CLI reference for %s\"\n---\n\n", title, name, title) + } + link := func(name string) string { return strings.ToLower(name) } + if err := doc.GenMarkdownTreeCustom(rootCmd, *out, prep, link); err != nil { + log.Fatal(err) + } + } else { + if err := doc.GenMarkdownTree(rootCmd, *out); err != nil { + log.Fatal(err) + } + } + case "man": + hdr := &doc.GenManHeader{Title: strings.ToUpper(rootCmd.Name()), Section: "1"} + if err := doc.GenManTree(rootCmd, hdr, *out); err != nil { + log.Fatal(err) + } + case "rest": + if err := doc.GenReSTTree(rootCmd, *out); err != nil { + log.Fatal(err) + } + default: + log.Fatalf("unknown format: %s", *format) + } +} diff --git a/cmd/authctl/main.go b/cmd/authctl/main.go index 5dbdbaa48e..ea154b7ce9 100644 --- a/cmd/authctl/main.go +++ b/cmd/authctl/main.go @@ -4,43 +4,14 @@ package main import ( "os" - "github.com/canonical/authd/cmd/authctl/group" "github.com/canonical/authd/cmd/authctl/internal/log" - "github.com/canonical/authd/cmd/authctl/user" - "github.com/spf13/cobra" + "github.com/canonical/authd/cmd/authctl/root" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -var rootCmd = &cobra.Command{ - Use: "authctl", - Short: "CLI tool to interact with authd", - Long: "authctl is a command-line tool to interact with the authd service for user and group management.", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - // The command was successfully parsed, so we don't want cobra to print usage information on error. - cmd.SilenceUsage = true - }, - CompletionOptions: cobra.CompletionOptions{ - HiddenDefaultCmd: true, - }, - // We handle errors ourselves - SilenceErrors: true, - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { return cmd.Usage() }, -} - -func init() { - // Disable command sorting by name. This makes cobra print the commands in the - // order they are added to the root command and adds the `help` and `completion` - // commands at the end. - cobra.EnableCommandSorting = false - - rootCmd.AddCommand(user.UserCmd) - rootCmd.AddCommand(group.GroupCmd) -} - func main() { - if err := rootCmd.Execute(); err != nil { + if err := root.RootCmd.Execute(); err != nil { s, ok := status.FromError(err) if !ok { // If the error is not a gRPC status, we print it as is. diff --git a/cmd/authctl/root/root.go b/cmd/authctl/root/root.go new file mode 100644 index 0000000000..36883d37dc --- /dev/null +++ b/cmd/authctl/root/root.go @@ -0,0 +1,38 @@ +// Package root contains the root command for authctl. +package root + +import ( + "github.com/canonical/authd/cmd/authctl/group" + "github.com/canonical/authd/cmd/authctl/user" + "github.com/spf13/cobra" +) + +// RootCmd is the root command for authctl. +var RootCmd = &cobra.Command{ + Use: "authctl", + Short: "CLI tool to interact with authd", + Long: "authctl is a command-line tool to interact with the authd service for user and group management.", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // The command was successfully parsed, so we don't want cobra to print usage information on error. + cmd.SilenceUsage = true + }, + CompletionOptions: cobra.CompletionOptions{ + HiddenDefaultCmd: true, + }, + // Avoid the "Auto generated by spf13/cobra" line in the generated markdown docs + DisableAutoGenTag: true, + // We handle errors ourselves + SilenceErrors: true, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { return cmd.Usage() }, +} + +func init() { + // Disable command sorting by name. This makes cobra print the commands in the + // order they are added to the root command and adds the `help` and `completion` + // commands at the end. + cobra.EnableCommandSorting = false + + RootCmd.AddCommand(user.UserCmd) + RootCmd.AddCommand(group.GroupCmd) +} diff --git a/cmd/authctl/user/lock.go b/cmd/authctl/user/lock.go index 11e6522f3e..c0d3caa23e 100644 --- a/cmd/authctl/user/lock.go +++ b/cmd/authctl/user/lock.go @@ -13,6 +13,7 @@ import ( var lockCmd = &cobra.Command{ Use: "lock ", Short: "Lock (disable) a user managed by authd", + Long: `Lock a user so that they cannot log in.`, Args: cobra.ExactArgs(1), ValidArgsFunction: completion.Users, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/authctl/user/set-uid.go b/cmd/authctl/user/set-uid.go index fe3d619f79..6516560020 100644 --- a/cmd/authctl/user/set-uid.go +++ b/cmd/authctl/user/set-uid.go @@ -20,21 +20,17 @@ var setUIDCmd = &cobra.Command{ Short: "Set the UID of a user managed by authd", Long: `Set the UID of a user managed by authd to the specified value. -The new UID value must be unique and non-negative. +The new UID must be unique and non-negative. The command must be run as root. -The user's home directory and any files within it owned by the user will -automatically have their ownership updated to the new UID. +The ownership of the user's home directory, and any files within the directory +that the user owns, will automatically be updated to the new UID. Files outside the user's home directory are not updated and must be changed manually. Note that changing a UID can be unsafe if files on the system are still owned by the original UID: those files may become accessible to a -different account that is later assigned that UID. - -This command requires root privileges. - -Examples: - authctl user set-uid john 15000 - authctl user set-uid alice 20000`, +different account that is later assigned that UID.`, + Example: ` # Set the UID of user "alice" to 15000 + authctl user set-uid alice 15000`, Args: cobra.ExactArgs(2), ValidArgsFunction: setUIDCompletionFunc, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/authctl/user/set-uid_test.go b/cmd/authctl/user/set-uid_test.go index ffdcda0ad3..e4a2ed0765 100644 --- a/cmd/authctl/user/set-uid_test.go +++ b/cmd/authctl/user/set-uid_test.go @@ -57,7 +57,7 @@ func TestSetUIDCommand(t *testing.T) { expectedExitCode: int(codes.Unknown), }, "Error_when_uid_is_negative": { - args: []string{"set-uid", "user1", "-1000"}, + args: []string{"set-uid", "user1", "--", "-1000"}, expectedExitCode: 1, }, "Error_when_authd_is_unavailable": { diff --git a/cmd/authctl/user/testdata/golden/TestSetUIDCommand/Error_when_uid_is_negative b/cmd/authctl/user/testdata/golden/TestSetUIDCommand/Error_when_uid_is_negative index 33c67cbd74..6345a0adf3 100644 --- a/cmd/authctl/user/testdata/golden/TestSetUIDCommand/Error_when_uid_is_negative +++ b/cmd/authctl/user/testdata/golden/TestSetUIDCommand/Error_when_uid_is_negative @@ -1,7 +1 @@ -Usage: - authctl user set-uid [flags] - -Flags: - -h, --help help for set-uid - -unknown shorthand flag: '1' in -1000 +failed to parse UID "-1000": invalid syntax diff --git a/cmd/authctl/user/unlock.go b/cmd/authctl/user/unlock.go index d13410b095..66811a0a0f 100644 --- a/cmd/authctl/user/unlock.go +++ b/cmd/authctl/user/unlock.go @@ -13,6 +13,7 @@ import ( var unlockCmd = &cobra.Command{ Use: "unlock ", Short: "Unlock (enable) a user managed by authd", + Long: `Unlock a locked user so that they can log in again.`, Args: cobra.ExactArgs(1), ValidArgsFunction: completion.Users, RunE: func(cmd *cobra.Command, args []string) error { diff --git a/debian/rules b/debian/rules index 8f3f9bdc89..8e402a44a3 100755 --- a/debian/rules +++ b/debian/rules @@ -46,7 +46,7 @@ export AUTHD_SKIP_ROOT_TESTS := 1 # Defines the targets to be built as part of dh_auto_build export DH_GOLANG_BUILDPKG := $(AUTHD_GO_PACKAGE)/... \ $(NULL) -export DH_GOLANG_EXCLUDES := authd-oidc-brokers/ +export DH_GOLANG_EXCLUDES := authd-oidc-brokers/ cmd/authctl/internal/docgen/ export DH_GOLANG_EXCLUDES_ALL := 1 # We add the required backported version to the $PATH so that if it exists, then diff --git a/docs/.custom_wordlist.txt b/docs/.custom_wordlist.txt index e202c67c26..c013b39e60 100644 --- a/docs/.custom_wordlist.txt +++ b/docs/.custom_wordlist.txt @@ -1,6 +1,7 @@ alice amd auth +authctl authd authd's biometric diff --git a/docs/conf.py b/docs/conf.py index e2b714e5a5..07019b73f0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -373,3 +373,9 @@ 'starter-pack': ('https://canonical-example-product-documentation.readthedocs-hosted.com/en/latest', None), 'sphinxcontrib-mermaid': ('https://sphinxcontrib-mermaid-demo.readthedocs.io/en/latest', None) } + +# Suppress warnings + +suppress_warnings = [ + "myst.header", # Suppress warnings relating to headers of autogenerated Cobra docs +] diff --git a/docs/generate.go b/docs/generate.go new file mode 100644 index 0000000000..9977e4d443 --- /dev/null +++ b/docs/generate.go @@ -0,0 +1,7 @@ +//go:build generate + +// TiCS: disabled // This is a helper file to generate the CLI documentation + +//go:generate sh -c "go run ../cmd/authctl/internal/docgen -format markdown -out reference/cli" + +package docs diff --git a/docs/reference/cli/authctl.md b/docs/reference/cli/authctl.md new file mode 100644 index 0000000000..b6956a4561 --- /dev/null +++ b/docs/reference/cli/authctl.md @@ -0,0 +1,23 @@ +## authctl + +CLI tool to interact with authd + +### Synopsis + +authctl is a command-line tool to interact with the authd service for user and group management. + +``` +authctl [flags] +``` + +### Options + +``` + -h, --help help for authctl +``` + +### SEE ALSO + +* [authctl group](authctl_group.md) - Commands related to groups +* [authctl user](authctl_user.md) - Commands related to users + diff --git a/docs/reference/cli/authctl_group.md b/docs/reference/cli/authctl_group.md new file mode 100644 index 0000000000..c9f3c574e3 --- /dev/null +++ b/docs/reference/cli/authctl_group.md @@ -0,0 +1,19 @@ +## authctl group + +Commands related to groups + +``` +authctl group [flags] +``` + +### Options + +``` + -h, --help help for group +``` + +### SEE ALSO + +* [authctl](authctl.md) - CLI tool to interact with authd +* [authctl group set-gid](authctl_group_set-gid.md) - Set the GID of a group managed by authd + diff --git a/docs/reference/cli/authctl_group_set-gid.md b/docs/reference/cli/authctl_group_set-gid.md new file mode 100644 index 0000000000..3b240c5520 --- /dev/null +++ b/docs/reference/cli/authctl_group_set-gid.md @@ -0,0 +1,41 @@ +## authctl group set-gid + +Set the GID of a group managed by authd + +### Synopsis + +Set the GID of a group managed by authd to the specified value. + +The new GID must be unique and non-negative. The command must be run as root. + +When a group's GID is changed, any users whose primary group is set to this +group will have the GID of their primary group updated. The home directories of +these users, and any files within these directories that are owned by the group, +will be updated to the new GID. + +Files outside users' home directories are not updated and must be changed +manually. Note that changing a GID can be unsafe if files on the system are +still owned by the original GID: those files may become accessible to a +different group that is later assigned that GID. + +``` +authctl group set-gid [flags] +``` + +### Examples + +``` + # Set the GID of group "staff" to 30000 + authctl group set-gid staff 30000 +``` + +### Options + +``` + -h, --help help for set-gid +``` + +### SEE ALSO + +* [authctl group](authctl_group.md) - Commands related to groups + diff --git a/docs/reference/cli/authctl_user.md b/docs/reference/cli/authctl_user.md new file mode 100644 index 0000000000..3a6e5606ae --- /dev/null +++ b/docs/reference/cli/authctl_user.md @@ -0,0 +1,21 @@ +## authctl user + +Commands related to users + +``` +authctl user [flags] +``` + +### Options + +``` + -h, --help help for user +``` + +### SEE ALSO + +* [authctl](authctl.md) - CLI tool to interact with authd +* [authctl user lock](authctl_user_lock.md) - Lock (disable) a user managed by authd +* [authctl user set-uid](authctl_user_set-uid.md) - Set the UID of a user managed by authd +* [authctl user unlock](authctl_user_unlock.md) - Unlock (enable) a user managed by authd + diff --git a/docs/reference/cli/authctl_user_lock.md b/docs/reference/cli/authctl_user_lock.md new file mode 100644 index 0000000000..b44c1614c6 --- /dev/null +++ b/docs/reference/cli/authctl_user_lock.md @@ -0,0 +1,22 @@ +## authctl user lock + +Lock (disable) a user managed by authd + +### Synopsis + +Lock a user so that they cannot log in. + +``` +authctl user lock [flags] +``` + +### Options + +``` + -h, --help help for lock +``` + +### SEE ALSO + +* [authctl user](authctl_user.md) - Commands related to users + diff --git a/docs/reference/cli/authctl_user_set-uid.md b/docs/reference/cli/authctl_user_set-uid.md new file mode 100644 index 0000000000..2716f55e3b --- /dev/null +++ b/docs/reference/cli/authctl_user_set-uid.md @@ -0,0 +1,39 @@ +## authctl user set-uid + +Set the UID of a user managed by authd + +### Synopsis + +Set the UID of a user managed by authd to the specified value. + +The new UID must be unique and non-negative. The command must be run as root. + +The ownership of the user's home directory, and any files within the directory +that the user owns, will automatically be updated to the new UID. + +Files outside the user's home directory are not updated and must be changed +manually. Note that changing a UID can be unsafe if files on the system are +still owned by the original UID: those files may become accessible to a +different account that is later assigned that UID. + +``` +authctl user set-uid [flags] +``` + +### Examples + +``` + # Set the UID of user "alice" to 15000 + authctl user set-uid alice 15000 +``` + +### Options + +``` + -h, --help help for set-uid +``` + +### SEE ALSO + +* [authctl user](authctl_user.md) - Commands related to users + diff --git a/docs/reference/cli/authctl_user_unlock.md b/docs/reference/cli/authctl_user_unlock.md new file mode 100644 index 0000000000..5babeffc5c --- /dev/null +++ b/docs/reference/cli/authctl_user_unlock.md @@ -0,0 +1,22 @@ +## authctl user unlock + +Unlock (enable) a user managed by authd + +### Synopsis + +Unlock a locked user so that they can log in again. + +``` +authctl user unlock [flags] +``` + +### Options + +``` + -h, --help help for unlock +``` + +### SEE ALSO + +* [authctl user](authctl_user.md) - Commands related to users + diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md new file mode 100644 index 0000000000..c9f8a20962 --- /dev/null +++ b/docs/reference/cli/index.md @@ -0,0 +1,35 @@ +# authctl reference + +The `authctl` command line tool is used to manage authd users and groups. + +Available commands: + +```{toctree} +:titlesonly: +:hidden: +authctl +``` + +```{toctree} +:titlesonly: +:hidden: +authctl_user +``` + +```{toctree} +:titlesonly: +authctl_user_lock +authctl_user_unlock +authctl_user_set-uid +``` + +```{toctree} +:titlesonly: +:hidden: +authctl_group +``` + +```{toctree} +:titlesonly: +authctl_group_set-gid +``` diff --git a/docs/reference/index.md b/docs/reference/index.md index 88c4a7593c..f81490548c 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -53,3 +53,15 @@ The documentation includes snippets to get you started. Deploying and configuring authd with Landscape Deploying and configuring authd with cloud-init ``` + +## Command line tool (authctl) + +The `authctl` command line tool is used to manage authd users and groups. + +Available commands: + +```{toctree} +:titlesonly: + +Command line tool (authctl) +``` diff --git a/go.mod b/go.mod index 8233f7b9b9..543332d83d 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect github.com/charmbracelet/x/ansi v0.8.0 // indirect github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect @@ -52,6 +53,7 @@ require ( github.com/otiai10/mint v1.6.3 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect diff --git a/go.sum b/go.sum index 9510801e6c..9316105b90 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,7 @@ github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSg github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -83,6 +84,7 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=