diff --git a/cmd/authelia-gen/cmd_all.go b/cmd/authelia-gen/cmd_all.go new file mode 100644 index 000000000..79b207d69 --- /dev/null +++ b/cmd/authelia-gen/cmd_all.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +func newAllCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "all", + Short: "Run all generators with default options", + RunE: allRunE, + } + + return cmd +} + +func allRunE(cmd *cobra.Command, args []string) (err error) { + for _, subCmd := range cmd.Parent().Commands() { + if subCmd == cmd || subCmd.Use == "completion" || subCmd.Use == "help [command]" { + continue + } + + switch { + case subCmd.RunE != nil: + if err = subCmd.RunE(subCmd, args); err != nil { + return err + } + case subCmd.Run != nil: + subCmd.Run(subCmd, args) + } + } + + return nil +} diff --git a/cmd/authelia-gen/cmd_code.go b/cmd/authelia-gen/cmd_code.go new file mode 100644 index 000000000..244c9de7a --- /dev/null +++ b/cmd/authelia-gen/cmd_code.go @@ -0,0 +1,32 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +func newCodeCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "code", + Short: "Generate code", + RunE: codeRunE, + } + + cmd.AddCommand(newCodeKeysCmd()) + + return cmd +} + +func codeRunE(cmd *cobra.Command, args []string) (err error) { + for _, subCmd := range cmd.Commands() { + switch { + case subCmd.RunE != nil: + if err = subCmd.RunE(subCmd, args); err != nil { + return err + } + case subCmd.Run != nil: + subCmd.Run(subCmd, args) + } + } + + return nil +} diff --git a/cmd/authelia-scripts/cmd_gen.go b/cmd/authelia-gen/cmd_code_keys.go similarity index 57% rename from cmd/authelia-scripts/cmd_gen.go rename to cmd/authelia-gen/cmd_code_keys.go index f57e662e3..801a6a9fa 100644 --- a/cmd/authelia-scripts/cmd_gen.go +++ b/cmd/authelia-gen/cmd_code_keys.go @@ -16,92 +16,63 @@ import ( "github.com/authelia/authelia/v4/internal/configuration/schema" ) -// NewRunGenCmd implements the code generation cobra command. -func NewRunGenCmd() (cmd *cobra.Command) { - cmd = &cobra.Command{ - Use: "gen", - RunE: runGenE, +func newCodeKeysCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "keys", + Short: "Generate the list of valid configuration keys", + RunE: codeKeysRunE, } + cmd.Flags().StringP("file", "f", "./internal/configuration/schema/keys.go", "Sets the path of the keys file") + cmd.Flags().String("package", "schema", "Sets the package name of the keys file") + return cmd } -func runGenE(cmd *cobra.Command, args []string) (err error) { - if err = genConfigurationKeys(); err != nil { +func codeKeysRunE(cmd *cobra.Command, args []string) (err error) { + var ( + file string + + f *os.File + ) + + data := keysTemplateStruct{ + Timestamp: time.Now(), + Keys: readTags("", reflect.TypeOf(schema.Configuration{})), + } + + if file, err = cmd.Flags().GetString("file"); err != nil { return err } - return nil -} - -func genConfigurationKeys() (err error) { - data := loadKeysTemplate() - - f, err := os.Create("./internal/configuration/schema/keys.go") - if err != nil { + if data.Package, err = cmd.Flags().GetString("package"); err != nil { return err } - return keysTemplate.Execute(f, data) + if f, err = os.Create(file); err != nil { + return fmt.Errorf("failed to create file '%s': %w", file, err) + } + + var ( + content []byte + tmpl *template.Template + ) + + if content, err = templatesFS.ReadFile("templates/config_keys.go.tmpl"); err != nil { + return err + } + + if tmpl, err = template.New("keys").Parse(string(content)); err != nil { + return err + } + + return tmpl.Execute(f, data) } -var keysTemplate = template.Must(template.New("keys").Parse(`// Code generated by go generate. DO NOT EDIT. -// -// Run the following command to generate this file: -// go run ./cmd/authelia-scripts gen -// - -package schema - -// Keys represents the detected schema keys. -var Keys = []string{ -{{- range .Keys }} - {{ printf "%q" . }}, -{{- end }} -} -`)) - type keysTemplateStruct struct { Timestamp time.Time Keys []string -} - -func loadKeysTemplate() keysTemplateStruct { - config := schema.Configuration{ - Storage: schema.StorageConfiguration{ - Local: &schema.LocalStorageConfiguration{}, - MySQL: &schema.MySQLStorageConfiguration{}, - PostgreSQL: &schema.PostgreSQLStorageConfiguration{}, - }, - Notifier: schema.NotifierConfiguration{ - FileSystem: &schema.FileSystemNotifierConfiguration{}, - SMTP: &schema.SMTPNotifierConfiguration{ - TLS: &schema.TLSConfig{}, - }, - }, - AuthenticationBackend: schema.AuthenticationBackendConfiguration{ - File: &schema.FileAuthenticationBackendConfiguration{ - Password: &schema.PasswordConfiguration{}, - }, - LDAP: &schema.LDAPAuthenticationBackendConfiguration{ - TLS: &schema.TLSConfig{}, - }, - }, - Session: schema.SessionConfiguration{ - Redis: &schema.RedisSessionConfiguration{ - TLS: &schema.TLSConfig{}, - HighAvailability: &schema.RedisHighAvailabilityConfiguration{}, - }, - }, - IdentityProviders: schema.IdentityProvidersConfiguration{ - OIDC: &schema.OpenIDConnectConfiguration{}, - }, - } - - return keysTemplateStruct{ - Timestamp: time.Now(), - Keys: readTags("", reflect.TypeOf(config)), - } + Package string } var decodedTypes = []reflect.Type{ diff --git a/cmd/authelia-gen/cmd_docs.go b/cmd/authelia-gen/cmd_docs.go new file mode 100644 index 000000000..3384cdc89 --- /dev/null +++ b/cmd/authelia-gen/cmd_docs.go @@ -0,0 +1,33 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +func newDocsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "docs", + Short: "Generate docs", + RunE: docsRunE, + } + + cmd.PersistentFlags().StringP("cwd", "C", "", "Sets the CWD for git commands") + cmd.AddCommand(newDocsCLICmd(), newDocsDateCmd()) + + return cmd +} + +func docsRunE(cmd *cobra.Command, args []string) (err error) { + for _, subCmd := range cmd.Commands() { + switch { + case subCmd.RunE != nil: + if err = subCmd.RunE(subCmd, args); err != nil { + return err + } + case subCmd.Run != nil: + subCmd.Run(subCmd, args) + } + } + + return nil +} diff --git a/cmd/authelia-gen/cmd_docs_cli.go b/cmd/authelia-gen/cmd_docs_cli.go new file mode 100644 index 000000000..1af452b34 --- /dev/null +++ b/cmd/authelia-gen/cmd_docs_cli.go @@ -0,0 +1,154 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" + + cmdscripts "github.com/authelia/authelia/v4/cmd/authelia-scripts/cmd" + "github.com/authelia/authelia/v4/internal/commands" +) + +func newDocsCLICmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "cli", + Short: "Generate CLI docs", + RunE: docsCLIRunE, + } + + cmd.Flags().StringP("directory", "d", "./docs/content/en/reference/cli", "The directory to store the markdown in") + + return cmd +} + +func docsCLIRunE(cmd *cobra.Command, args []string) (err error) { + var root string + + if root, err = cmd.Flags().GetString("directory"); err != nil { + return err + } + + if err = os.MkdirAll(root, 0775); err != nil { + if !os.IsExist(err) { + return err + } + } + + if err = genCLIDoc(commands.NewRootCmd(), filepath.Join(root, "authelia")); err != nil { + return err + } + + if err = genCLIDocWriteIndex(root, "authelia"); err != nil { + return err + } + + if err = genCLIDoc(cmdscripts.NewRootCmd(), filepath.Join(root, "authelia-scripts")); err != nil { + return err + } + + if err = genCLIDocWriteIndex(root, "authelia-scripts"); err != nil { + return err + } + + if err = genCLIDoc(newRootCmd(), filepath.Join(root, "authelia-gen")); err != nil { + return err + } + + if err = genCLIDocWriteIndex(root, "authelia-gen"); err != nil { + return err + } + + return nil +} + +func genCLIDoc(cmd *cobra.Command, path string) (err error) { + if err = os.Mkdir(path, 0755); err != nil { + if !os.IsExist(err) { + return err + } + } + + if err = doc.GenMarkdownTreeCustom(cmd, path, prepend, linker); err != nil { + return err + } + + return nil +} + +func genCLIDocWriteIndex(path, name string) (err error) { + now := time.Now() + + f, err := os.Create(filepath.Join(path, name, "_index.md")) + if err != nil { + return err + } + + weight := 900 + + if name == "authelia" { + weight = 320 + } + + _, err = fmt.Fprintf(f, indexDocs, name, now.Format(dateFmtYAML), "cli-"+name, weight) + + return err +} + +func prepend(input string) string { + now := time.Now() + + pathz := strings.Split(strings.Replace(input, ".md", "", 1), "\\") + parts := strings.Split(pathz[len(pathz)-1], "_") + + cmd := parts[0] + + args := strings.Join(parts, " ") + + weight := 330 + if len(parts) == 1 { + weight = 320 + } + + return fmt.Sprintf(prefixDocs, args, fmt.Sprintf("Reference for the %s command.", args), "", now.Format(dateFmtYAML), "cli-"+cmd, weight) +} + +func linker(input string) string { + return input +} + +const indexDocs = `--- +title: "%s" +description: "" +lead: "" +date: %s +draft: false +images: [] +menu: + reference: + parent: "cli" + identifier: "%s" +weight: %d +toc: true +--- +` + +const prefixDocs = `--- +title: "%s" +description: "%s" +lead: "%s" +date: %s +draft: false +images: [] +menu: + reference: + parent: "%s" +weight: %d +toc: true +--- + +` diff --git a/cmd/authelia-gen/cmd_docs_date.go b/cmd/authelia-gen/cmd_docs_date.go new file mode 100644 index 000000000..f3e677544 --- /dev/null +++ b/cmd/authelia-gen/cmd_docs_date.go @@ -0,0 +1,220 @@ +package main + +import ( + "bufio" + "bytes" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/spf13/cobra" + "gopkg.in/yaml.v3" +) + +func newDocsDateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "date", + Short: "Generate doc dates", + RunE: docsDateRunE, + } + + cmd.Flags().StringP("directory", "d", "./docs/content", "The directory to modify") + cmd.Flags().String("commit-until", "HEAD", "The commit to check the logs until") + cmd.Flags().String("commit-since", "", "The commit to check the logs since") + + return cmd +} + +func docsDateRunE(cmd *cobra.Command, args []string) (err error) { + var ( + dir, cwd, commitUtil, commitSince, commitFilter string + ) + + if dir, err = cmd.Flags().GetString("directory"); err != nil { + return err + } + + if cwd, err = cmd.Flags().GetString("cwd"); err != nil { + return err + } + + if cmd.Flags().Changed("commit-since") { + if commitUtil, err = cmd.Flags().GetString("commit-util"); err != nil { + return err + } + + if commitSince, err = cmd.Flags().GetString("commit-since"); err != nil { + return err + } + + commitFilter = fmt.Sprintf("%s...%s", commitUtil, commitSince) + } + + return filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + if !strings.HasSuffix(info.Name(), ".md") { + return nil + } + + abs, err := filepath.Abs(path) + if err != nil { + return nil + } + + frontmatterBytes := getFrontmatter(abs) + + if frontmatterBytes == nil { + return nil + } + + frontmatter := map[string]interface{}{} + + if err = yaml.Unmarshal(frontmatterBytes, frontmatter); err != nil { + return err + } + + var ( + date time.Time + ) + + if value, ok := frontmatter["date"]; ok { + date = value.(time.Time) + } + + dateGit := getDateFromGit(cwd, abs, commitFilter) + + replaceDates(abs, date, dateGit) + + return nil + }) +} + +var newline = []byte("\n") + +func getDateFromGit(cwd, path, commitFilter string) *time.Time { + var args []string + + if len(cwd) != 0 { + args = append(args, "-C", cwd) + } + + args = append(args, "log") + + if len(commitFilter) != 0 { + args = append(args, commitFilter) + } + + args = append(args, "-1", "--follow", "--diff-filter=A", "--pretty=format:%cD", "--", path) + + return getTimeFromGitCmd(exec.Command("git", args...)) +} + +func getTimeFromGitCmd(cmd *exec.Cmd) *time.Time { + var ( + output []byte + err error + t time.Time + ) + + if output, err = cmd.Output(); err != nil { + return nil + } + + if t, err = time.Parse(dateFmtRFC2822, string(output)); err != nil { + return nil + } + + return &t +} + +func replaceDates(path string, date time.Time, dateGit *time.Time) { + f, err := os.Open(path) + if err != nil { + return + } + + buf := bytes.Buffer{} + + scanner := bufio.NewScanner(f) + + var dateGitLine string + + dateLine := fmt.Sprintf("date: %s", date.Format(dateFmtYAML)) + + if dateGit != nil { + dateGitLine = fmt.Sprintf("date: %s", dateGit.Format(dateFmtYAML)) + } else { + dateGitLine = dateLine + } + + found := 0 + + frontmatter := 0 + + for scanner.Scan() { + if found < 2 && frontmatter < 2 { + switch { + case scanner.Text() == frontmatterDelimiterLine: + buf.Write(scanner.Bytes()) + frontmatter++ + case frontmatter != 0 && strings.HasPrefix(scanner.Text(), "date: "): + buf.WriteString(dateGitLine) + found++ + default: + buf.Write(scanner.Bytes()) + } + } else { + buf.Write(scanner.Bytes()) + } + + buf.Write(newline) + } + + f.Close() + + newF, err := os.Create(path) + if err != nil { + return + } + + _, _ = buf.WriteTo(newF) + + newF.Close() +} + +func getFrontmatter(path string) []byte { + f, err := os.Open(path) + if err != nil { + return nil + } + + defer f.Close() + + scanner := bufio.NewScanner(f) + + var start bool + + buf := bytes.Buffer{} + + for scanner.Scan() { + if start { + if scanner.Text() == frontmatterDelimiterLine { + break + } + + buf.Write(scanner.Bytes()) + buf.Write(newline) + } else if scanner.Text() == frontmatterDelimiterLine { + start = true + } + } + + return buf.Bytes() +} diff --git a/cmd/authelia-gen/const.go b/cmd/authelia-gen/const.go new file mode 100644 index 000000000..71f3ada18 --- /dev/null +++ b/cmd/authelia-gen/const.go @@ -0,0 +1,7 @@ +package main + +const ( + dateFmtRFC2822 = "Mon, _2 Jan 2006 15:04:05 -0700" + dateFmtYAML = "2006-01-02T15:04:05-07:00" + frontmatterDelimiterLine = "---" +) diff --git a/cmd/authelia-gen/main.go b/cmd/authelia-gen/main.go new file mode 100644 index 000000000..daf2e9b7d --- /dev/null +++ b/cmd/authelia-gen/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "embed" + + "github.com/spf13/cobra" +) + +//go:embed templates/* +var templatesFS embed.FS + +func main() { + if err := newRootCmd().Execute(); err != nil { + panic(err) + } +} + +func newRootCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "authelia-gen", + Short: "Authelia's generator tooling", + } + + cmd.AddCommand(newAllCmd(), newCodeCmd(), newDocsCmd()) + + return cmd +} diff --git a/cmd/authelia-gen/templates/config_keys.go.tmpl b/cmd/authelia-gen/templates/config_keys.go.tmpl new file mode 100644 index 000000000..ee8e73451 --- /dev/null +++ b/cmd/authelia-gen/templates/config_keys.go.tmpl @@ -0,0 +1,14 @@ +// Code generated by go generate. DO NOT EDIT. +// +// Run the following command to generate this file: +// go run ./cmd/authelia-gen code keys +// + +package {{ .Package }} + +// Keys is a list of valid schema keys detected by reflecting over a schema.Configuration struct. +var Keys = []string{ + {{- range .Keys }} + {{ printf "%q" . }}, + {{- end }} +} diff --git a/cmd/authelia-scripts/cmd_bootstrap.go b/cmd/authelia-scripts/cmd/bootstrap.go similarity index 94% rename from cmd/authelia-scripts/cmd_bootstrap.go rename to cmd/authelia-scripts/cmd/bootstrap.go index 627a5b50d..d0c397358 100644 --- a/cmd/authelia-scripts/cmd_bootstrap.go +++ b/cmd/authelia-scripts/cmd/bootstrap.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "fmt" @@ -12,10 +12,53 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -// HostEntry represents an entry in /etc/hosts. -type HostEntry struct { - Domain string - IP string +func newBootstrapCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "bootstrap", + Short: cmdBootstrapShort, + Long: cmdBootstrapLong, + Example: cmdBootstrapExample, + Args: cobra.NoArgs, + Run: cmdBootstrapRun, + } + + return cmd +} + +func cmdBootstrapRun(_ *cobra.Command, _ []string) { + bootstrapPrintln("Checking command installation...") + checkCommandExist("node", "Follow installation guidelines from https://nodejs.org/en/download/package-manager/ or download installer from https://nodejs.org/en/download/") + checkCommandExist("pnpm", "Follow installation guidelines from https://pnpm.io/installation") + checkCommandExist("docker", "Follow installation guidelines from https://docs.docker.com/get-docker/") + checkCommandExist("docker-compose", "Follow installation guidelines from https://docs.docker.com/compose/install/") + + bootstrapPrintln("Getting versions of tools") + readVersions() + + bootstrapPrintln("Checking if GOPATH is set") + + goPathFound := false + + for _, v := range os.Environ() { + if strings.HasPrefix(v, "GOPATH=") { + goPathFound = true + break + } + } + + if !goPathFound { + log.Fatal("GOPATH is not set") + } + + createTemporaryDirectory() + createPNPMDirectory() + + bootstrapPrintln("Preparing /etc/hosts to serve subdomains of example.com...") + prepareHostsFile() + + fmt.Println() + bootstrapPrintln("Run 'authelia-scripts suites setup Standalone' to start Authelia and visit https://home.example.com:8080.") + bootstrapPrintln("More details at https://github.com/authelia/authelia/blob/master/docs/getting-started.md") } var hostEntries = []HostEntry{ @@ -78,9 +121,8 @@ func runCommand(cmd string, args ...string) { func checkCommandExist(cmd string, resolutionHint string) { fmt.Print("Checking if '" + cmd + "' command is installed...") command := exec.Command("bash", "-c", "command -v "+cmd) //nolint:gosec // Used only in development. - err := command.Run() - if err != nil { + if command.Run() != nil { msg := "[ERROR] You must install " + cmd + " on your machine." if resolutionHint != "" { msg += fmt.Sprintf(" %s", resolutionHint) @@ -214,40 +256,3 @@ func readVersions() { readVersion("docker", "--version") readVersion("docker-compose", "version") } - -// Bootstrap bootstrap authelia dev environment. -func Bootstrap(cobraCmd *cobra.Command, args []string) { - bootstrapPrintln("Checking command installation...") - checkCommandExist("node", "Follow installation guidelines from https://nodejs.org/en/download/package-manager/ or download installer from https://nodejs.org/en/download/") - checkCommandExist("pnpm", "Follow installation guidelines from https://pnpm.io/installation") - checkCommandExist("docker", "Follow installation guidelines from https://docs.docker.com/get-docker/") - checkCommandExist("docker-compose", "Follow installation guidelines from https://docs.docker.com/compose/install/") - - bootstrapPrintln("Getting versions of tools") - readVersions() - - bootstrapPrintln("Checking if GOPATH is set") - - goPathFound := false - - for _, v := range os.Environ() { - if strings.HasPrefix(v, "GOPATH=") { - goPathFound = true - break - } - } - - if !goPathFound { - log.Fatal("GOPATH is not set") - } - - createTemporaryDirectory() - createPNPMDirectory() - - bootstrapPrintln("Preparing /etc/hosts to serve subdomains of example.com...") - prepareHostsFile() - - fmt.Println() - bootstrapPrintln("Run 'authelia-scripts suites setup Standalone' to start Authelia and visit https://home.example.com:8080.") - bootstrapPrintln("More details at https://github.com/authelia/authelia/blob/master/docs/getting-started.md") -} diff --git a/cmd/authelia-scripts/cmd_build.go b/cmd/authelia-scripts/cmd/build.go similarity index 54% rename from cmd/authelia-scripts/cmd_build.go rename to cmd/authelia-scripts/cmd/build.go index 35c65ee46..70134ba53 100644 --- a/cmd/authelia-scripts/cmd_build.go +++ b/cmd/authelia-scripts/cmd/build.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "os" @@ -12,56 +12,114 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -func buildAutheliaBinary(xflags []string, buildkite bool) { +func newBuildCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "build", + Short: cmdBuildShort, + Long: cmdBuildLong, + Example: cmdBuildExample, + Args: cobra.NoArgs, + Run: cmdBuildRun, + } + + return cmd +} + +func cmdBuildRun(cobraCmd *cobra.Command, args []string) { + branch := os.Getenv("BUILDKITE_BRANCH") + + if strings.HasPrefix(branch, "renovate/") { + buildFrontend(branch) + log.Info("Skip building Authelia for deps...") + os.Exit(0) + } + + log.Info("Building Authelia...") + + cmdCleanRun(cobraCmd, args) + + xflags, err := getXFlags(branch, os.Getenv("BUILDKITE_BUILD_NUMBER"), "") + if err != nil { + log.Fatal(err) + } + + log.Debug("Creating `" + OutputDir + "` directory") + + if err = os.MkdirAll(OutputDir, os.ModePerm); err != nil { + log.Fatal(err) + } + + log.Debug("Building Authelia frontend...") + buildFrontend(branch) + + log.Debug("Building swagger-ui frontend...") + buildSwagger() + + buildkite, _ := cobraCmd.Flags().GetBool("buildkite") + if buildkite { - var wg sync.WaitGroup + log.Info("Building Authelia Go binaries with gox...") - s := time.Now() - - wg.Add(2) - - go func() { - defer wg.Done() - - cmd := utils.CommandWithStdout("gox", "-output={{.Dir}}-{{.OS}}-{{.Arch}}-musl", "-buildmode=pie", "-trimpath", "-cgo", "-ldflags=-linkmode=external -s -w "+strings.Join(xflags, " "), "-osarch=linux/amd64 linux/arm linux/arm64", "./cmd/authelia/") - - cmd.Env = append(os.Environ(), - "CGO_CPPFLAGS=-D_FORTIFY_SOURCE=2 -fstack-protector-strong", "CGO_LDFLAGS=-Wl,-z,relro,-z,now", - "GOX_LINUX_ARM_CC=arm-linux-musleabihf-gcc", "GOX_LINUX_ARM64_CC=aarch64-linux-musl-gcc") - - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - }() - - go func() { - defer wg.Done() - - cmd := utils.CommandWithStdout("bash", "-c", "docker run --rm -e GOX_LINUX_ARM_CC=arm-linux-gnueabihf-gcc -e GOX_LINUX_ARM64_CC=aarch64-linux-gnu-gcc -e GOX_FREEBSD_AMD64_CC=x86_64-pc-freebsd13-gcc -v ${PWD}:/workdir -v /buildkite/.go:/root/go authelia/crossbuild "+ - "gox -output={{.Dir}}-{{.OS}}-{{.Arch}} -buildmode=pie -trimpath -cgo -ldflags=\"-linkmode=external -s -w "+strings.Join(xflags, " ")+"\" -osarch=\"linux/amd64 linux/arm linux/arm64 freebsd/amd64\" ./cmd/authelia/") - - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - }() - - wg.Wait() - - e := time.Since(s) - - log.Debugf("Binary compilation completed in %s.", e) + buildAutheliaBinaryGOX(xflags) } else { - cmd := utils.CommandWithStdout("go", "build", "-buildmode=pie", "-trimpath", "-o", OutputDir+"/authelia", "-ldflags", "-linkmode=external -s -w "+strings.Join(xflags, " "), "./cmd/authelia/") + log.Info("Building Authelia Go binary...") + + buildAutheliaBinaryGO(xflags) + } + + cleanAssets() +} + +func buildAutheliaBinaryGOX(xflags []string) { + var wg sync.WaitGroup + + s := time.Now() + + wg.Add(2) + + go func() { + defer wg.Done() + + cmd := utils.CommandWithStdout("gox", "-output={{.Dir}}-{{.OS}}-{{.Arch}}-musl", "-buildmode=pie", "-trimpath", "-cgo", "-ldflags=-linkmode=external -s -w "+strings.Join(xflags, " "), "-osarch=linux/amd64 linux/arm linux/arm64", "./cmd/authelia/") cmd.Env = append(os.Environ(), - "CGO_CPPFLAGS=-D_FORTIFY_SOURCE=2 -fstack-protector-strong", "CGO_LDFLAGS=-Wl,-z,relro,-z,now") + "CGO_CPPFLAGS=-D_FORTIFY_SOURCE=2 -fstack-protector-strong", "CGO_LDFLAGS=-Wl,-z,relro,-z,now", + "GOX_LINUX_ARM_CC=arm-linux-musleabihf-gcc", "GOX_LINUX_ARM64_CC=aarch64-linux-musl-gcc") err := cmd.Run() if err != nil { log.Fatal(err) } + }() + + go func() { + defer wg.Done() + + cmd := utils.CommandWithStdout("bash", "-c", "docker run --rm -e GOX_LINUX_ARM_CC=arm-linux-gnueabihf-gcc -e GOX_LINUX_ARM64_CC=aarch64-linux-gnu-gcc -e GOX_FREEBSD_AMD64_CC=x86_64-pc-freebsd13-gcc -v ${PWD}:/workdir -v /buildkite/.go:/root/go authelia/crossbuild "+ + "gox -output={{.Dir}}-{{.OS}}-{{.Arch}} -buildmode=pie -trimpath -cgo -ldflags=\"-linkmode=external -s -w "+strings.Join(xflags, " ")+"\" -osarch=\"linux/amd64 linux/arm linux/arm64 freebsd/amd64\" ./cmd/authelia/") + + err := cmd.Run() + if err != nil { + log.Fatal(err) + } + }() + + wg.Wait() + + e := time.Since(s) + + log.Debugf("Binary compilation completed in %s.", e) +} + +func buildAutheliaBinaryGO(xflags []string) { + cmd := utils.CommandWithStdout("go", "build", "-buildmode=pie", "-trimpath", "-o", OutputDir+"/authelia", "-ldflags", "-linkmode=external -s -w "+strings.Join(xflags, " "), "./cmd/authelia/") + + cmd.Env = append(os.Environ(), + "CGO_CPPFLAGS=-D_FORTIFY_SOURCE=2 -fstack-protector-strong", "CGO_LDFLAGS=-Wl,-z,relro,-z,now") + + err := cmd.Run() + if err != nil { + log.Fatal(err) } } @@ -133,47 +191,3 @@ func cleanAssets() { log.Fatal(err) } } - -// Build build Authelia. -func Build(cobraCmd *cobra.Command, args []string) { - buildkite, _ := cobraCmd.Flags().GetBool("buildkite") - branch := os.Getenv("BUILDKITE_BRANCH") - - if strings.HasPrefix(branch, "renovate/") { - buildFrontend(branch) - log.Info("Skip building Authelia for deps...") - os.Exit(0) - } - - log.Info("Building Authelia...") - - Clean(cobraCmd, args) - - xflags, err := getXFlags(branch, os.Getenv("BUILDKITE_BUILD_NUMBER"), "") - if err != nil { - log.Fatal(err) - } - - log.Debug("Creating `" + OutputDir + "` directory") - err = os.MkdirAll(OutputDir, os.ModePerm) - - if err != nil { - log.Fatal(err) - } - - log.Debug("Building Authelia frontend...") - buildFrontend(branch) - - log.Debug("Building swagger-ui frontend...") - buildSwagger() - - if buildkite { - log.Debug("Building Authelia Go binaries with gox...") - } else { - log.Debug("Building Authelia Go binary...") - } - - buildAutheliaBinary(xflags, buildkite) - - cleanAssets() -} diff --git a/cmd/authelia-scripts/cmd_ci.go b/cmd/authelia-scripts/cmd/ci.go similarity index 65% rename from cmd/authelia-scripts/cmd_ci.go rename to cmd/authelia-scripts/cmd/ci.go index a6aecf7d8..ab01f40ae 100644 --- a/cmd/authelia-scripts/cmd_ci.go +++ b/cmd/authelia-scripts/cmd/ci.go @@ -1,4 +1,4 @@ -package main +package cmd import ( log "github.com/sirupsen/logrus" @@ -7,12 +7,23 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -// RunCI run the CI scripts. -func RunCI(cmd *cobra.Command, args []string) { +func newCICmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "ci", + Short: cmdCIShort, + Long: cmdCILong, + Example: cmdCIExample, + Args: cobra.NoArgs, + Run: cmdCIRun, + } + + return cmd +} + +func cmdCIRun(cmd *cobra.Command, _ []string) { log.Info("=====> Build stage <=====") - buildkite, _ := cmd.Flags().GetBool("buildkite") - if buildkite { + if buildkite, _ := cmd.Flags().GetBool("buildkite"); buildkite { if err := utils.CommandWithStdout("authelia-scripts", "--log-level", "debug", "--buildkite", "build").Run(); err != nil { log.Fatal(err) } diff --git a/cmd/authelia-scripts/cmd/clean.go b/cmd/authelia-scripts/cmd/clean.go new file mode 100644 index 000000000..9a1a0cabd --- /dev/null +++ b/cmd/authelia-scripts/cmd/clean.go @@ -0,0 +1,30 @@ +package cmd + +import ( + "os" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func newCleanCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "clean", + Short: cmdCleanShort, + Long: cmdCleanLong, + Example: cmdCleanExample, + Args: cobra.NoArgs, + Run: cmdCleanRun, + } + + return cmd +} + +func cmdCleanRun(_ *cobra.Command, _ []string) { + log.Debug("Removing `" + OutputDir + "` directory") + err := os.RemoveAll(OutputDir) + + if err != nil { + panic(err) + } +} diff --git a/cmd/authelia-scripts/cmd/const.go b/cmd/authelia-scripts/cmd/const.go new file mode 100644 index 000000000..90fc28ccc --- /dev/null +++ b/cmd/authelia-scripts/cmd/const.go @@ -0,0 +1,128 @@ +package cmd + +// OutputDir the output directory where the built version of Authelia is located. +var OutputDir = "dist" + +// DockerImageName the official name of Authelia docker image. +var DockerImageName = "authelia/authelia" + +// IntermediateDockerImageName local name of the docker image. +var IntermediateDockerImageName = "authelia:dist" + +const dockerhub = "docker.io" +const ghcr = "ghcr.io" + +const masterTag = "master" +const stringFalse = "false" +const webDirectory = "web" + +const fmtLDFLAGSX = "-X 'github.com/authelia/authelia/v4/internal/utils.%s=%s'" + +const ( + cmdRootShort = "A utility used in the Authelia development process." + + cmdRootLong = `The authelia-scripts utility is utilized by developers and the CI/CD pipeline for configuring +testing suites and various other aspects of the environment. + +It can be used to automate or manually run unit testing, integration testing, etc.` + + cmdRootExample = `authelia-scripts help` + + cmdBootstrapShort = "Prepare environment for development and testing" + + cmdBootstrapLong = `Prepare environment for development and testing.` + + cmdBootstrapExample = `authelia-scripts bootstrap` + + cmdBuildShort = "Build Authelia binary and static assets" + + cmdBuildLong = `Build Authelia binary and static assets.` + + cmdBuildExample = `authelia-scripts build` + + cmdCleanShort = "Clean build artifacts" + + cmdCleanLong = `Clean build artifacts.` + + cmdCleanExample = `authelia-scripts clean` + + cmdCIShort = "Run the continuous integration script" + + cmdCILong = `Run the continuous integration script.` + + cmdCIExample = `authelia-scripts ci` + + cmdDockerShort = "Commands related to building and publishing docker image" + + cmdDockerLong = `Commands related to building and publishing docker image.` + + cmdDockerExample = `authelia-scripts docker` + + cmdDockerBuildShort = "Build the docker image of Authelia" + + cmdDockerBuildLong = `Build the docker image of Authelia.` + + cmdDockerBuildExample = `authelia-scripts docker build` + + cmdDockerPushManifestShort = "Push Authelia docker manifest to the Docker registries" + + cmdDockerPushManifestLong = `Push Authelia docker manifest to the Docker registries.` + + cmdDockerPushManifestExample = `authelia-scripts docker push-manifest` + + cmdServeShort = "Serve compiled version of Authelia" + + cmdServeLong = `Serve compiled version of Authelia.` + + cmdServeExample = `authelia-scripts serve test.yml` + + cmdSuitesShort = "Commands related to suites management" + + cmdSuitesLong = `Commands related to suites management.` + + cmdSuitesExample = `authelia-scripts suites` + + cmdSuitesListShort = "List available suites" + + cmdSuitesListLong = `List available suites. + +Suites can be ran with the authelia-scripts suites test [suite] command.` + + cmdSuitesListExample = `authelia-scripts suites list` + + cmdSuitesTestShort = "Run a test suite" + + cmdSuitesTestLong = `Run a test suite. + +Suites can be listed with the authelia-scripts suites list command.` + + cmdSuitesTestExample = `authelia-scripts suites test Standalone` + + cmdSuitesSetupShort = "Setup a test suite environment" + + cmdSuitesSetupLong = `Setup a test suite environment. + +Suites can be listed with the authelia-scripts suites list command.` + + cmdSuitesSetupExample = `authelia-scripts suites setup Standalone` + + cmdSuitesTeardownShort = "Teardown a test suite environment" + + cmdSuitesTeardownLong = `Teardown a test suite environment. + +Suites can be listed with the authelia-scripts suites list command.` + + cmdSuitesTeardownExample = `authelia-scripts suites setup Standalone` + + cmdUnitTestShort = "Run unit tests" + + cmdUnitTestLong = `Run unit tests.` + + cmdUnitTestExample = `authelia-scripts unittest` + + cmdXFlagsShort = "Generate X LDFlags for building Authelia" + + cmdXFlagsLong = `Generate X LDFlags for building Authelia.` + + cmdXFlagsExample = `authelia-scripts xflags` +) diff --git a/cmd/authelia-scripts/cmd_docker.go b/cmd/authelia-scripts/cmd/docker.go similarity index 70% rename from cmd/authelia-scripts/cmd_docker.go rename to cmd/authelia-scripts/cmd/docker.go index fb90414eb..7695fc2e8 100644 --- a/cmd/authelia-scripts/cmd_docker.go +++ b/cmd/authelia-scripts/cmd/docker.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "errors" @@ -23,8 +23,103 @@ var ignoredSuffixes = regexp.MustCompile("alpha|beta") var publicRepo = regexp.MustCompile(`.*:.*`) var tags = dockerTags.FindStringSubmatch(ciTag) -func init() { - DockerBuildCmd.PersistentFlags().StringVar(&container, "container", defaultContainer, "target container among: "+strings.Join(containers, ", ")) +func newDockerCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "docker", + Short: cmdDockerShort, + Long: cmdDockerLong, + Example: cmdDockerExample, + Args: cobra.NoArgs, + Run: cmdDockerBuildRun, + } + + cmd.AddCommand(newDockerBuildCmd(), newDockerPushManifestCmd()) + + return cmd +} + +func newDockerBuildCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "build", + Short: cmdDockerBuildShort, + Long: cmdDockerBuildLong, + Example: cmdDockerBuildExample, + Args: cobra.NoArgs, + Run: cmdDockerBuildRun, + } + + cmd.PersistentFlags().StringVar(&container, "container", defaultContainer, "target container among: "+strings.Join(containers, ", ")) + + return cmd +} + +func newDockerPushManifestCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "push-manifest", + Short: cmdDockerPushManifestShort, + Long: cmdDockerPushManifestLong, + Example: cmdDockerPushManifestExample, + Args: cobra.NoArgs, + Run: cmdDockerPushManifestRun, + } + + return cmd +} + +func cmdDockerBuildRun(_ *cobra.Command, _ []string) { + log.Infof("Building Docker image %s...", DockerImageName) + checkContainerIsSupported(container) + err := dockerBuildOfficialImage(container) + + if err != nil { + log.Fatal(err) + } + + docker := &Docker{} + err = docker.Tag(IntermediateDockerImageName, DockerImageName) + + if err != nil { + log.Fatal(err) + } +} + +func cmdDockerPushManifestRun(_ *cobra.Command, _ []string) { + docker := &Docker{} + + switch { + case ciTag != "": + if len(tags) == 4 { + log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) + login(docker, dockerhub) + login(docker, ghcr) + deployManifest(docker, tags[1]) + publishDockerReadme(docker) + + if !ignoredSuffixes.MatchString(ciTag) { + deployManifest(docker, tags[2]) + deployManifest(docker, tags[3]) + deployManifest(docker, "latest") + publishDockerReadme(docker) + } + } else { + log.Fatal("Docker manifest will not be published, the specified tag does not conform to the standard") + } + case ciBranch != masterTag && !publicRepo.MatchString(ciBranch): + login(docker, dockerhub) + login(docker, ghcr) + deployManifest(docker, ciBranch) + case ciBranch != masterTag && publicRepo.MatchString(ciBranch): + login(docker, dockerhub) + login(docker, ghcr) + deployManifest(docker, "PR"+ciPullRequest) + case ciBranch == masterTag && ciPullRequest == stringFalse: + login(docker, dockerhub) + login(docker, ghcr) + deployManifest(docker, "master") + publishDockerReadme(docker) + default: + log.Info("Docker manifest will not be published") + } } func checkContainerIsSupported(container string) { @@ -51,37 +146,6 @@ func dockerBuildOfficialImage(arch string) error { strings.Join(flags, " ")) } -// DockerBuildCmd Command for building docker image of Authelia. -var DockerBuildCmd = &cobra.Command{ - Use: "build", - Short: "Build the docker image of Authelia", - Run: func(cmd *cobra.Command, args []string) { - log.Infof("Building Docker image %s...", DockerImageName) - checkContainerIsSupported(container) - err := dockerBuildOfficialImage(container) - - if err != nil { - log.Fatal(err) - } - - docker := &Docker{} - err = docker.Tag(IntermediateDockerImageName, DockerImageName) - - if err != nil { - log.Fatal(err) - } - }, -} - -// DockerManifestCmd Command for pushing Authelia docker manifest to DockerHub. -var DockerManifestCmd = &cobra.Command{ - Use: "push-manifest", - Short: "Publish Authelia docker manifest to Docker Hub", - Run: func(cmd *cobra.Command, args []string) { - publishDockerManifest() - }, -} - func login(docker *Docker, registry string) { username := "" password := "" @@ -122,45 +186,6 @@ func deployManifest(docker *Docker, tag string) { } } -func publishDockerManifest() { - docker := &Docker{} - - switch { - case ciTag != "": - if len(tags) == 4 { - log.Infof("Detected tags: '%s' | '%s' | '%s'", tags[1], tags[2], tags[3]) - login(docker, dockerhub) - login(docker, ghcr) - deployManifest(docker, tags[1]) - publishDockerReadme(docker) - - if !ignoredSuffixes.MatchString(ciTag) { - deployManifest(docker, tags[2]) - deployManifest(docker, tags[3]) - deployManifest(docker, "latest") - publishDockerReadme(docker) - } - } else { - log.Fatal("Docker manifest will not be published, the specified tag does not conform to the standard") - } - case ciBranch != masterTag && !publicRepo.MatchString(ciBranch): - login(docker, dockerhub) - login(docker, ghcr) - deployManifest(docker, ciBranch) - case ciBranch != masterTag && publicRepo.MatchString(ciBranch): - login(docker, dockerhub) - login(docker, ghcr) - deployManifest(docker, "PR"+ciPullRequest) - case ciBranch == masterTag && ciPullRequest == stringFalse: - login(docker, dockerhub) - login(docker, ghcr) - deployManifest(docker, "master") - publishDockerReadme(docker) - default: - log.Info("Docker manifest will not be published") - } -} - func publishDockerReadme(docker *Docker) { log.Info("Docker pushing README.md to Docker Hub") diff --git a/cmd/authelia-scripts/cmd/errors.go b/cmd/authelia-scripts/cmd/errors.go new file mode 100644 index 000000000..f97fb2374 --- /dev/null +++ b/cmd/authelia-scripts/cmd/errors.go @@ -0,0 +1,11 @@ +package cmd + +import ( + "errors" +) + +// ErrNotAvailableSuite error raised when suite is not available. +var ErrNotAvailableSuite = errors.New("unavailable suite") + +// ErrNoRunningSuite error raised when no suite is running. +var ErrNoRunningSuite = errors.New("no running suite") diff --git a/cmd/authelia-scripts/helpers.go b/cmd/authelia-scripts/cmd/helpers.go similarity index 99% rename from cmd/authelia-scripts/helpers.go rename to cmd/authelia-scripts/cmd/helpers.go index 3e74ecd44..728ade11e 100644 --- a/cmd/authelia-scripts/helpers.go +++ b/cmd/authelia-scripts/cmd/helpers.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "fmt" diff --git a/cmd/authelia-scripts/docker.go b/cmd/authelia-scripts/cmd/helpers_docker.go similarity index 99% rename from cmd/authelia-scripts/docker.go rename to cmd/authelia-scripts/cmd/helpers_docker.go index c9ec305d8..89d098238 100644 --- a/cmd/authelia-scripts/docker.go +++ b/cmd/authelia-scripts/cmd/helpers_docker.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "github.com/authelia/authelia/v4/internal/utils" diff --git a/cmd/authelia-scripts/cmd/log.go b/cmd/authelia-scripts/cmd/log.go new file mode 100644 index 000000000..ebedc7d3f --- /dev/null +++ b/cmd/authelia-scripts/cmd/log.go @@ -0,0 +1,17 @@ +package cmd + +import ( + log "github.com/sirupsen/logrus" +) + +var logLevel string + +func levelStringToLevel(level string) log.Level { + if level == "debug" { + return log.DebugLevel + } else if level == "warning" { + return log.WarnLevel + } + + return log.InfoLevel +} diff --git a/cmd/authelia-scripts/cmd/root.go b/cmd/authelia-scripts/cmd/root.go new file mode 100644 index 000000000..3d49a4136 --- /dev/null +++ b/cmd/authelia-scripts/cmd/root.go @@ -0,0 +1,29 @@ +package cmd + +import ( + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +// NewRootCmd returns the root authelia-scripts cmd. +func NewRootCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "authelia-scripts", + Short: cmdRootShort, + Long: cmdRootLong, + Example: cmdRootExample, + } + + cmd.PersistentFlags().Bool("buildkite", false, "Set CI flag for Buildkite") + cmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Set the log level for the command") + + cmd.AddCommand(newBootstrapCmd(), newBuildCmd(), newCleanCmd(), newCICmd(), newDockerCmd(), newServeCmd(), newSuitesCmd(), newUnitTestCmd(), newXFlagsCmd()) + + cobra.OnInitialize(cmdRootInit) + + return cmd +} + +func cmdRootInit() { + log.SetLevel(levelStringToLevel(logLevel)) +} diff --git a/cmd/authelia-scripts/cmd/serve.go b/cmd/authelia-scripts/cmd/serve.go new file mode 100644 index 000000000..249415447 --- /dev/null +++ b/cmd/authelia-scripts/cmd/serve.go @@ -0,0 +1,27 @@ +package cmd + +import ( + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + + "github.com/authelia/authelia/v4/internal/utils" +) + +func newServeCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "serve [config]", + Short: cmdServeShort, + Long: cmdServeLong, + Example: cmdServeExample, + Args: cobra.MinimumNArgs(1), + Run: cmdServeRun, + } + + return cmd +} + +func cmdServeRun(_ *cobra.Command, args []string) { + log.Infof("Running Authelia with config %s...", args[0]) + execCmd := utils.CommandWithStdout(OutputDir+"/authelia", "--config", args[0]) + utils.RunCommandUntilCtrlC(execCmd) +} diff --git a/cmd/authelia-scripts/cmd_suites.go b/cmd/authelia-scripts/cmd/suites.go similarity index 69% rename from cmd/authelia-scripts/cmd_suites.go rename to cmd/authelia-scripts/cmd/suites.go index 07350fc82..cf1391dff 100644 --- a/cmd/authelia-scripts/cmd_suites.go +++ b/cmd/authelia-scripts/cmd/suites.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "errors" @@ -17,92 +17,156 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -// ErrNotAvailableSuite error raised when suite is not available. -var ErrNotAvailableSuite = errors.New("unavailable suite") +var ( + runningSuiteFile = ".suite" + failfast, headless bool + testPattern string +) -// ErrNoRunningSuite error raised when no suite is running. -var ErrNoRunningSuite = errors.New("no running suite") +func newSuitesCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "suites", + Short: cmdSuitesShort, + Long: cmdSuitesLong, + Example: cmdSuitesExample, + Run: cmdSuitesListRun, + Args: cobra.NoArgs, + } -// runningSuiteFile name of the file containing the currently running suite. -var runningSuiteFile = ".suite" + cmd.AddCommand(newSuitesListCmd(), newSuitesSetupCmd(), newSuitesTestCmd(), newSuitesTeardownCmd()) -var failfast bool -var headless bool -var testPattern string - -func init() { - SuitesTestCmd.Flags().BoolVar(&failfast, "failfast", false, "Stops tests on first failure") - SuitesTestCmd.Flags().BoolVar(&headless, "headless", false, "Run tests in headless mode") - SuitesTestCmd.Flags().StringVar(&testPattern, "test", "", "The single test to run") + return cmd } -// SuitesListCmd Command for listing the available suites. -var SuitesListCmd = &cobra.Command{ - Use: "list", - Short: "List available suites.", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println(strings.Join(listSuites(), "\n")) - }, - Args: cobra.ExactArgs(0), +func newSuitesListCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "list", + Short: cmdSuitesListShort, + Long: cmdSuitesListLong, + Example: cmdSuitesListExample, + Run: cmdSuitesListRun, + Args: cobra.NoArgs, + } + + return cmd } -// SuitesSetupCmd Command to setup a suite environment. -var SuitesSetupCmd = &cobra.Command{ - Use: "setup [suite]", - Short: "Setup a Go suite environment. Suites can be listed using the list command.", - Run: func(cmd *cobra.Command, args []string) { - providedSuite := args[0] +func newSuitesSetupCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "setup [suite]", + Short: cmdSuitesSetupShort, + Long: cmdSuitesSetupLong, + Example: cmdSuitesSetupExample, + Run: cmdSuitesSetupRun, + Args: cobra.MaximumNArgs(1), + } + + return cmd +} + +func newSuitesTeardownCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "teardown [suite]", + Short: cmdSuitesTeardownShort, + Long: cmdSuitesTeardownLong, + Example: cmdSuitesTeardownExample, + Run: cmdSuitesTeardownRun, + Args: cobra.MaximumNArgs(1), + } + + return cmd +} + +func newSuitesTestCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "test [suite]", + Short: cmdSuitesTestShort, + Long: cmdSuitesTestLong, + Example: cmdSuitesTestExample, + Run: cmdSuitesTestRun, + Args: cobra.MaximumNArgs(1), + } + + cmd.Flags().BoolVar(&failfast, "failfast", false, "Stops tests on first failure") + cmd.Flags().BoolVar(&headless, "headless", false, "Run tests in headless mode") + cmd.Flags().StringVar(&testPattern, "test", "", "The single test to run") + + return cmd +} + +func cmdSuitesListRun(_ *cobra.Command, _ []string) { + fmt.Println(strings.Join(listSuites(), "\n")) +} + +func cmdSuitesSetupRun(_ *cobra.Command, args []string) { + providedSuite := args[0] + runningSuite, err := getRunningSuite() + + if err != nil { + log.Fatal(err) + } + + if runningSuite != "" && runningSuite != providedSuite { + log.Fatal("A suite is already running") + } + + if err := setupSuite(providedSuite); err != nil { + log.Fatal(err) + } +} + +func cmdSuitesTeardownRun(_ *cobra.Command, args []string) { + var suiteName string + if len(args) == 1 { + suiteName = args[0] + } else { runningSuite, err := getRunningSuite() if err != nil { log.Fatal(err) } - if runningSuite != "" && runningSuite != providedSuite { - log.Fatal("A suite is already running") + if runningSuite == "" { + log.Fatal(ErrNoRunningSuite) } + suiteName = runningSuite + } - if err := setupSuite(providedSuite); err != nil { - log.Fatal(err) - } - }, - Args: cobra.ExactArgs(1), + if err := teardownSuite(suiteName); err != nil { + log.Fatal(err) + } } -// SuitesTeardownCmd Command for tearing down a suite environment. -var SuitesTeardownCmd = &cobra.Command{ - Use: "teardown [suite]", - Short: "Teardown a Go suite environment. Suites can be listed using the list command.", - Run: func(cmd *cobra.Command, args []string) { - var suiteName string - if len(args) == 1 { - suiteName = args[0] - } else { - runningSuite, err := getRunningSuite() +func cmdSuitesTestRun(_ *cobra.Command, args []string) { + runningSuite, err := getRunningSuite() + if err != nil { + log.Fatal(err) + } - if err != nil { + // If suite(s) are provided as argument. + if len(args) >= 1 { + suiteArg := args[0] + + if runningSuite != "" && suiteArg != runningSuite { + log.Fatal(errors.New("Running suite (" + runningSuite + ") is different than suite(s) to be tested (" + suiteArg + "). Shutdown running suite and retry")) + } + + if err := runMultipleSuitesTests(strings.Split(suiteArg, ","), runningSuite == ""); err != nil { + log.Fatal(err) + } + } else { + if runningSuite != "" { + fmt.Println("Running suite (" + runningSuite + ") detected. Run tests of that suite") + if err := runSuiteTests(runningSuite, false); err != nil { log.Fatal(err) } - - if runningSuite == "" { - log.Fatal(ErrNoRunningSuite) + } else { + fmt.Println("No suite provided therefore all suites will be tested") + if err := runAllSuites(); err != nil { + log.Fatal(err) } - suiteName = runningSuite } - - if err := teardownSuite(suiteName); err != nil { - log.Fatal(err) - } - }, - Args: cobra.MaximumNArgs(1), -} - -// SuitesTestCmd Command for testing a suite. -var SuitesTestCmd = &cobra.Command{ - Use: "test [suite]", - Short: "Test a suite. Suites can be listed using the list command.", - Run: testSuite, - Args: cobra.MaximumNArgs(1), + } } func listSuites() []string { @@ -114,9 +178,9 @@ func listSuites() []string { } func checkSuiteAvailable(suite string) error { - suites := listSuites() + suiteNames := listSuites() - for _, s := range suites { + for _, s := range suiteNames { if s == suite { return nil } @@ -218,38 +282,6 @@ func teardownSuite(suiteName string) error { return runSuiteSetupTeardown("teardown", suiteName) } -func testSuite(cmd *cobra.Command, args []string) { - runningSuite, err := getRunningSuite() - if err != nil { - log.Fatal(err) - } - - // If suite(s) are provided as argument. - if len(args) >= 1 { - suiteArg := args[0] - - if runningSuite != "" && suiteArg != runningSuite { - log.Fatal(errors.New("Running suite (" + runningSuite + ") is different than suite(s) to be tested (" + suiteArg + "). Shutdown running suite and retry")) - } - - if err := runMultipleSuitesTests(strings.Split(suiteArg, ","), runningSuite == ""); err != nil { - log.Fatal(err) - } - } else { - if runningSuite != "" { - fmt.Println("Running suite (" + runningSuite + ") detected. Run tests of that suite") - if err := runSuiteTests(runningSuite, false); err != nil { - log.Fatal(err) - } - } else { - fmt.Println("No suite provided therefore all suites will be tested") - if err := runAllSuites(); err != nil { - log.Fatal(err) - } - } - } -} - func getRunningSuite() (string, error) { exist, err := utils.FileExists(runningSuiteFile) diff --git a/cmd/authelia-scripts/cmd/types.go b/cmd/authelia-scripts/cmd/types.go new file mode 100644 index 000000000..4da3d28fc --- /dev/null +++ b/cmd/authelia-scripts/cmd/types.go @@ -0,0 +1,7 @@ +package cmd + +// HostEntry represents an entry in /etc/hosts. +type HostEntry struct { + Domain string + IP string +} diff --git a/cmd/authelia-scripts/cmd_unittest.go b/cmd/authelia-scripts/cmd/unittest.go similarity index 60% rename from cmd/authelia-scripts/cmd_unittest.go rename to cmd/authelia-scripts/cmd/unittest.go index 8814cb5db..8ac8b44bb 100644 --- a/cmd/authelia-scripts/cmd_unittest.go +++ b/cmd/authelia-scripts/cmd/unittest.go @@ -1,4 +1,4 @@ -package main +package cmd import ( "os" @@ -9,8 +9,20 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -// RunUnitTest run the unit tests. -func RunUnitTest(cobraCmd *cobra.Command, args []string) { +func newUnitTestCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "unittest", + Short: cmdUnitTestShort, + Long: cmdUnitTestLong, + Example: cmdUnitTestExample, + Args: cobra.NoArgs, + Run: cmdUnitTestRun, + } + + return cmd +} + +func cmdUnitTestRun(_ *cobra.Command, _ []string) { log.SetLevel(log.TraceLevel) if err := utils.Shell("go test -coverprofile=coverage.txt -covermode=atomic $(go list ./... | grep -v suites)").Run(); err != nil { diff --git a/cmd/authelia-scripts/cmd/xflags.go b/cmd/authelia-scripts/cmd/xflags.go new file mode 100644 index 000000000..0366e838d --- /dev/null +++ b/cmd/authelia-scripts/cmd/xflags.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "fmt" + "strings" + + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func newXFlagsCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "xflags", + Short: cmdXFlagsShort, + Long: cmdXFlagsLong, + Example: cmdXFlagsExample, + Args: cobra.NoArgs, + Run: cmdXFlagsRun, + } + + cmd.Flags().StringP("build", "b", "0", "Sets the BuildNumber flag value") + cmd.Flags().StringP("extra", "e", "", "Sets the BuildExtra flag value") + + return cmd +} + +func cmdXFlagsRun(cobraCmd *cobra.Command, _ []string) { + build, err := cobraCmd.Flags().GetString("build") + if err != nil { + log.Fatal(err) + } + + extra, err := cobraCmd.Flags().GetString("extra") + if err != nil { + log.Fatal(err) + } + + flags, err := getXFlags("", build, extra) + if err != nil { + log.Fatal(err) + } + + fmt.Println(strings.Join(flags, " ")) +} diff --git a/cmd/authelia-scripts/cmd_clean.go b/cmd/authelia-scripts/cmd_clean.go deleted file mode 100644 index fa8e7f765..000000000 --- a/cmd/authelia-scripts/cmd_clean.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import ( - "os" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -// Clean artifacts built and installed by authelia-scripts. -func Clean(cobraCmd *cobra.Command, args []string) { - log.Debug("Removing `" + OutputDir + "` directory") - err := os.RemoveAll(OutputDir) - - if err != nil { - panic(err) - } -} diff --git a/cmd/authelia-scripts/cmd_serve.go b/cmd/authelia-scripts/cmd_serve.go deleted file mode 100644 index fccc04683..000000000 --- a/cmd/authelia-scripts/cmd_serve.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - - "github.com/authelia/authelia/v4/internal/utils" -) - -// ServeCmd serve Authelia with the provided configuration. -func ServeCmd(cmd *cobra.Command, args []string) { - log.Infof("Running Authelia with config %s...", args[0]) - execCmd := utils.CommandWithStdout(OutputDir+"/authelia", "--config", args[0]) - utils.RunCommandUntilCtrlC(execCmd) -} diff --git a/cmd/authelia-scripts/cmd_xflags.go b/cmd/authelia-scripts/cmd_xflags.go deleted file mode 100644 index ccd2ac1d4..000000000 --- a/cmd/authelia-scripts/cmd_xflags.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" -) - -func init() { - xflagsCmd.Flags().StringP("build", "b", "0", "Sets the BuildNumber flag value") - xflagsCmd.Flags().StringP("extra", "e", "", "Sets the BuildExtra flag value") -} - -var xflagsCmd = &cobra.Command{ - Use: "xflags", - Run: runXFlags, - Short: "Generate X LDFlags for building Authelia", -} - -func runXFlags(cobraCmd *cobra.Command, _ []string) { - build, err := cobraCmd.Flags().GetString("build") - if err != nil { - log.Fatal(err) - } - - extra, err := cobraCmd.Flags().GetString("extra") - if err != nil { - log.Fatal(err) - } - - flags, err := getXFlags("", build, extra) - if err != nil { - log.Fatal(err) - } - - fmt.Println(strings.Join(flags, " ")) -} diff --git a/cmd/authelia-scripts/const.go b/cmd/authelia-scripts/const.go deleted file mode 100644 index 0a884f89e..000000000 --- a/cmd/authelia-scripts/const.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -// OutputDir the output directory where the built version of Authelia is located. -var OutputDir = "dist" - -// DockerImageName the official name of Authelia docker image. -var DockerImageName = "authelia/authelia" - -// IntermediateDockerImageName local name of the docker image. -var IntermediateDockerImageName = "authelia:dist" - -const dockerhub = "docker.io" -const ghcr = "ghcr.io" - -const masterTag = "master" -const stringFalse = "false" -const webDirectory = "web" - -const fmtLDFLAGSX = "-X 'github.com/authelia/authelia/v4/internal/utils.%s=%s'" diff --git a/cmd/authelia-scripts/main.go b/cmd/authelia-scripts/main.go index 011cb09b2..623523a77 100755 --- a/cmd/authelia-scripts/main.go +++ b/cmd/authelia-scripts/main.go @@ -5,151 +5,12 @@ package main import ( log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/authelia/authelia/v4/internal/commands" - "github.com/authelia/authelia/v4/internal/utils" + "github.com/authelia/authelia/v4/cmd/authelia-scripts/cmd" ) -var buildkite bool -var logLevel string - -// AutheliaCommandDefinition is the definition of one authelia-scripts command. -type AutheliaCommandDefinition struct { - Name string - Short string - Long string - CommandLine string - Args cobra.PositionalArgs - Func func(cmd *cobra.Command, args []string) - SubCommands []*cobra.Command -} - -// CobraCommands list of cobra commands. -type CobraCommands = []*cobra.Command - -// Commands is the list of commands of authelia-scripts. -var Commands = []AutheliaCommandDefinition{ - { - Name: "bootstrap", - Short: "Prepare environment for development and testing.", - Long: `Prepare environment for development and testing. This command prepares docker - images and download tools like Kind for Kubernetes testing.`, - Func: Bootstrap, - }, - { - Name: "build", - Short: "Build Authelia binary and static assets", - Func: Build, - }, - { - Name: "clean", - Short: "Clean build artifacts", - Func: Clean, - }, - { - Name: "docker", - Short: "Commands related to building and publishing docker image", - SubCommands: CobraCommands{DockerBuildCmd, DockerManifestCmd}, - }, - { - Name: "serve [config]", - Short: "Serve compiled version of Authelia", - Func: ServeCmd, - Args: cobra.MinimumNArgs(1), - }, - { - Name: "suites", - Short: "Commands related to suites management", - SubCommands: CobraCommands{ - SuitesTestCmd, - SuitesListCmd, - SuitesSetupCmd, - SuitesTeardownCmd, - }, - }, - { - Name: "ci", - Short: "Run continuous integration script", - Func: RunCI, - }, - { - Name: "unittest", - Short: "Run unit tests", - Func: RunUnitTest, - }, -} - -func levelStringToLevel(level string) log.Level { - if level == "debug" { - return log.DebugLevel - } else if level == "warning" { - return log.WarnLevel - } - - return log.InfoLevel -} - func main() { - var rootCmd = &cobra.Command{Use: "authelia-scripts"} - - cobraCommands := make([]*cobra.Command, 0) - - for _, autheliaCommand := range Commands { - var fn func(cobraCmd *cobra.Command, args []string) - - if autheliaCommand.CommandLine != "" { - cmdline := autheliaCommand.CommandLine - fn = func(cobraCmd *cobra.Command, args []string) { - cmd := utils.CommandWithStdout(cmdline, args...) - - err := cmd.Run() - if err != nil { - panic(err) - } - } - } else if autheliaCommand.Func != nil { - fn = autheliaCommand.Func - } - - command := &cobra.Command{ - Use: autheliaCommand.Name, - Short: autheliaCommand.Short, - } - - if autheliaCommand.Long != "" { - command.Long = autheliaCommand.Long - } - - if fn != nil { - command.Run = fn - } - - if autheliaCommand.Args != nil { - command.Args = autheliaCommand.Args - } - - if autheliaCommand.SubCommands != nil { - command.AddCommand(autheliaCommand.SubCommands...) - } - - cobraCommands = append(cobraCommands, command) - } - - cobraCommands = append(cobraCommands, commands.NewHashPasswordCmd(), commands.NewCertificatesCmd(), commands.NewRSACmd(), NewRunGenCmd(), xflagsCmd) - - rootCmd.PersistentFlags().BoolVar(&buildkite, "buildkite", false, "Set CI flag for Buildkite") - rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Set the log level for the command") - rootCmd.AddCommand(cobraCommands...) - cobra.OnInitialize(initConfig) - - err := rootCmd.Execute() - - if err != nil { + if err := cmd.NewRootCmd().Execute(); err != nil { log.Fatal(err) } } - -func initConfig() { - log.SetLevel(levelStringToLevel(logLevel)) -} diff --git a/go.mod b/go.mod index d3771061c..42f919857 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -87,6 +88,7 @@ require ( github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/savsgio/dictpool v0.0.0-20220406081701-03de5edb2e6d // indirect github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect github.com/spf13/afero v1.6.0 // indirect diff --git a/go.sum b/go.sum index e49979597..944c9d6fb 100644 --- a/go.sum +++ b/go.sum @@ -158,9 +158,11 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -1185,8 +1187,10 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rubenv/sql-migrate v0.0.0-20190212093014-1007f53448d7/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= diff --git a/internal/commands/acl.go b/internal/commands/acl.go index e0bc6c95a..7a6cd4fde 100644 --- a/internal/commands/acl.go +++ b/internal/commands/acl.go @@ -17,8 +17,10 @@ import ( func newAccessControlCommand() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "access-control", - Short: "Helpers for the access control system", + Use: "access-control", + Short: cmdAutheliaAccessControlShort, + Long: cmdAutheliaAccessControlLong, + Example: cmdAutheliaAccessControlExample, } cmd.AddCommand( @@ -30,13 +32,14 @@ func newAccessControlCommand() (cmd *cobra.Command) { func newAccessControlCheckCommand() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "check-policy", - Short: "Checks a request against the access control rules to determine what policy would be applied", - Long: accessControlPolicyCheckLong, - RunE: accessControlCheckRunE, + Use: "check-policy", + Short: cmdAutheliaAccessControlCheckPolicyShort, + Long: cmdAutheliaAccessControlCheckPolicyLong, + Example: cmdAutheliaAccessControlCheckPolicyExample, + RunE: accessControlCheckRunE, } - cmdWithConfigFlags(cmd, false, []string{"config.yml"}) + cmdWithConfigFlags(cmd, false, []string{"configuration.yml"}) cmd.Flags().String("url", "", "the url of the object") cmd.Flags().String("method", "GET", "the HTTP method of the object") diff --git a/internal/commands/build-info.go b/internal/commands/build-info.go index 9e260e573..7933d8e11 100644 --- a/internal/commands/build-info.go +++ b/internal/commands/build-info.go @@ -11,11 +11,12 @@ import ( func newBuildInfoCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "build-info", - Short: "Show the build information of Authelia", - Long: buildLong, - RunE: cmdBuildInfoRunE, - Args: cobra.NoArgs, + Use: "build-info", + Short: cmdAutheliaBuildInfoShort, + Long: cmdAutheliaBuildInfoLong, + Example: cmdAutheliaBuildInfoExample, + RunE: cmdBuildInfoRunE, + Args: cobra.NoArgs, } return cmd diff --git a/internal/commands/certificates.go b/internal/commands/certificates.go index 6b581c12f..6a0bb286b 100644 --- a/internal/commands/certificates.go +++ b/internal/commands/certificates.go @@ -13,12 +13,13 @@ import ( "github.com/authelia/authelia/v4/internal/utils" ) -// NewCertificatesCmd returns a new Certificates Cmd. -func NewCertificatesCmd() (cmd *cobra.Command) { +func newCertificatesCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "certificates", - Short: "Commands related to certificate generation", - Args: cobra.NoArgs, + Use: "certificates", + Short: cmdAutheliaCertificatesShort, + Long: cmdAutheliaCertificatesLong, + Example: cmdAutheliaCertificatesExample, + Args: cobra.NoArgs, } cmd.PersistentFlags().StringSlice("host", []string{}, "Comma-separated hostnames and IPs to generate a certificate for") @@ -35,10 +36,12 @@ func NewCertificatesCmd() (cmd *cobra.Command) { func newCertificatesGenerateCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "generate", - Short: "Generate a self-signed certificate", - Args: cobra.NoArgs, - Run: cmdCertificatesGenerateRun, + Use: "generate", + Short: cmdAutheliaCertificatesGenerateShort, + Long: cmdAutheliaCertificatesGenerateLong, + Example: cmdAutheliaCertificatesGenerateExample, + Args: cobra.NoArgs, + Run: cmdCertificatesGenerateRun, } cmd.Flags().String("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011") diff --git a/internal/commands/completion.go b/internal/commands/completion.go deleted file mode 100644 index fa19e35ca..000000000 --- a/internal/commands/completion.go +++ /dev/null @@ -1,45 +0,0 @@ -package commands - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -func newCompletionCmd() (cmd *cobra.Command) { - cmd = &cobra.Command{ - Use: "completion [bash|zsh|fish|powershell]", - Short: "Generate completion script", - Long: completionLong, - Args: cobra.ExactValidArgs(1), - ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, - DisableFlagsInUseLine: true, - Run: cmdCompletionRun, - } - - return cmd -} - -func cmdCompletionRun(cmd *cobra.Command, args []string) { - var err error - - switch args[0] { - case "bash": - err = cmd.Root().GenBashCompletion(os.Stdout) - case "zsh": - err = cmd.Root().GenZshCompletion(os.Stdout) - case "fish": - err = cmd.Root().GenFishCompletion(os.Stdout, true) - case "powershell": - err = cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) - default: - fmt.Printf("Invalid shell provided for completion command: %s\n", args[0]) - os.Exit(1) - } - - if err != nil { - fmt.Printf("Error generating completion: %v\n", err) - os.Exit(1) - } -} diff --git a/internal/commands/const.go b/internal/commands/const.go index bba2a615d..01b4624cd 100644 --- a/internal/commands/const.go +++ b/internal/commands/const.go @@ -4,21 +4,22 @@ import ( "errors" ) -const cmdAutheliaExample = `authelia --config /etc/authelia/config.yml --config /etc/authelia/access-control.yml -authelia --config /etc/authelia/config.yml,/etc/authelia/access-control.yml -authelia --config /etc/authelia/config/ -` +const ( + fmtCmdAutheliaShort = "authelia %s" -const fmtAutheliaLong = `authelia %s + fmtCmdAutheliaLong = `authelia %s -An open-source authentication and authorization server providing -two-factor authentication and single sign-on (SSO) for your +An open-source authentication and authorization server providing +two-factor authentication and single sign-on (SSO) for your applications via a web portal. -Documentation is available at: https://www.authelia.com/docs -` +Documentation is available at: https://www.authelia.com/docs` -const fmtAutheliaBuild = `Last Tag: %s + cmdAutheliaExample = `authelia --config /etc/authelia/config.yml --config /etc/authelia/access-control.yml +authelia --config /etc/authelia/config.yml,/etc/authelia/access-control.yml +authelia --config /etc/authelia/config/` + + fmtAutheliaBuild = `Last Tag: %s State: %s Branch: %s Commit: %s @@ -29,7 +30,9 @@ Build Date: %s Extra: %s ` -const buildLong = `Show the build information of Authelia + cmdAutheliaBuildInfoShort = "Show the build information of Authelia" + + cmdAutheliaBuildInfoLong = `Show the build information of Authelia. This outputs detailed version information about the specific version of the Authelia binary. This information is embedded into Authelia @@ -39,48 +42,17 @@ This could be vital in debugging if you're not using a particular tagged build of Authelia. It's suggested to provide it along with your issue. ` + cmdAutheliaBuildInfoExample = `authelia build-info` -const completionLong = `To load completions: + cmdAutheliaAccessControlShort = "Helpers for the access control system" -Bash: + cmdAutheliaAccessControlLong = `Helpers for the access control system.` - $ source <(authelia completion bash) + cmdAutheliaAccessControlExample = `authelia access-control --help` - # To load completions for each session, execute once: - # Linux: - $ authelia completion bash > /etc/bash_completion.d/authelia - # macOS: - $ authelia completion bash > /usr/local/etc/bash_completion.d/authelia + cmdAutheliaAccessControlCheckPolicyShort = "Checks a request against the access control rules to determine what policy would be applied" -Zsh: - - # If shell completion is not already enabled in your environment, - # you will need to enable it. You can execute the following once: - - $ echo "autoload -U compinit; compinit" >> ~/.zshrc - - # To load completions for each session, execute once: - $ authelia completion zsh > "${fpath[1]}/_authelia" - - # You will need to start a new shell for this setup to take effect. - -fish: - - $ authelia completion fish | source - - # To load completions for each session, execute once: - $ authelia completion fish > ~/.config/fish/completions/authelia.fish - -PowerShell: - - PS> authelia completion powershell | Out-String | Invoke-Expression - - # To load completions for every new session, run: - PS> authelia completion powershell > authelia.ps1 - # and source this file from your PowerShell profile. -` - -const accessControlPolicyCheckLong = ` + cmdAutheliaAccessControlCheckPolicyLong = ` Checks a request against the access control rules to determine what policy would be applied. Legend: @@ -97,15 +69,299 @@ Notes: A rule that potentially matches a request will cause a redirection to occur in order to perform one-factor authentication. This is so Authelia can adequately determine if the rule actually matches. ` + cmdAutheliaAccessControlCheckPolicyExample = `authelia access-control check-policy --config config.yml --url https://example.com +authelia access-control check-policy --config config.yml --url https://example.com --username john +authelia access-control check-policy --config config.yml --url https://example.com --groups admin,public +authelia access-control check-policy --config config.yml --url https://example.com --username john --method GET +authelia access-control check-policy --config config.yml --url https://example.com --username john --method GET --verbose` + + cmdAutheliaStorageShort = "Manage the Authelia storage" + + cmdAutheliaStorageLong = `Manage the Authelia storage. + +This subcommand has several methods to interact with the Authelia SQL Database. This allows doing several advanced +operations which would be much harder to do manually. +` + + cmdAutheliaStorageExample = `authelia storage --help` + + cmdAutheliaStorageEncryptionShort = "Manage storage encryption" + + cmdAutheliaStorageEncryptionLong = `Manage storage encryption. + +This subcommand allows management of the storage encryption.` + + cmdAutheliaStorageEncryptionExample = `authelia storage encryption --help` + + cmdAutheliaStorageEncryptionCheckShort = "Checks the encryption key against the database data" + + cmdAutheliaStorageEncryptionCheckLong = `Checks the encryption key against the database data. + +This is useful for validating all data that can be encrypted is intact.` + + cmdAutheliaStorageEncryptionCheckExample = `authelia storage encryption check +authelia storage encryption check --verbose +authelia storage encryption check --verbose --config config.yml +authelia storage encryption check --verbose --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageEncryptionChangeKeyShort = "Changes the encryption key" + + cmdAutheliaStorageEncryptionChangeKeyLong = `Changes the encryption key. + +This subcommand allows you to change the encryption key of an Authelia SQL database.` + + cmdAutheliaStorageEncryptionChangeKeyExample = `authelia storage encryption change-key --config config.yml --new-encryption-key 0e95cb49-5804-4ad9-be82-bb04a9ddecd8 +authelia storage encryption change-key --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --new-encryption-key 0e95cb49-5804-4ad9-be82-bb04a9ddecd8 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageUserShort = "Manages user settings" + + cmdAutheliaStorageUserLong = `Manages user settings. + +This subcommand allows modifying and exporting user settings.` + + cmdAutheliaStorageUserExample = `authelia storage user --help` + + cmdAutheliaStorageUserIdentifiersShort = "Manage user opaque identifiers" + + cmdAutheliaStorageUserIdentifiersLong = `Manage user opaque identifiers. + +This subcommand allows performing various tasks related to the opaque identifiers for users.` + + cmdAutheliaStorageUserIdentifiersExample = `authelia storage user identifiers --help` + + cmdAutheliaStorageUserIdentifiersExportShort = "Export the identifiers to a YAML file" + + cmdAutheliaStorageUserIdentifiersExportLong = `Export the identifiers to a YAML file. + +This subcommand allows exporting the opaque identifiers for users in order to back them up.` + + cmdAutheliaStorageUserIdentifiersExportExample = `authelia storage user identifiers export +authelia storage user identifiers export --file export.yaml +authelia storage user identifiers export --file export.yaml --config config.yml +authelia storage user identifiers export --file export.yaml --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageUserIdentifiersImportShort = "Import the identifiers from a YAML file" + + cmdAutheliaStorageUserIdentifiersImportLong = `Import the identifiers from a YAML file. + +This subcommand allows you to import the opaque identifiers for users from a YAML file. + +The YAML file can either be automatically generated using the authelia storage user identifiers export command, or +manually provided the file is in the same format.` + + cmdAutheliaStorageUserIdentifiersImportExample = `authelia storage user identifiers import +authelia storage user identifiers import --file export.yaml +authelia storage user identifiers import --file export.yaml --config config.yml +authelia storage user identifiers import --file export.yaml --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageUserIdentifiersGenerateShort = "Generate opaque identifiers in bulk" + + cmdAutheliaStorageUserIdentifiersGenerateLong = `Generate opaque identifiers in bulk. + +This subcommand allows various options for generating the opaque identifies for users in bulk.` + + cmdAutheliaStorageUserIdentifiersGenerateExample = `authelia storage user identifiers generate --users john,mary +authelia storage user identifiers generate --users john,mary --services openid +authelia storage user identifiers generate --users john,mary --services openid --sectors=",example.com,test.com" +authelia storage user identifiers generate --users john,mary --services openid --sectors=",example.com,test.com" --config config.yml +authelia storage user identifiers generate --users john,mary --services openid --sectors=",example.com,test.com" --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageUserIdentifiersAddShort = "Add an opaque identifier for a user to the database" + + cmdAutheliaStorageUserIdentifiersAddLong = `Add an opaque identifier for a user to the database. + +This subcommand allows manually adding an opaque identifier for a user to the database provided it's in the correct format.` + + cmdAutheliaStorageUserIdentifiersAddExample = `authelia storage user identifiers add john --identifier f0919359-9d15-4e15-bcba-83b41620a073 +authelia storage user identifiers add john --identifier f0919359-9d15-4e15-bcba-83b41620a073 --config config.yml +authelia storage user identifiers add john --identifier f0919359-9d15-4e15-bcba-83b41620a073 --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageUserTOTPShort = "Manage TOTP configurations" + + cmdAutheliaStorageUserTOTPLong = `Manage TOTP configurations. + +This subcommand allows deleting, exporting, and creating user TOTP configurations.` + + cmdAutheliaStorageUserTOTPExample = `authelia storage user totp --help` + + cmdAutheliaStorageUserTOTPGenerateShort = "Generate a TOTP configuration for a user" + + cmdAutheliaStorageUserTOTPGenerateLong = `Generate a TOTP configuration for a user. + +This subcommand allows generating a new TOTP configuration for a user, +and overwriting the existing configuration if applicable.` + + cmdAutheliaStorageUserTOTPGenerateExample = `authelia storage user totp generate john +authelia storage user totp generate john --period 90 +authelia storage user totp generate john --digits 8 +authelia storage user totp generate john --algorithm SHA512 +authelia storage user totp generate john --algorithm SHA512 --config config.yml +authelia storage user totp generate john --algorithm SHA512 --config config.yml --path john.png` + + cmdAutheliaStorageUserTOTPDeleteShort = "Delete a TOTP configuration for a user" + + cmdAutheliaStorageUserTOTPDeleteLong = `Delete a TOTP configuration for a user. + +This subcommand allows deleting a TOTP configuration directly from the database for a given user.` + + cmdAutheliaStorageUserTOTPDeleteExample = `authelia storage user totp delete john +authelia storage user totp delete john --config config.yml +authelia storage user totp delete john --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageUserTOTPExportShort = "Perform exports of the TOTP configurations" + + cmdAutheliaStorageUserTOTPExportLong = `Perform exports of the TOTP configurations. + +This subcommand allows exporting TOTP configurations to various formats.` + + cmdAutheliaStorageUserTOTPExportExample = `authelia storage user totp export --format csv +authelia storage user totp export --format png --dir ./totp-qr +authelia storage user totp export --format png --dir ./totp-qr --config config.yml +authelia storage user totp export --format png --dir ./totp-qr --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageSchemaInfoShort = "Show the storage information" + + cmdAutheliaStorageSchemaInfoLong = `Show the storage information. + +This subcommand shows advanced information about the storage schema useful in some diagnostic tasks.` + + cmdAutheliaStorageSchemaInfoExample = `authelia storage schema-info +authelia storage schema-info --config config.yml +authelia storage schema-info --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageMigrateShort = "Perform or list migrations" + + cmdAutheliaStorageMigrateLong = `Perform or list migrations. + +This subcommand handles schema migration tasks.` + + cmdAutheliaStorageMigrateExample = `authelia storage migrate --help` + + cmdAutheliaStorageMigrateHistoryShort = "Show migration history" + + cmdAutheliaStorageMigrateHistoryLong = `Show migration history. + +This subcommand allows users to list previous migrations.` + + cmdAutheliaStorageMigrateHistoryExample = `authelia storage migrate history +authelia storage migrate history --config config.yml +authelia storage migrate history --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageMigrateListUpShort = "List the up migrations available" + + cmdAutheliaStorageMigrateListUpLong = `List the up migrations available. + +This subcommand lists the schema migrations available in this version of Authelia which are greater than the current +schema version of the database.` + + cmdAutheliaStorageMigrateListUpExample = `authelia storage migrate list-up +authelia storage migrate list-up --config config.yml +authelia storage migrate list-up --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageMigrateListDownShort = "List the down migrations available" + + cmdAutheliaStorageMigrateListDownLong = `List the down migrations available. + +This subcommand lists the schema migrations available in this version of Authelia which are less than the current +schema version of the database.` + + cmdAutheliaStorageMigrateListDownExample = `authelia storage migrate list-down +authelia storage migrate list-down --config config.yml +authelia storage migrate list-down --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageMigrateUpShort = "Perform a migration up" + + cmdAutheliaStorageMigrateUpLong = `Perform a migration up. + +This subcommand performs the schema migrations available in this version of Authelia which are greater than the current +schema version of the database. By default this will migrate up to the latest available, but you can customize this.` + + cmdAutheliaStorageMigrateUpExample = `authelia storage migrate up +authelia storage migrate up --config config.yml +authelia storage migrate up --target 20 --config config.yml +authelia storage migrate up --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaStorageMigrateDownShort = "Perform a migration down" + + cmdAutheliaStorageMigrateDownLong = `Perform a migration down. + +This subcommand performs the schema migrations available in this version of Authelia which are less than the current +schema version of the database.` + + cmdAutheliaStorageMigrateDownExample = `authelia storage migrate down --target 20 +authelia storage migrate down --target 20 --config config.yml +authelia storage migrate down --target 20 --encryption-key b3453fde-ecc2-4a1f-9422-2707ddbed495 --postgres.host postgres --postgres.password autheliapw` + + cmdAutheliaValidateConfigShort = "Check a configuration against the internal configuration validation mechanisms" + + cmdAutheliaValidateConfigLong = `Check a configuration against the internal configuration validation mechanisms. + +This subcommand allows validation of the YAML and Environment configurations so that a configuration can be checked +prior to deploying it.` + + cmdAutheliaValidateConfigExample = `authelia validate-config +authelia validate-config --config config.yml` + + cmdAutheliaCertificatesShort = "Commands related to certificate generation" + + cmdAutheliaCertificatesLong = `Commands related to certificate generation. + +This subcommand allows preforming X509 certificate tasks.` + + cmdAutheliaCertificatesExample = `authelia certificates --help` + + cmdAutheliaCertificatesGenerateShort = "Generate a self-signed certificate" + + cmdAutheliaCertificatesGenerateLong = `Generate a self-signed certificate. + +This subcommand allows generating self-signed certificates.` + + cmdAutheliaCertificatesGenerateExample = `authelia certificates generate +authelia certificates generate --dir ./out` + + cmdAutheliaRSAShort = "Commands related to rsa keypair generation" + + cmdAutheliaRSALong = `Commands related to rsa keypair generation. + +This subcommand allows performing RSA keypair tasks.` + + cmdAutheliaRSAExample = `authelia rsa --help` + + cmdAutheliaRSAGenerateShort = "Generate a RSA keypair" + + cmdAutheliaRSAGenerateLong = `Generate a RSA keypair. + +This subcommand allows generating an RSA keypair.` + + cmdAutheliaRSAGenerateExample = `authelia rsa generate +authelia rsa generate --dir ./out` + + cmdAutheliaHashPasswordShort = "Hash a password to be used in file-based users database." + + cmdAutheliaHashPasswordLong = `Hash a password to be used in file-based users database.` + + //nolint:gosec // This is an example. + cmdAutheliaHashPasswordExample = `authelia hash-password -- 'mypass' +authelia hash-password --sha512 -- 'mypass' +authelia hash-password --iterations=4 -- 'mypass' +authelia hash-password --memory=128 -- 'mypass' +authelia hash-password --parallelism=1 -- 'mypass' +authelia hash-password --key-length=64 -- 'mypass'` +) + const ( storageMigrateDirectionUp = "up" storageMigrateDirectionDown = "down" ) const ( - storageExportFormatCSV = "csv" - storageExportFormatURI = "uri" - storageExportFormatPNG = "png" + storageTOTPExportFormatCSV = "csv" + storageTOTPExportFormatURI = "uri" + storageTOTPExportFormatPNG = "png" +) + +var ( + validStorageTOTPExportFormats = []string{storageTOTPExportFormatCSV, storageTOTPExportFormatURI, storageTOTPExportFormatPNG} ) var ( diff --git a/internal/commands/hash.go b/internal/commands/hash.go index cf1d45790..dae64d766 100644 --- a/internal/commands/hash.go +++ b/internal/commands/hash.go @@ -12,13 +12,14 @@ import ( "github.com/authelia/authelia/v4/internal/configuration/validator" ) -// NewHashPasswordCmd returns a new Hash Password Cmd. -func NewHashPasswordCmd() (cmd *cobra.Command) { +func newHashPasswordCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "hash-password [flags] -- ", - Short: "Hash a password to be used in file-based users database. Default algorithm is argon2id.", - Args: cobra.MinimumNArgs(1), - RunE: cmdHashPasswordRunE, + Use: "hash-password [flags] -- ", + Short: cmdAutheliaHashPasswordShort, + Long: cmdAutheliaHashPasswordLong, + Example: cmdAutheliaHashPasswordExample, + Args: cobra.MinimumNArgs(1), + RunE: cmdHashPasswordRunE, } cmd.Flags().BoolP("sha512", "z", false, fmt.Sprintf("use sha512 as the algorithm (changes iterations to %d, change with -i)", schema.DefaultPasswordSHA512Configuration.Iterations)) diff --git a/internal/commands/root.go b/internal/commands/root.go index 0fd3082ef..52face48a 100644 --- a/internal/commands/root.go +++ b/internal/commands/root.go @@ -23,9 +23,9 @@ func NewRootCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ Use: "authelia", + Short: fmt.Sprintf(fmtCmdAutheliaShort, version), + Long: fmt.Sprintf(fmtCmdAutheliaLong, version), Example: cmdAutheliaExample, - Short: fmt.Sprintf("authelia %s", version), - Long: fmt.Sprintf(fmtAutheliaLong, version), Version: version, Args: cobra.NoArgs, PreRun: newCmdWithConfigPreRun(true, true, true), @@ -36,11 +36,10 @@ func NewRootCmd() (cmd *cobra.Command) { cmd.AddCommand( newBuildInfoCmd(), - NewCertificatesCmd(), - newCompletionCmd(), - NewHashPasswordCmd(), + newCertificatesCmd(), + newHashPasswordCmd(), NewRSACmd(), - NewStorageCmd(), + newStorageCmd(), newValidateConfigCmd(), newAccessControlCommand(), ) diff --git a/internal/commands/rsa.go b/internal/commands/rsa.go index 650406b28..4b8983321 100644 --- a/internal/commands/rsa.go +++ b/internal/commands/rsa.go @@ -13,9 +13,11 @@ import ( // NewRSACmd returns a new RSA Cmd. func NewRSACmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "rsa", - Short: "Commands related to rsa keypair generation", - Args: cobra.NoArgs, + Use: "rsa", + Short: cmdAutheliaRSAShort, + Long: cmdAutheliaRSALong, + Example: cmdAutheliaRSAExample, + Args: cobra.NoArgs, } cmd.AddCommand(newRSAGenerateCmd()) @@ -25,10 +27,12 @@ func NewRSACmd() (cmd *cobra.Command) { func newRSAGenerateCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "generate", - Short: "Generate a RSA keypair", - Args: cobra.NoArgs, - Run: cmdRSAGenerateRun, + Use: "generate", + Short: cmdAutheliaRSAGenerateShort, + Long: cmdAutheliaRSAGenerateLong, + Example: cmdAutheliaRSAGenerateExample, + Args: cobra.NoArgs, + Run: cmdRSAGenerateRun, } cmd.Flags().StringP("dir", "d", "", "Target directory where the keypair will be stored") diff --git a/internal/commands/storage.go b/internal/commands/storage.go index 5ceae4c34..2024f3bd3 100644 --- a/internal/commands/storage.go +++ b/internal/commands/storage.go @@ -1,16 +1,20 @@ package commands import ( + "fmt" + "strings" + "github.com/spf13/cobra" "github.com/authelia/authelia/v4/internal/configuration/schema" ) -// NewStorageCmd returns a new storage *cobra.Command. -func NewStorageCmd() (cmd *cobra.Command) { +func newStorageCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ Use: "storage", - Short: "Manage the Authelia storage", + Short: cmdAutheliaStorageShort, + Long: cmdAutheliaStorageLong, + Example: cmdAutheliaStorageExample, Args: cobra.NoArgs, PersistentPreRunE: storagePersistentPreRunE, } @@ -48,15 +52,61 @@ func NewStorageCmd() (cmd *cobra.Command) { return cmd } +func newStorageEncryptionCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "encryption", + Short: cmdAutheliaStorageEncryptionShort, + Long: cmdAutheliaStorageEncryptionLong, + Example: cmdAutheliaStorageEncryptionExample, + } + + cmd.AddCommand( + newStorageEncryptionChangeKeyCmd(), + newStorageEncryptionCheckCmd(), + ) + + return cmd +} + +func newStorageEncryptionCheckCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "check", + Short: cmdAutheliaStorageEncryptionCheckShort, + Long: cmdAutheliaStorageEncryptionCheckLong, + Example: cmdAutheliaStorageEncryptionCheckExample, + RunE: storageSchemaEncryptionCheckRunE, + } + + cmd.Flags().Bool("verbose", false, "enables verbose checking of every row of encrypted data") + + return cmd +} + +func newStorageEncryptionChangeKeyCmd() (cmd *cobra.Command) { + cmd = &cobra.Command{ + Use: "change-key", + Short: cmdAutheliaStorageEncryptionChangeKeyShort, + Long: cmdAutheliaStorageEncryptionChangeKeyLong, + Example: cmdAutheliaStorageEncryptionChangeKeyExample, + RunE: storageSchemaEncryptionChangeKeyRunE, + } + + cmd.Flags().String("new-encryption-key", "", "the new key to encrypt the data with") + + return cmd +} + func newStorageUserCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "user", - Short: "Manages user settings", + Use: "user", + Short: cmdAutheliaStorageUserShort, + Long: cmdAutheliaStorageUserLong, + Example: cmdAutheliaStorageUserExample, } cmd.AddCommand( newStorageUserIdentifiersCmd(), - newStorageTOTPCmd(), + newStorageUserTOTPCmd(), ) return cmd @@ -64,8 +114,10 @@ func newStorageUserCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "identifiers", - Short: "Manages user opaque identifiers", + Use: "identifiers", + Short: cmdAutheliaStorageUserIdentifiersShort, + Long: cmdAutheliaStorageUserIdentifiersLong, + Example: cmdAutheliaStorageUserIdentifiersExample, } cmd.AddCommand( @@ -80,9 +132,11 @@ func newStorageUserIdentifiersCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersExportCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "export", - Short: "Export the identifiers to a YAML file", - RunE: storageUserIdentifiersExport, + Use: "export", + Short: cmdAutheliaStorageUserIdentifiersExportShort, + Long: cmdAutheliaStorageUserIdentifiersExportLong, + Example: cmdAutheliaStorageUserIdentifiersExportExample, + RunE: storageUserIdentifiersExport, } cmd.Flags().StringP("file", "f", "user-opaque-identifiers.yml", "The file name for the YAML export") @@ -92,9 +146,11 @@ func newStorageUserIdentifiersExportCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersImportCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "import", - Short: "Import the identifiers from a YAML file", - RunE: storageUserIdentifiersImport, + Use: "import", + Short: cmdAutheliaStorageUserIdentifiersImportShort, + Long: cmdAutheliaStorageUserIdentifiersImportLong, + Example: cmdAutheliaStorageUserIdentifiersImportExample, + RunE: storageUserIdentifiersImport, } cmd.Flags().StringP("file", "f", "user-opaque-identifiers.yml", "The file name for the YAML import") @@ -104,13 +160,15 @@ func newStorageUserIdentifiersImportCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersGenerateCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "generate", - Short: "Generate opaque identifiers in bulk", - RunE: storageUserIdentifiersGenerate, + Use: "generate", + Short: cmdAutheliaStorageUserIdentifiersGenerateShort, + Long: cmdAutheliaStorageUserIdentifiersGenerateLong, + Example: cmdAutheliaStorageUserIdentifiersGenerateExample, + RunE: storageUserIdentifiersGenerate, } cmd.Flags().StringSlice("users", nil, "The list of users to generate the opaque identifiers for") - cmd.Flags().StringSlice("services", []string{identifierServiceOpenIDConnect}, "The list of services to generate the opaque identifiers for, valid values are: openid") + cmd.Flags().StringSlice("services", []string{identifierServiceOpenIDConnect}, fmt.Sprintf("The list of services to generate the opaque identifiers for, valid values are: %s", strings.Join(validIdentifierServices, ", "))) cmd.Flags().StringSlice("sectors", []string{""}, "The list of sectors to generate identifiers for") return cmd @@ -118,78 +176,46 @@ func newStorageUserIdentifiersGenerateCmd() (cmd *cobra.Command) { func newStorageUserIdentifiersAddCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "add [username]", - Short: "Add an identifiers to the database", - Args: cobra.ExactArgs(1), - RunE: storageUserIdentifiersAdd, + Use: "add ", + Short: cmdAutheliaStorageUserIdentifiersAddShort, + Long: cmdAutheliaStorageUserIdentifiersAddLong, + Example: cmdAutheliaStorageUserIdentifiersAddExample, + Args: cobra.ExactArgs(1), + RunE: storageUserIdentifiersAdd, } cmd.Flags().String("identifier", "", "The optional version 4 UUID to use, if not set a random one will be used") - cmd.Flags().String("service", identifierServiceOpenIDConnect, "The service to add the identifier for, valid values are: openid") + cmd.Flags().String("service", identifierServiceOpenIDConnect, fmt.Sprintf("The service to add the identifier for, valid values are: %s", strings.Join(validIdentifierServices, ", "))) cmd.Flags().String("sector", "", "The sector identifier to use (should usually be blank)") return cmd } -func newStorageEncryptionCmd() (cmd *cobra.Command) { +func newStorageUserTOTPCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "encryption", - Short: "Manages encryption", + Use: "totp", + Short: cmdAutheliaStorageUserTOTPShort, + Long: cmdAutheliaStorageUserTOTPLong, + Example: cmdAutheliaStorageUserTOTPExample, } cmd.AddCommand( - newStorageEncryptionChangeKeyCmd(), - newStorageEncryptionCheckCmd(), + newStorageUserTOTPGenerateCmd(), + newStorageUserTOTPDeleteCmd(), + newStorageUserTOTPExportCmd(), ) return cmd } -func newStorageEncryptionCheckCmd() (cmd *cobra.Command) { +func newStorageUserTOTPGenerateCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "check", - Short: "Checks the encryption key against the database data", - RunE: storageSchemaEncryptionCheckRunE, - } - - cmd.Flags().Bool("verbose", false, "enables verbose checking of every row of encrypted data") - - return cmd -} - -func newStorageEncryptionChangeKeyCmd() (cmd *cobra.Command) { - cmd = &cobra.Command{ - Use: "change-key", - Short: "Changes the encryption key", - RunE: storageSchemaEncryptionChangeKeyRunE, - } - - cmd.Flags().String("new-encryption-key", "", "the new key to encrypt the data with") - - return cmd -} - -func newStorageTOTPCmd() (cmd *cobra.Command) { - cmd = &cobra.Command{ - Use: "totp", - Short: "Manage TOTP configurations", - } - - cmd.AddCommand( - newStorageTOTPGenerateCmd(), - newStorageTOTPDeleteCmd(), - newStorageTOTPExportCmd(), - ) - - return cmd -} - -func newStorageTOTPGenerateCmd() (cmd *cobra.Command) { - cmd = &cobra.Command{ - Use: "generate [username]", - Short: "Generate a TOTP configuration for a user", - RunE: storageTOTPGenerateRunE, - Args: cobra.ExactArgs(1), + Use: "generate ", + Short: cmdAutheliaStorageUserTOTPGenerateShort, + Long: cmdAutheliaStorageUserTOTPGenerateLong, + Example: cmdAutheliaStorageUserTOTPGenerateExample, + RunE: storageTOTPGenerateRunE, + Args: cobra.ExactArgs(1), } cmd.Flags().String("secret", "", "Optionally set the TOTP shared secret as base32 encoded bytes (no padding), it's recommended to not set this option unless you're restoring an TOTP config") @@ -204,25 +230,29 @@ func newStorageTOTPGenerateCmd() (cmd *cobra.Command) { return cmd } -func newStorageTOTPDeleteCmd() (cmd *cobra.Command) { +func newStorageUserTOTPDeleteCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "delete username", - Short: "Delete a TOTP configuration for a user", - RunE: storageTOTPDeleteRunE, - Args: cobra.ExactArgs(1), + Use: "delete ", + Short: cmdAutheliaStorageUserTOTPDeleteShort, + Long: cmdAutheliaStorageUserTOTPDeleteLong, + Example: cmdAutheliaStorageUserTOTPDeleteExample, + RunE: storageTOTPDeleteRunE, + Args: cobra.ExactArgs(1), } return cmd } -func newStorageTOTPExportCmd() (cmd *cobra.Command) { +func newStorageUserTOTPExportCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "export", - Short: "Performs exports of the TOTP configurations", - RunE: storageTOTPExportRunE, + Use: "export", + Short: cmdAutheliaStorageUserTOTPExportShort, + Long: cmdAutheliaStorageUserTOTPExportLong, + Example: cmdAutheliaStorageUserTOTPExportExample, + RunE: storageTOTPExportRunE, } - cmd.Flags().String("format", storageExportFormatURI, "sets the output format") + cmd.Flags().String("format", storageTOTPExportFormatURI, fmt.Sprintf("sets the output format, valid values are: %s", strings.Join(validStorageTOTPExportFormats, ", "))) cmd.Flags().String("dir", "", "used with the png output format to specify which new directory to save the files in") return cmd @@ -230,9 +260,11 @@ func newStorageTOTPExportCmd() (cmd *cobra.Command) { func newStorageSchemaInfoCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "schema-info", - Short: "Show the storage information", - RunE: storageSchemaInfoRunE, + Use: "schema-info", + Short: cmdAutheliaStorageSchemaInfoShort, + Long: cmdAutheliaStorageSchemaInfoLong, + Example: cmdAutheliaStorageSchemaInfoExample, + RunE: storageSchemaInfoRunE, } return cmd @@ -241,9 +273,11 @@ func newStorageSchemaInfoCmd() (cmd *cobra.Command) { // NewMigrationCmd returns a new Migration Cmd. func newStorageMigrateCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "migrate", - Short: "Perform or list migrations", - Args: cobra.NoArgs, + Use: "migrate", + Short: cmdAutheliaStorageMigrateShort, + Long: cmdAutheliaStorageMigrateLong, + Example: cmdAutheliaStorageMigrateExample, + Args: cobra.NoArgs, } cmd.AddCommand( @@ -257,10 +291,12 @@ func newStorageMigrateCmd() (cmd *cobra.Command) { func newStorageMigrateHistoryCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "history", - Short: "Show migration history", - Args: cobra.NoArgs, - RunE: storageMigrateHistoryRunE, + Use: "history", + Short: cmdAutheliaStorageMigrateHistoryShort, + Long: cmdAutheliaStorageMigrateHistoryLong, + Example: cmdAutheliaStorageMigrateHistoryExample, + Args: cobra.NoArgs, + RunE: storageMigrateHistoryRunE, } return cmd @@ -268,10 +304,12 @@ func newStorageMigrateHistoryCmd() (cmd *cobra.Command) { func newStorageMigrateListUpCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "list-up", - Short: "List the up migrations available", - Args: cobra.NoArgs, - RunE: newStorageMigrateListRunE(true), + Use: "list-up", + Short: cmdAutheliaStorageMigrateListUpShort, + Long: cmdAutheliaStorageMigrateListUpLong, + Example: cmdAutheliaStorageMigrateListUpExample, + Args: cobra.NoArgs, + RunE: newStorageMigrateListRunE(true), } return cmd @@ -279,10 +317,12 @@ func newStorageMigrateListUpCmd() (cmd *cobra.Command) { func newStorageMigrateListDownCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "list-down", - Short: "List the down migrations available", - Args: cobra.NoArgs, - RunE: newStorageMigrateListRunE(false), + Use: "list-down", + Short: cmdAutheliaStorageMigrateListDownShort, + Long: cmdAutheliaStorageMigrateListDownLong, + Example: cmdAutheliaStorageMigrateListDownExample, + Args: cobra.NoArgs, + RunE: newStorageMigrateListRunE(false), } return cmd @@ -290,10 +330,12 @@ func newStorageMigrateListDownCmd() (cmd *cobra.Command) { func newStorageMigrateUpCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: storageMigrateDirectionUp, - Short: "Perform a migration up", - Args: cobra.NoArgs, - RunE: newStorageMigrationRunE(true), + Use: storageMigrateDirectionUp, + Short: cmdAutheliaStorageMigrateUpShort, + Long: cmdAutheliaStorageMigrateUpLong, + Example: cmdAutheliaStorageMigrateUpExample, + Args: cobra.NoArgs, + RunE: newStorageMigrationRunE(true), } cmd.Flags().IntP("target", "t", 0, "sets the version to migrate to, by default this is the latest version") @@ -303,10 +345,12 @@ func newStorageMigrateUpCmd() (cmd *cobra.Command) { func newStorageMigrateDownCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: storageMigrateDirectionDown, - Short: "Perform a migration down", - Args: cobra.NoArgs, - RunE: newStorageMigrationRunE(false), + Use: storageMigrateDirectionDown, + Short: cmdAutheliaStorageMigrateDownShort, + Long: cmdAutheliaStorageMigrateDownLong, + Example: cmdAutheliaStorageMigrateDownExample, + Args: cobra.NoArgs, + RunE: newStorageMigrationRunE(false), } cmd.Flags().IntP("target", "t", 0, "sets the version to migrate to") diff --git a/internal/commands/storage_run.go b/internal/commands/storage_run.go index c425b1490..b911fd64f 100644 --- a/internal/commands/storage_run.go +++ b/internal/commands/storage_run.go @@ -351,17 +351,17 @@ func storageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) { return err } - if page == 0 && format == storageExportFormatCSV { + if page == 0 && format == storageTOTPExportFormatCSV { fmt.Printf("issuer,username,algorithm,digits,period,secret\n") } for _, c := range configurations { switch format { - case storageExportFormatCSV: + case storageTOTPExportFormatCSV: fmt.Printf("%s,%s,%s,%d,%d,%s\n", c.Issuer, c.Username, c.Algorithm, c.Digits, c.Period, string(c.Secret)) - case storageExportFormatURI: + case storageTOTPExportFormatURI: fmt.Println(c.URI()) - case storageExportFormatPNG: + case storageTOTPExportFormatPNG: file, _ := os.Create(filepath.Join(dir, fmt.Sprintf("%s.png", c.Username))) if img, err = c.Image(256, 256); err != nil { @@ -385,7 +385,7 @@ func storageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) { } } - if format == storageExportFormatPNG { + if format == storageTOTPExportFormatPNG { fmt.Printf("Exported TOTP QR codes in PNG format in the '%s' directory\n", dir) } @@ -402,9 +402,9 @@ func storageTOTPExportGetConfigFromFlags(cmd *cobra.Command) (format, dir string } switch format { - case storageExportFormatCSV, storageExportFormatURI: + case storageTOTPExportFormatCSV, storageTOTPExportFormatURI: break - case storageExportFormatPNG: + case storageTOTPExportFormatPNG: if dir == "" { dir = utils.RandomString(8, utils.AlphaNumericCharacters, false) } diff --git a/internal/commands/validate.go b/internal/commands/validate.go index 2b46a9751..cc0ab2952 100644 --- a/internal/commands/validate.go +++ b/internal/commands/validate.go @@ -10,10 +10,12 @@ import ( func newValidateConfigCmd() (cmd *cobra.Command) { cmd = &cobra.Command{ - Use: "validate-config", - Short: "Check a configuration against the internal configuration validation mechanisms", - Args: cobra.NoArgs, - RunE: cmdValidateConfigRunE, + Use: "validate-config", + Short: cmdAutheliaValidateConfigShort, + Long: cmdAutheliaValidateConfigLong, + Example: cmdAutheliaValidateConfigExample, + Args: cobra.NoArgs, + RunE: cmdValidateConfigRunE, } cmdWithConfigFlags(cmd, false, []string{"configuration.yml"}) diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go index 4d0f4ace6..4d3a83815 100644 --- a/internal/configuration/schema/keys.go +++ b/internal/configuration/schema/keys.go @@ -1,12 +1,12 @@ // Code generated by go generate. DO NOT EDIT. // // Run the following command to generate this file: -// go run ./cmd/authelia-scripts gen +// go run ./cmd/authelia-gen code keys // package schema -// Keys represents the detected schema keys. +// Keys is a list of valid schema keys detected by reflecting over a schema.Configuration struct. var Keys = []string{ "theme", "certificates_directory",