mirror of https://github.com/k3s-io/k3s
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
164 lines
2.7 KiB
164 lines
2.7 KiB
package passwd |
|
|
|
import ( |
|
"encoding/csv" |
|
"fmt" |
|
"io" |
|
"os" |
|
"strings" |
|
|
|
"github.com/k3s-io/k3s/pkg/token" |
|
"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) Check(name, pass string) (matches bool, exists bool) { |
|
e, ok := p.names[name] |
|
if !ok { |
|
return false, false |
|
} |
|
return e.pass == pass, true |
|
} |
|
|
|
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 := token.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) |
|
}
|
|
|