mirror of
https://github.com/bytedream/docker4ssh.git
synced 2025-05-09 12:15:11 +02:00
255 lines
6.6 KiB
Go
255 lines
6.6 KiB
Go
package config
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/BurntSushi/toml"
|
|
"go.uber.org/zap"
|
|
"hash"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
type Profile struct {
|
|
name string
|
|
Username *regexp.Regexp
|
|
Password *regexp.Regexp
|
|
passwordHashAlgo hash.Hash
|
|
NetworkMode int
|
|
Configurable bool
|
|
RunLevel int
|
|
StartupInformation bool
|
|
ExitAfter string
|
|
KeepOnExit bool
|
|
Image string
|
|
ContainerID string
|
|
}
|
|
|
|
func (p *Profile) Name() string {
|
|
return p.name
|
|
}
|
|
|
|
func (p *Profile) Match(user string, password []byte) bool {
|
|
// username should only be nil if profile was generated from Config.Profile.Dynamic
|
|
if p.Username == nil || p.Username.MatchString(user) {
|
|
if p.passwordHashAlgo != nil {
|
|
password = p.passwordHashAlgo.Sum(password)
|
|
}
|
|
return p.Password.Match(password)
|
|
}
|
|
return false
|
|
}
|
|
|
|
type preProfile struct {
|
|
Username string
|
|
Password string
|
|
NetworkMode int
|
|
Configurable bool
|
|
RunLevel int
|
|
StartupInformation bool
|
|
ExitAfter string
|
|
KeepOnExit bool
|
|
Image string
|
|
Container string
|
|
}
|
|
|
|
func LoadProfileFile(path string, defaultPreProfile preProfile) (Profiles, error) {
|
|
var rawProfile map[string]interface{}
|
|
if _, err := toml.DecodeFile(path, &rawProfile); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
profiles, err := parseRawProfile(rawProfile, path, defaultPreProfile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return profiles, nil
|
|
}
|
|
|
|
func LoadProfileDir(path string, defaultPreProfile preProfile) (Profiles, error) {
|
|
dir, err := os.ReadDir(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var profiles Profiles
|
|
for i, profileConf := range dir {
|
|
p, err := LoadProfileFile(filepath.Join(path, profileConf.Name()), defaultPreProfile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
profiles = append(profiles, p...)
|
|
zap.S().Debugf("Pre-loaded file %d (%s) with %d profile(s)", i+1, profileConf.Name(), len(p))
|
|
}
|
|
|
|
return profiles, nil
|
|
}
|
|
|
|
func parseRawProfile(rawProfile map[string]interface{}, path string, defaultPreProfile preProfile) (profiles []*Profile, err error) {
|
|
var count int
|
|
for key, value := range rawProfile {
|
|
rawValue, err := json.Marshal(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pp := preProfile{
|
|
NetworkMode: 3,
|
|
RunLevel: 1,
|
|
StartupInformation: true,
|
|
}
|
|
if err = json.Unmarshal(rawValue, &pp); err != nil {
|
|
return nil, fmt.Errorf("failed to parse %s profile conf file %s: %v", key, path, err)
|
|
}
|
|
|
|
var rawUsername string
|
|
if rawUsername = strings.TrimPrefix(pp.Username, "regex:"); rawUsername == pp.Username {
|
|
rawUsername = strings.ReplaceAll(rawUsername, "*", ".*")
|
|
}
|
|
if !strings.HasSuffix(rawUsername, "$") {
|
|
rawUsername += "$"
|
|
}
|
|
username, err := regexp.Compile("(?m)" + rawUsername)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse %s profile username regex for conf file %s: %v", key, path, err)
|
|
}
|
|
|
|
var rawPassword string
|
|
if rawPassword = strings.TrimPrefix(pp.Password, "regex:"); rawUsername == pp.Password {
|
|
rawPassword = strings.ReplaceAll(rawPassword, "*", ".*")
|
|
}
|
|
algo, rawPasswordOrHash := getHash(rawPassword)
|
|
if algo == nil && rawPasswordOrHash == "" {
|
|
rawPasswordOrHash = ".*"
|
|
}
|
|
if !strings.HasSuffix(rawPasswordOrHash, "$") {
|
|
rawPasswordOrHash += "$"
|
|
}
|
|
password, err := regexp.Compile("(?m)" + rawPasswordOrHash)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse %s profile password regex for conf file %s: %v", key, path, err)
|
|
}
|
|
|
|
if (pp.Image == "") == (pp.Container == "") {
|
|
return nil, fmt.Errorf("failed to interpret %s profile image / container definition for conf file %s: `Image` or `Container` must be specified, not both nor none of them", key, path)
|
|
}
|
|
|
|
profiles = append(profiles, &Profile{
|
|
name: key,
|
|
Username: username,
|
|
Password: password,
|
|
passwordHashAlgo: algo,
|
|
NetworkMode: pp.NetworkMode,
|
|
Configurable: pp.Configurable,
|
|
RunLevel: pp.RunLevel,
|
|
StartupInformation: pp.StartupInformation,
|
|
ExitAfter: pp.ExitAfter,
|
|
KeepOnExit: pp.KeepOnExit,
|
|
Image: pp.Image,
|
|
ContainerID: pp.Container,
|
|
})
|
|
count++
|
|
zap.S().Debugf("Pre-loaded profile %s (%d)", key, count)
|
|
}
|
|
return
|
|
}
|
|
|
|
type Profiles []*Profile
|
|
|
|
func (ps Profiles) GetByName(name string) (*Profile, bool) {
|
|
for _, profile := range ps {
|
|
if profile.name == name {
|
|
return profile, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (ps Profiles) Match(user string, password []byte) (*Profile, bool) {
|
|
for _, profile := range ps {
|
|
if profile.Match(user, password) {
|
|
return profile, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func DefaultPreProfileFromConfig(config *Config) preProfile {
|
|
defaultProfile := config.Profile.Default
|
|
|
|
return preProfile{
|
|
Password: defaultProfile.Password,
|
|
NetworkMode: defaultProfile.NetworkMode,
|
|
Configurable: defaultProfile.Configurable,
|
|
RunLevel: defaultProfile.RunLevel,
|
|
StartupInformation: defaultProfile.StartupInformation,
|
|
ExitAfter: defaultProfile.ExitAfter,
|
|
KeepOnExit: defaultProfile.KeepOnExit,
|
|
}
|
|
}
|
|
|
|
func HardcodedPreProfile() preProfile {
|
|
return preProfile{
|
|
NetworkMode: 3,
|
|
RunLevel: 1,
|
|
StartupInformation: true,
|
|
}
|
|
}
|
|
|
|
func DynamicProfileFromConfig(config *Config, defaultPreProfile preProfile) (Profile, error) {
|
|
raw, err := json.Marshal(config.Profile.Dynamic)
|
|
if err != nil {
|
|
return Profile{}, err
|
|
}
|
|
json.Unmarshal(raw, &defaultPreProfile)
|
|
|
|
algo, rawPasswordOrHash := getHash(defaultPreProfile.Password)
|
|
if algo == nil && rawPasswordOrHash == "" {
|
|
rawPasswordOrHash = ".*"
|
|
}
|
|
password, err := regexp.Compile("(?m)" + rawPasswordOrHash)
|
|
if err != nil {
|
|
return Profile{}, fmt.Errorf("failed to parse password regex: %v ", err)
|
|
}
|
|
|
|
return Profile{
|
|
name: "",
|
|
Username: nil,
|
|
Password: password,
|
|
passwordHashAlgo: algo,
|
|
NetworkMode: defaultPreProfile.NetworkMode,
|
|
Configurable: defaultPreProfile.Configurable,
|
|
RunLevel: defaultPreProfile.RunLevel,
|
|
StartupInformation: defaultPreProfile.StartupInformation,
|
|
ExitAfter: defaultPreProfile.ExitAfter,
|
|
KeepOnExit: defaultPreProfile.KeepOnExit,
|
|
}, nil
|
|
}
|
|
|
|
func getHash(password string) (algo hash.Hash, raw string) {
|
|
split := strings.SplitN(password, ":", 1)
|
|
|
|
if len(split) == 1 {
|
|
return nil, password
|
|
} else {
|
|
raw = split[1]
|
|
}
|
|
|
|
switch split[0] {
|
|
case "sha1":
|
|
algo = sha1.New()
|
|
case "sha256":
|
|
algo = sha256.New()
|
|
case "sha512":
|
|
algo = sha512.New()
|
|
default:
|
|
algo = nil
|
|
}
|
|
return
|
|
}
|