package passwd import ( "encoding/csv" "fmt" "io" "os" "strings" "github.com/k3s-io/k3s/pkg/util" ) type entry struct { pass string role string } type Passwd struct { changed bool names map[string]entry } func Read(file string) (*Passwd, error) { result := &Passwd{ names: map[string]entry{}, } f, err := os.Open(file) if err != nil { if os.IsNotExist(err) { return result, nil } return nil, err } defer f.Close() reader := csv.NewReader(f) reader.FieldsPerRecord = -1 for { record, err := reader.Read() if err == io.EOF { break } if err != nil { return nil, err } if len(record) < 2 { return nil, fmt.Errorf("password file '%s' must have at least 2 columns (password, name), found %d", file, len(record)) } e := entry{ pass: record[0], } if len(record) > 3 { e.role = record[3] } result.names[record[1]] = e } return result, nil } func (p *Passwd) EnsureUser(name, role, passwd string) error { tokenPrefix := "::" + name + ":" idx := strings.Index(passwd, tokenPrefix) if idx > 0 && strings.HasPrefix(passwd, "K10") { passwd = passwd[idx+len(tokenPrefix):] } if e, ok := p.names[name]; ok { if passwd != "" && e.pass != passwd { p.changed = true e.pass = passwd } if e.role != role { p.changed = true e.role = role } p.names[name] = e return nil } if passwd == "" { token, err := util.Random(16) if err != nil { return err } passwd = token } p.changed = true p.names[name] = entry{ pass: passwd, role: role, } return nil } func (p *Passwd) Users() []string { users := []string{} for user := range p.names { users = append(users, user) } return users } func (p *Passwd) Pass(name string) (string, bool) { e, ok := p.names[name] if !ok { return "", false } return e.pass, true } func (p *Passwd) Write(passwdFile string) error { if !p.changed { return nil } var records [][]string for name, e := range p.names { records = append(records, []string{ e.pass, name, name, e.role, }) } return writePasswords(passwdFile, records) } func writePasswords(passwdFile string, records [][]string) error { err := func() error { // ensure to close tmp file before rename for filesystems like NTFS out, err := os.Create(passwdFile + ".tmp") if err != nil { return err } defer out.Close() if err := util.SetFileModeForFile(out, 0600); err != nil { return err } return csv.NewWriter(out).WriteAll(records) }() if err != nil { return err } return os.Rename(passwdFile+".tmp", passwdFile) }