mirror of
https://github.com/bytedream/docker4ssh.git
synced 2025-06-27 09:50:31 +02:00
Initial commit
This commit is contained in:
254
server/config/profile.go
Normal file
254
server/config/profile.go
Normal file
@ -0,0 +1,254 @@
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user