feat: add database-backed console login
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.yuntuo.hk/k2safe/orbit/internal/buildinfo"
|
||||
@@ -26,13 +27,15 @@ func main() {
|
||||
}
|
||||
func run() error {
|
||||
if len(os.Args) < 2 {
|
||||
return errors.New("usage: orbit-admin release manifest|bootstrap|verify|observe snapshot|retention inventory|credential status|rotate|revoke|apply")
|
||||
return errors.New("usage: orbit-admin release manifest|bootstrap|console-user create|verify|observe snapshot|retention inventory|credential status|rotate|revoke|apply")
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "release":
|
||||
return runRelease(os.Args[2:])
|
||||
case "bootstrap":
|
||||
return runBootstrap(os.Args[2:])
|
||||
case "console-user":
|
||||
return runConsoleUser(os.Args[2:])
|
||||
case "verify":
|
||||
return runVerify(os.Args[2:])
|
||||
case "observe":
|
||||
@@ -42,7 +45,7 @@ func run() error {
|
||||
case "credential":
|
||||
return runCredential(os.Args[2:])
|
||||
}
|
||||
return errors.New("usage: orbit-admin release manifest|bootstrap|verify|observe snapshot|retention inventory|credential status|rotate|revoke|apply")
|
||||
return errors.New("usage: orbit-admin release manifest|bootstrap|console-user create|verify|observe snapshot|retention inventory|credential status|rotate|revoke|apply")
|
||||
}
|
||||
|
||||
// releaseManifest 是发布物自身的静态身份清单,不读取数据库、不包含 DSN、凭据或业务数据。
|
||||
@@ -324,3 +327,78 @@ func runBootstrap(args []string) error {
|
||||
fmt.Println("Bootstrap committed. Credentials saved in the requested private file; valid for 30 days.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// runConsoleUser 创建一个控制台登录账号。密码默认从标准输入读取,避免出现在
|
||||
// shell 历史和进程列表;账号只绑定已有业务凭据,不会生成或输出业务 token。
|
||||
func runConsoleUser(args []string) error {
|
||||
if len(args) == 0 || args[0] != "create" {
|
||||
return errors.New("usage: orbit-admin console-user create --credential-file path --username name [--display-name name] [--password-stdin]")
|
||||
}
|
||||
flags := flag.NewFlagSet("console-user create", flag.ContinueOnError)
|
||||
var namespace, credential, credentialFile, username, displayName, password string
|
||||
var passwordStdin bool
|
||||
flags.StringVar(&namespace, "namespace-id", "", "namespace id; can be read from --credential-file")
|
||||
flags.StringVar(&credential, "credential-id", "", "credential id; can be read from --credential-file")
|
||||
flags.StringVar(&credentialFile, "credential-file", "", "private bootstrap credential JSON file")
|
||||
flags.StringVar(&username, "username", "", "console login username")
|
||||
flags.StringVar(&displayName, "display-name", "", "console display name")
|
||||
flags.StringVar(&password, "password", "", "password; prefer --password-stdin")
|
||||
flags.BoolVar(&passwordStdin, "password-stdin", false, "read password from stdin")
|
||||
if err := flags.Parse(args[1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if flags.NArg() != 0 || username == "" {
|
||||
return errors.New("--username and principal information are required")
|
||||
}
|
||||
if credentialFile != "" {
|
||||
raw, err := os.ReadFile(credentialFile)
|
||||
if err != nil {
|
||||
return errors.New("credential file could not be read")
|
||||
}
|
||||
var file struct {
|
||||
NamespaceID core.ID `json:"namespace_id"`
|
||||
CredentialID core.ID `json:"credential_id"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &file); err != nil {
|
||||
return errors.New("credential file is invalid")
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = string(file.NamespaceID)
|
||||
}
|
||||
if credential == "" {
|
||||
credential = string(file.CredentialID)
|
||||
}
|
||||
}
|
||||
if passwordStdin {
|
||||
raw, err := io.ReadAll(io.LimitReader(os.Stdin, 2049))
|
||||
if err != nil {
|
||||
return errors.New("password could not be read")
|
||||
}
|
||||
password = strings.TrimRight(string(raw), "\r\n")
|
||||
}
|
||||
ns, cred := core.ID(namespace), core.ID(credential)
|
||||
if ns.Validate() != nil || cred.Validate() != nil || password == "" {
|
||||
return errors.New("valid namespace, credential and password are required")
|
||||
}
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
db, err := mysql.Open(ctx, cfg.MySQLDSN, 2, 1)
|
||||
if err != nil {
|
||||
return errors.New("database unavailable")
|
||||
}
|
||||
defer db.Close()
|
||||
user, err := mysql.NewConsoleAuthStore(db).CreateConsoleUser(ctx, mysql.ConsoleUserInput{NamespaceID: ns, CredentialID: cred, Username: username, DisplayName: displayName, Password: password})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := json.Marshal(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(encoded))
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user