server/remote/dex-update-passwd/main.go

112 lines
2.6 KiB
Go

package main
import (
"context"
"encoding/base64"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"golang.org/x/crypto/bcrypt"
"github.com/dexidp/dex/api/v2"
"github.com/dexidp/dex/storage"
"github.com/ghodss/yaml"
"google.golang.org/grpc"
)
// Keep Config struct in sync with dex one.
// password is storage.Password
// https://github.com/dexidp/dex/blob/master/storage/storage.go#L336
type password storage.Password
func (p *password) UnmarshalJSON(b []byte) error {
var data struct {
Email string `json:"email"`
Username string `json:"username"`
UserID string `json:"userID"`
Hash string `json:"hash"`
HashFromEnv string `json:"hashFromEnv"`
}
if err := json.Unmarshal(b, &data); err != nil {
return err
}
*p = password(storage.Password{
Email: data.Email,
Username: data.Username,
UserID: data.UserID,
})
if len(data.Hash) == 0 && len(data.HashFromEnv) > 0 {
data.Hash = os.Getenv(data.HashFromEnv)
}
if len(data.Hash) == 0 {
return fmt.Errorf("no password hash provided")
}
// If this value is a valid bcrypt, use it.
_, bcryptErr := bcrypt.Cost([]byte(data.Hash))
if bcryptErr == nil {
p.Hash = []byte(data.Hash)
return nil
}
// For backwards compatibility try to base64 decode this value.
hashBytes, err := base64.StdEncoding.DecodeString(data.Hash)
if err != nil {
return fmt.Errorf("malformed bcrypt hash: %v", bcryptErr)
}
if _, err := bcrypt.Cost(hashBytes); err != nil {
return fmt.Errorf("malformed bcrypt hash: %v", err)
}
p.Hash = hashBytes
return nil
}
// https://github.com/dexidp/dex/blob/master/cmd/dex/config.go#L25
type Config struct {
StaticPasswords []password `json:"staticPasswords"`
}
func main() {
flag.Parse()
if flag.NArg() != 1 {
log.Fatal("Need one argument: the new config file")
}
// Load dex config file
configData, err := os.ReadFile(flag.Args()[0])
if err != nil {
log.Fatalf("failed to read config file %s: %v", flag.Args()[0], err)
}
var c Config
if err := yaml.Unmarshal(configData, &c); err != nil {
log.Fatalf("error parse config file %s: %v", flag.Args()[0], err)
}
if len(c.StaticPasswords) == 0 {
log.Fatalf("no password in config file. Aborting")
}
// Connect to dex through GRPC API
conn, err := grpc.Dial("127.0.0.1:5557", grpc.WithInsecure())
if err != nil {
log.Fatalf("failed creating dex client: %v ", err)
}
client := api.NewDexClient(conn)
for _, passwd := range c.StaticPasswords {
req := &api.UpdatePasswordReq{
Email: passwd.Email,
NewHash: passwd.Hash,
}
if _, err := client.UpdatePassword(context.TODO(), req); err != nil {
log.Printf("failed update password for %q: %v", passwd.Email, err)
}
}
}