Add ocis idm resetpassword subcommand

This allows resetting the ocis admin user's password (when ocis is
not running) when running with the builtin LDAP server.

Fixes #4084
This commit is contained in:
root
2022-08-09 07:28:03 +00:00
committed by Ralf Haferkamp
parent f6f6a50de8
commit eb5e9675d6
6 changed files with 154 additions and 1 deletions

View File

@@ -0,0 +1,8 @@
Enhancement: added command to reset administrator password
The new command `ocis idm resetpassword` allows to reset the administrator
password when ocis is not running. So it is possible to recover setups
where the admin password was lost.
https://github.com/owncloud/ocis/issues/4084
https://github.com/owncloud/ocis/pull/4365

View File

@@ -0,0 +1,24 @@
---
title: Resetting a lost administrator password
date: 2022-08-29:00:00+00:00
weight: 10
geekdocRepo: https://github.com/owncloud/ocis
geekdocEditPath: edit/master/docs/services/idm
geekdocFilePath: admin_password_reset.md
geekdocCollapseSection: true
---
## Resetting a lost administrator password
By default, when using oCIS with the builtin IDM an ad generates the
user `admin` (DN `uid=admin,ou=users,o=libregraph-idm`) if, for any
reason, the password of that user is lost, it can be reset using
the `resetpassword` sub-command:
```
ocis idm resetpassword
```
It will prompt for a new password and set the password of that user
accordingly. Note: As this command is accessing the idm database directly
will only work while ocis is not running and nothing else is accessing
database.

3
go.mod
View File

@@ -63,6 +63,7 @@ require (
github.com/urfave/cli/v2 v2.11.1
github.com/xhit/go-simple-mail/v2 v2.11.0
go-micro.dev/v4 v4.8.0
go.etcd.io/bbolt v1.3.6
go.opencensus.io v0.23.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.34.0
go.opentelemetry.io/otel v1.9.0
@@ -74,6 +75,7 @@ require (
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e
golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035
google.golang.org/genproto v0.0.0-20220805133916-01dd62135a58
google.golang.org/grpc v1.48.0
google.golang.org/protobuf v1.28.1
@@ -254,7 +256,6 @@ require (
github.com/wk8/go-ordered-map v1.0.0 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/api/v3 v3.5.2 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.2 // indirect
go.etcd.io/etcd/client/v3 v3.5.2 // indirect

1
go.sum
View File

@@ -1568,6 +1568,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -0,0 +1,118 @@
package command
import (
"context"
"errors"
"fmt"
"os"
"syscall"
"time"
"github.com/go-ldap/ldap/v3"
"github.com/libregraph/idm/pkg/ldbbolt"
"github.com/libregraph/idm/server"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/services/idm/pkg/config"
"github.com/owncloud/ocis/v2/services/idm/pkg/config/parser"
"github.com/owncloud/ocis/v2/services/idm/pkg/logging"
"github.com/urfave/cli/v2"
bolt "go.etcd.io/bbolt"
"golang.org/x/term"
)
// ResetPassword is the entrypoint for the resetpassword command
func ResetPassword(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "resetpassword",
Usage: fmt.Sprintf("Reset admin password"),
Category: "password reset",
Before: func(c *cli.Context) error {
err := parser.ParseConfig(cfg)
if err != nil {
fmt.Printf("%v", err)
os.Exit(1)
}
return err
},
Action: func(c *cli.Context) error {
logger := logging.Configure(cfg.Service.Name, cfg.Log)
ctx, cancel := func() (context.Context, context.CancelFunc) {
if cfg.Context == nil {
return context.WithCancel(context.Background())
}
return context.WithCancel(cfg.Context)
}()
defer cancel()
return resetPassword(ctx, logger, cfg)
},
}
}
func resetPassword(ctx context.Context, logger log.Logger, cfg *config.Config) error {
servercfg := server.Config{
Logger: log.LogrusWrap(logger.Logger),
LDAPHandler: "boltdb",
LDAPBaseDN: "o=libregraph-idm",
BoltDBFile: cfg.IDM.DatabasePath,
}
adminUserDN := "uid=admin,ou=users," + servercfg.LDAPBaseDN
fmt.Printf("Resetting password for user '%s'.\n", adminUserDN)
if _, err := os.Stat(servercfg.BoltDBFile); errors.Is(err, os.ErrNotExist) {
fmt.Fprintf(os.Stderr, "IDM database does not exist.\n")
return err
}
newPw, err := getPassword()
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading password: %v\n", err)
return err
}
bdb := &ldbbolt.LdbBolt{}
opts := bolt.Options{
Timeout: 1 * time.Millisecond,
}
if err := bdb.Configure(servercfg.Logger, servercfg.LDAPBaseDN, servercfg.BoltDBFile, &opts); err != nil {
fmt.Fprintf(os.Stderr, "Failed to open database: '%s'. Please stop any running ocis/idm instance, as this tool requires exclusive access to the database.\n", err)
return err
}
defer bdb.Close()
if err := bdb.Initialize(); err != nil {
return err
}
pwRequest := ldap.NewPasswordModifyRequest(adminUserDN, "", newPw)
if err := bdb.UpdatePassword(pwRequest); err != nil {
fmt.Fprintf(os.Stderr, "Failed to update admin password: %v\n", err)
}
fmt.Printf("Password for user '%s' updated.\n", adminUserDN)
return nil
}
func getPassword() (string, error) {
fmt.Print("Enter new password: ")
bytePassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", err
}
fmt.Println("")
fmt.Print("Re-enter new password: ")
bytePasswordVerify, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return "", err
}
fmt.Println("")
password := string(bytePassword)
passwordVerify := string(bytePasswordVerify)
if password != passwordVerify {
return "", errors.New("Passwords do not match")
}
return password, nil
}

View File

@@ -18,6 +18,7 @@ func GetCommands(cfg *config.Config) cli.Commands {
Server(cfg),
// interaction with this service
ResetPassword(cfg),
// infos about this service
Health(cfg),