diff --git a/app/api/api.go b/app/api/api.go index 6e41e15e..f6a78063 100644 --- a/app/api/api.go +++ b/app/api/api.go @@ -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 + for _, user := range users { + if _, err := iam.GetIdentity(user.Name); err == nil { + continue + } - 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"}) + err := iam.CreateIdentity(user) + if err != nil { + return fmt.Errorf("iam: %w", err) + } } - if cfg.RTMP.Enable && len(cfg.RTMP.Token) == 0 { - iam.AddPolicy("$anon", "$none", "rtmp:/**", []string{"ANY"}) - } + iam.SaveIdentities() - if cfg.SRT.Enable && len(cfg.SRT.Token) == 0 { - iam.AddPolicy("$anon", "$none", "srt:**", []string{"ANY"}) + for _, policy := range policies { + iam.AddPolicy(policy.Name, policy.Domain, policy.Resource, policy.Actions) } a.iam = iam diff --git a/config/data.go b/config/data.go index 35507888..98db0867 100644 --- a/config/data.go +++ b/config/data.go @@ -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"` diff --git a/config/value/s3.go b/config/value/s3.go index 7d80c193..52e5c934 100644 --- a/config/value/s3.go +++ b/config/value/s3.go @@ -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 { diff --git a/http/api/iam.go b/http/api/iam.go index a56aaf62..a8e1f1fc 100644 --- a/http/api/iam.go +++ b/http/api/iam.go @@ -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,8 +109,8 @@ type IAMUserAuthAPIAuth0 struct { } type IAMUserAuthServices struct { - Basic IAMUserAuthPassword `json:"basic"` - Token []string `json:"token"` + Basic []IAMUserAuthPassword `json:"basic"` + Token []string `json:"token"` } type IAMUserAuthPassword struct { diff --git a/http/middleware/iam/iam_test.go b/http/middleware/iam/iam_test.go index 7d77246c..116bced4 100644 --- a/http/middleware/iam/iam_test.go +++ b/http/middleware/iam/iam_test.go @@ -51,9 +51,11 @@ func getIAM() (iam.IAM, error) { }, }, Services: iam.UserAuthServices{ - Basic: iam.UserAuthPassword{ - Enable: true, - Password: "secret", + Basic: []iam.UserAuthPassword{ + { + Enable: true, + Password: "secret", + }, }, }, }, diff --git a/iam/adapter.go b/iam/adapter.go index 74502cef..955ed4c2 100644 --- a/iam/adapter.go +++ b/iam/adapter.go @@ -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) } diff --git a/iam/identity.go b/iam/identity.go index 18d5d2c8..4eaf66ae 100644 --- a/iam/identity.go +++ b/iam/identity.go @@ -44,8 +44,8 @@ type UserAuthAPIAuth0 struct { } type UserAuthServices struct { - Basic UserAuthPassword `json:"basic"` - Token []string `json:"token"` + Basic []UserAuthPassword `json:"basic"` + Token []string `json:"token"` } type UserAuthPassword struct { @@ -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 + } + + if basic.Password == password { + valid = true + break + } } - return i.user.Auth.Services.Basic.Password == password, nil + 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 basic.Password } - return i.user.Auth.Services.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 } diff --git a/iam/identity_test.go b/iam/identity_test.go index f1798dc5..9ea6c6a1 100644 --- a/iam/identity_test.go +++ b/iam/identity_test.go @@ -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,9 +614,11 @@ func TestRemoveUser(t *testing.T) { Auth0: UserAuthAPIAuth0{}, }, Services: UserAuthServices{ - Basic: UserAuthPassword{ - Enable: true, - Password: "secret", + Basic: []UserAuthPassword{ + { + Enable: true, + Password: "secret", + }, }, Token: []string{"tokensecret"}, }, diff --git a/restream/rewrite/rewrite_test.go b/restream/rewrite/rewrite_test.go index 3e0e5fe9..63c74082 100644 --- a/restream/rewrite/rewrite_test.go +++ b/restream/rewrite/rewrite_test.go @@ -21,9 +21,11 @@ func getIdentityManager(enableBasic bool) iam.IdentityManager { Auth: iam.UserAuth{ API: iam.UserAuthAPI{}, Services: iam.UserAuthServices{ - Basic: iam.UserAuthPassword{ - Enable: enableBasic, - Password: "basicauthpassword", + Basic: []iam.UserAuthPassword{ + { + Enable: enableBasic, + Password: "basicauthpassword", + }, }, Token: []string{"servicetoken"}, },