112 lines
2.6 KiB
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)
|
|
}
|
|
}
|
|
}
|