Create identities for basic auth access to mount points

This commit is contained in:
Ingo Oppermann
2023-05-24 14:29:14 +02:00
parent 7a1eb1251b
commit 3c89cbb831
9 changed files with 231 additions and 72 deletions

View File

@@ -398,10 +398,6 @@ func (a *api) start() error {
},
},
Services: iam.UserAuthServices{
Basic: iam.UserAuthPassword{
Enable: cfg.Storage.Memory.Auth.Enable,
Password: cfg.Storage.Memory.Auth.Password,
},
Token: []string{
cfg.RTMP.Token,
cfg.SRT.Token,
@@ -419,6 +415,126 @@ func (a *api) start() error {
}
}
// Create policies and users in order to mimic the behaviour before IAM
policies := []iam.Policy{
{
Name: "$anon",
Domain: "$none",
Resource: "fs:/**",
Actions: []string{"GET", "HEAD", "OPTIONS"},
},
{
Name: "$anon",
Domain: "$none",
Resource: "api:/api",
Actions: []string{"GET", "HEAD", "OPTIONS"},
},
{
Name: "$anon",
Domain: "$none",
Resource: "api:/api/v3/widget/process/**",
Actions: []string{"GET", "HEAD", "OPTIONS"},
},
}
users := []iam.User{}
if !cfg.Storage.Memory.Auth.Enable {
policies = append(policies, iam.Policy{
Name: "$anon",
Domain: "$none",
Resource: "fs:/memfs/**",
Actions: []string{"ANY"},
})
} else {
if cfg.Storage.Memory.Auth.Username != superuser.Name {
users = append(users, iam.User{
Name: cfg.Storage.Memory.Auth.Username,
Auth: iam.UserAuth{
Services: iam.UserAuthServices{
Basic: []iam.UserAuthPassword{
{
Enable: cfg.Storage.Memory.Auth.Enable,
Password: cfg.Storage.Memory.Auth.Password,
},
},
},
},
})
} else {
superuser.Auth.Services.Basic = append(superuser.Auth.Services.Basic, iam.UserAuthPassword{
Enable: cfg.Storage.Memory.Auth.Enable,
Password: cfg.Storage.Memory.Auth.Password,
})
}
policies = append(policies, iam.Policy{
Name: cfg.Storage.Memory.Auth.Username,
Domain: "$none",
Resource: "fs:/memfs/**",
Actions: []string{"ANY"},
})
}
for _, s := range cfg.Storage.S3 {
if !s.Auth.Enable {
policies = append(policies, iam.Policy{
Name: "$anon",
Domain: "$none",
Resource: "fs:" + s.Mountpoint + "/**",
Actions: []string{"ANY"},
})
} else {
if s.Auth.Username != superuser.Name {
users = append(users, iam.User{
Name: s.Auth.Username,
Auth: iam.UserAuth{
Services: iam.UserAuthServices{
Basic: []iam.UserAuthPassword{
{
Enable: s.Auth.Enable,
Password: s.Auth.Password,
},
},
},
},
})
} else {
superuser.Auth.Services.Basic = append(superuser.Auth.Services.Basic, iam.UserAuthPassword{
Enable: s.Auth.Enable,
Password: s.Auth.Password,
})
}
policies = append(policies, iam.Policy{
Name: s.Auth.Username,
Domain: "$none",
Resource: "fs:" + s.Mountpoint + "/**",
Actions: []string{"ANY"},
})
}
}
if cfg.RTMP.Enable && len(cfg.RTMP.Token) == 0 {
policies = append(policies, iam.Policy{
Name: "$anon",
Domain: "$none",
Resource: "rtmp:/**",
Actions: []string{"ANY"},
})
}
if cfg.SRT.Enable && len(cfg.SRT.Token) == 0 {
policies = append(policies, iam.Policy{
Name: "$anon",
Domain: "$none",
Resource: "srt:**",
Actions: []string{"ANY"},
})
}
fs, err := fs.NewRootedDiskFilesystem(fs.RootedDiskConfig{
Root: filepath.Join(cfg.DB.Dir, "iam"),
})
@@ -442,24 +558,21 @@ func (a *api) start() error {
return fmt.Errorf("iam: %w", err)
}
// Create default policies for anonymous users in order to mimic the behaviour before IAM
iam.RemovePolicy("$anon", "$none", "", nil)
iam.AddPolicy("$anon", "$none", "fs:/**", []string{"GET", "HEAD", "OPTIONS"})
iam.AddPolicy("$anon", "$none", "api:/api", []string{"GET", "HEAD", "OPTIONS"})
iam.AddPolicy("$anon", "$none", "api:/api/v3/widget/process/**", []string{"GET", "HEAD", "OPTIONS"})
if !cfg.Storage.Memory.Auth.Enable {
iam.AddPolicy("$anon", "$none", "fs:/memfs/**", []string{"ANY"})
for _, user := range users {
if _, err := iam.GetIdentity(user.Name); err == nil {
continue
}
if cfg.RTMP.Enable && len(cfg.RTMP.Token) == 0 {
iam.AddPolicy("$anon", "$none", "rtmp:/**", []string{"ANY"})
err := iam.CreateIdentity(user)
if err != nil {
return fmt.Errorf("iam: %w", err)
}
}
if cfg.SRT.Enable && len(cfg.SRT.Token) == 0 {
iam.AddPolicy("$anon", "$none", "srt:**", []string{"ANY"})
iam.SaveIdentities()
for _, policy := range policies {
iam.AddPolicy(policy.Name, policy.Domain, policy.Resource, policy.Actions)
}
a.iam = iam

View File

@@ -82,10 +82,10 @@ type Data struct {
} `json:"disk"`
Memory struct {
Auth struct {
Enable bool `json:"enable"`
Username string `json:"username"`
Password string `json:"password"`
} `json:"auth"`
Enable bool `json:"enable"` // Deprecated, use IAM
Username string `json:"username"` // Deprecated, use IAM
Password string `json:"password"` // Deprecated, use IAM
} `json:"auth"` // Deprecated, use IAM
Size int64 `json:"max_size_mbytes" format:"int64"`
Purge bool `json:"purge"`
} `json:"memory"`
@@ -101,13 +101,13 @@ type Data struct {
Address string `json:"address"`
AddressTLS string `json:"address_tls"`
App string `json:"app"`
Token string `json:"token"`
Token string `json:"token"` // Deprecated, use IAM
} `json:"rtmp"`
SRT struct {
Enable bool `json:"enable"`
Address string `json:"address"`
Passphrase string `json:"passphrase"`
Token string `json:"token"`
Token string `json:"token"` // Deprecated, use IAM
Log struct {
Enable bool `json:"enable"`
Topics []string `json:"topics"`

View File

@@ -13,7 +13,7 @@ import (
type S3Storage struct {
Name string `json:"name"`
Mountpoint string `json:"mountpoint"`
Auth S3StorageAuth `json:"auth"`
Auth S3StorageAuth `json:"auth"` // Deprecated, use IAM
Endpoint string `json:"endpoint"`
AccessKeyID string `json:"access_key_id"`
SecretAccessKey string `json:"secret_access_key"`
@@ -23,9 +23,9 @@ type S3Storage struct {
}
type S3StorageAuth struct {
Enable bool `json:"enable"`
Username string `json:"username"`
Password string `json:"password"`
Enable bool `json:"enable"` // Deprecated, use IAM
Username string `json:"username"` // Deprecated, use IAM
Password string `json:"password"` // Deprecated, use IAM
}
func (t *S3Storage) String() string {

View File

@@ -25,14 +25,17 @@ func (u *IAMUser) Marshal(user iam.User, policies []iam.Policy) {
},
},
Services: IAMUserAuthServices{
Basic: IAMUserAuthPassword{
Enable: user.Auth.Services.Basic.Enable,
Password: user.Auth.Services.Basic.Password,
},
Token: user.Auth.Services.Token,
},
}
for _, basic := range user.Auth.Services.Basic {
u.Auth.Services.Basic = append(u.Auth.Services.Basic, IAMUserAuthPassword{
Enable: basic.Enable,
Password: basic.Password,
})
}
for _, p := range policies {
u.Policies = append(u.Policies, IAMPolicy{
Domain: p.Domain,
@@ -63,15 +66,18 @@ func (u *IAMUser) Unmarshal() (iam.User, []iam.Policy) {
},
},
Services: iam.UserAuthServices{
Basic: iam.UserAuthPassword{
Enable: u.Auth.Services.Basic.Enable,
Password: u.Auth.Services.Basic.Password,
},
Token: u.Auth.Services.Token,
},
},
}
for _, basic := range u.Auth.Services.Basic {
iamuser.Auth.Services.Basic = append(iamuser.Auth.Services.Basic, iam.UserAuthPassword{
Enable: basic.Enable,
Password: basic.Password,
})
}
iampolicies := []iam.Policy{}
for _, p := range u.Policies {
@@ -103,7 +109,7 @@ type IAMUserAuthAPIAuth0 struct {
}
type IAMUserAuthServices struct {
Basic IAMUserAuthPassword `json:"basic"`
Basic []IAMUserAuthPassword `json:"basic"`
Token []string `json:"token"`
}

View File

@@ -51,12 +51,14 @@ func getIAM() (iam.IAM, error) {
},
},
Services: iam.UserAuthServices{
Basic: iam.UserAuthPassword{
Basic: []iam.UserAuthPassword{
{
Enable: true,
Password: "secret",
},
},
},
},
})
return i, nil

View File

@@ -125,7 +125,7 @@ func (a *adapter) importPolicy(model model.Model, rule []string) error {
"domain": copiedRule[2],
"resource": copiedRule[3],
"actions": copiedRule[4],
}).Log("imported policy")
}).Log("Imported policy")
ok, err := model.HasPolicyEx(copiedRule[0], copiedRule[0], copiedRule[1:])
if err != nil {
@@ -215,7 +215,7 @@ func (a *adapter) addPolicy(ptype string, rule []string) error {
"domain": domain,
"resource": resource,
"actions": actions,
}).Log("adding policy")
}).Log("Adding policy")
} else if ptype == "g" {
username = rule[0]
role = rule[1]
@@ -225,7 +225,7 @@ func (a *adapter) addPolicy(ptype string, rule []string) error {
"subject": username,
"role": role,
"domain": domain,
}).Log("adding role mapping")
}).Log("Adding role mapping")
} else {
return fmt.Errorf("unknown ptype: %s", ptype)
}
@@ -415,7 +415,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
"domain": domain,
"resource": resource,
"actions": actions,
}).Log("removing policy")
}).Log("Removing policy")
} else if ptype == "g" {
username = rule[0]
role = rule[1]
@@ -425,7 +425,7 @@ func (a *adapter) removePolicy(ptype string, rule []string) error {
"subject": username,
"role": role,
"domain": domain,
}).Log("adding role mapping")
}).Log("Removing role mapping")
} else {
return fmt.Errorf("unknown ptype: %s", ptype)
}

View File

@@ -44,7 +44,7 @@ type UserAuthAPIAuth0 struct {
}
type UserAuthServices struct {
Basic UserAuthPassword `json:"basic"`
Basic []UserAuthPassword `json:"basic"`
Token []string `json:"token"`
}
@@ -58,9 +58,11 @@ func (u *User) validate() error {
return fmt.Errorf("the name is required")
}
re := regexp.MustCompile(`[^A-Za-z0-9_-]`)
chars := `A-Za-z0-9:_-`
re := regexp.MustCompile(`[^` + chars + `]`)
if re.MatchString(u.Name) {
return fmt.Errorf("the name can only the contain [A-Za-z0-9_-]")
return fmt.Errorf("the name can only contain [%s]", chars)
}
if u.Auth.API.Userpass.Enable && len(u.Auth.API.Userpass.Password) == 0 {
@@ -71,8 +73,10 @@ func (u *User) validate() error {
return fmt.Errorf("a user for Auth0 login is required")
}
if u.Auth.Services.Basic.Enable && len(u.Auth.Services.Basic.Password) == 0 {
return fmt.Errorf("a password for service basic auth is required")
for i, basic := range u.Auth.Services.Basic {
if basic.Enable && len(basic.Password) == 0 {
return fmt.Errorf("a password for service basic auth nr. %d is required", i)
}
}
return nil
@@ -306,11 +310,24 @@ func (i *identity) VerifyServiceBasicAuth(password string) (bool, error) {
return false, fmt.Errorf("invalid identity")
}
if !i.user.Auth.Services.Basic.Enable {
return false, fmt.Errorf("authentication method disabled")
valid := false
for _, basic := range i.user.Auth.Services.Basic {
if !basic.Enable {
continue
}
return i.user.Auth.Services.Basic.Password == password, nil
if basic.Password == password {
valid = true
break
}
}
if !valid {
return false, nil
}
return true, nil
}
func (i *identity) GetServiceBasicAuth() string {
@@ -321,11 +338,15 @@ func (i *identity) GetServiceBasicAuth() string {
return ""
}
if !i.user.Auth.Services.Basic.Enable {
return ""
for _, basic := range i.user.Auth.Services.Basic {
if !basic.Enable {
continue
}
return i.user.Auth.Services.Basic.Password
return basic.Password
}
return ""
}
func (i *identity) VerifyServiceToken(token string) (bool, error) {
@@ -528,6 +549,8 @@ func (im *identityManager) create(u User) (*identity, error) {
identity.valid = true
im.logger.Debug().WithField("name", identity.Name()).Log("Identity created")
return identity, nil
}
@@ -573,6 +596,11 @@ func (im *identityManager) Update(name string, u User) error {
im.identities[identity.user.Name] = identity
im.logger.Debug().WithFields(log.Fields{
"oldname": name,
"newname": identity.Name(),
}).Log("Identity updated")
if im.autosave {
im.save(im.filePath)
}
@@ -765,6 +793,8 @@ func (im *identityManager) save(filePath string) error {
_, _, err = im.fs.WriteFileSafe(filePath, jsondata)
im.logger.Debug().WithField("path", filePath).Log("Identity file save")
return err
}

View File

@@ -46,11 +46,13 @@ func TestUserAuth(t *testing.T) {
err = user.validate()
require.NoError(t, err)
user.Auth.Services.Basic.Enable = true
user.Auth.Services.Basic = append(user.Auth.Services.Basic, UserAuthPassword{
Enable: true,
})
err = user.validate()
require.Error(t, err)
user.Auth.Services.Basic.Password = "secret"
user.Auth.Services.Basic[0].Password = "secret"
err = user.validate()
require.NoError(t, err)
}
@@ -159,8 +161,10 @@ func TestIdentityServiceBasicAuth(t *testing.T) {
require.False(t, ok)
require.Error(t, err)
identity.user.Auth.Services.Basic.Enable = true
identity.user.Auth.Services.Basic.Password = "secret"
identity.user.Auth.Services.Basic = append(identity.user.Auth.Services.Basic, UserAuthPassword{
Enable: true,
Password: "secret",
})
ok, err = identity.VerifyServiceBasicAuth("secret")
require.False(t, ok)
@@ -172,14 +176,14 @@ func TestIdentityServiceBasicAuth(t *testing.T) {
require.True(t, ok)
require.NoError(t, err)
identity.user.Auth.Services.Basic.Enable = false
identity.user.Auth.Services.Basic[0].Enable = false
ok, err = identity.VerifyServiceBasicAuth("secret")
require.False(t, ok)
require.Error(t, err)
require.NoError(t, err)
identity.user.Auth.Services.Basic.Enable = true
identity.user.Auth.Services.Basic.Password = "terces"
identity.user.Auth.Services.Basic[0].Enable = true
identity.user.Auth.Services.Basic[0].Password = "terces"
ok, err = identity.VerifyServiceBasicAuth("secret")
require.False(t, ok)
@@ -610,10 +614,12 @@ func TestRemoveUser(t *testing.T) {
Auth0: UserAuthAPIAuth0{},
},
Services: UserAuthServices{
Basic: UserAuthPassword{
Basic: []UserAuthPassword{
{
Enable: true,
Password: "secret",
},
},
Token: []string{"tokensecret"},
},
},

View File

@@ -21,10 +21,12 @@ func getIdentityManager(enableBasic bool) iam.IdentityManager {
Auth: iam.UserAuth{
API: iam.UserAuthAPI{},
Services: iam.UserAuthServices{
Basic: iam.UserAuthPassword{
Basic: []iam.UserAuthPassword{
{
Enable: enableBasic,
Password: "basicauthpassword",
},
},
Token: []string{"servicetoken"},
},
},