acl.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. package acl
  2. import (
  3. "context"
  4. "strings"
  5. config "github.com/OliveTin/OliveTin/internal/config"
  6. log "github.com/sirupsen/logrus"
  7. "golang.org/x/exp/slices"
  8. "google.golang.org/grpc/metadata"
  9. )
  10. type PermissionBits int
  11. const (
  12. View PermissionBits = 1 << iota
  13. Exec
  14. Logs
  15. Kill
  16. )
  17. func (p PermissionBits) Has(permission PermissionBits) bool {
  18. return p&permission != 0
  19. }
  20. // User respresents a person.
  21. type AuthenticatedUser struct {
  22. Username string
  23. UsergroupLine string
  24. Provider string
  25. SID string
  26. Acls []string
  27. EffectivePolicy *config.ConfigurationPolicy
  28. }
  29. func (u *AuthenticatedUser) IsGuest() bool {
  30. return u.Username == "guest" && u.Provider == "system"
  31. }
  32. func (u *AuthenticatedUser) parseUsergroupLine(sep string) []string {
  33. ret := []string{}
  34. if sep != "" {
  35. for _, v := range strings.Split(u.UsergroupLine, sep) {
  36. trimmed := strings.TrimSpace(v)
  37. if trimmed != "" {
  38. ret = append(ret, trimmed)
  39. }
  40. }
  41. } else {
  42. ret = strings.Fields(u.UsergroupLine)
  43. }
  44. return ret
  45. }
  46. func (u *AuthenticatedUser) matchesUsergroupAcl(matchUsergroups []string, sep string) bool {
  47. groupList := u.parseUsergroupLine(sep)
  48. for _, group := range groupList {
  49. if slices.Contains(matchUsergroups, group) {
  50. log.Debugf("Usergroup %v found in %+v (len: %v)", group, groupList, len(groupList))
  51. return true
  52. }
  53. }
  54. return false
  55. }
  56. type UnauthenticatedUser struct {
  57. Username string
  58. Usergroup string
  59. Provider string
  60. Sid string
  61. }
  62. func logAclNotMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action, acl *config.AccessControlList) {
  63. if cfg.LogDebugOptions.AclNotMatched {
  64. log.WithFields(log.Fields{
  65. "User": user.Username,
  66. "Action": action.Title,
  67. "ACL": acl.Name,
  68. }).Debugf("%v - ACL Not Matched", aclFunction)
  69. }
  70. }
  71. func logAclMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action, acl *config.AccessControlList) {
  72. actionTitle := "N/A"
  73. if action != nil {
  74. actionTitle = action.Title
  75. }
  76. if cfg.LogDebugOptions.AclMatched {
  77. log.WithFields(log.Fields{
  78. "User": user.Username,
  79. "Action": actionTitle,
  80. "ACL": acl.Name,
  81. }).Debugf("%v - Matched ACL", aclFunction)
  82. }
  83. }
  84. func logAclNoneMatched(cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action, defaultPermission bool) {
  85. if cfg.LogDebugOptions.AclNoneMatched {
  86. log.WithFields(log.Fields{
  87. "User": user.Username,
  88. "Action": action.Title,
  89. "Default": defaultPermission,
  90. }).Debugf("%v - No ACLs Matched, returning default permission", aclFunction)
  91. }
  92. }
  93. func permissionsConfigToBits(permissions config.PermissionsList) PermissionBits {
  94. type permPair struct {
  95. enabled bool
  96. bit PermissionBits
  97. }
  98. permMap := []permPair{
  99. {permissions.View, View},
  100. {permissions.Exec, Exec},
  101. {permissions.Logs, Logs},
  102. {permissions.Kill, Kill},
  103. }
  104. var ret PermissionBits
  105. for _, perm := range permMap {
  106. if perm.enabled {
  107. ret |= perm.bit
  108. }
  109. }
  110. return ret
  111. }
  112. func aclCheck(requiredPermission PermissionBits, defaultValue bool, cfg *config.Config, aclFunction string, user *AuthenticatedUser, action *config.Action) bool {
  113. relevantAcls := getRelevantAcls(cfg, action.Acls, user)
  114. if cfg.LogDebugOptions.AclCheckStarted {
  115. log.WithFields(log.Fields{
  116. "actionTitle": action.Title,
  117. "username": user.Username,
  118. "usergroupLine": user.UsergroupLine,
  119. "relevantAcls": len(relevantAcls),
  120. "requiredPermission": requiredPermission,
  121. }).Debugf("ACL check - %v", aclFunction)
  122. }
  123. for _, acl := range relevantAcls {
  124. permissionBits := permissionsConfigToBits(acl.Permissions)
  125. if permissionBits.Has(requiredPermission) {
  126. logAclMatched(cfg, aclFunction, user, action, acl)
  127. return true
  128. } else {
  129. logAclNotMatched(cfg, aclFunction, user, action, acl)
  130. }
  131. }
  132. logAclNoneMatched(cfg, aclFunction, user, action, cfg.DefaultPermissions.Logs)
  133. return defaultValue
  134. }
  135. // IsAllowedLogs checks if a AuthenticatedUser is allowed to view an action's logs
  136. func IsAllowedLogs(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool {
  137. return aclCheck(Logs, cfg.DefaultPermissions.Logs, cfg, "isAllowedLogs", user, action)
  138. }
  139. // IsAllowedExec checks if a AuthenticatedUser is allowed to execute an Action
  140. func IsAllowedExec(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool {
  141. return aclCheck(Exec, cfg.DefaultPermissions.Exec, cfg, "isAllowedExec", user, action)
  142. }
  143. // IsAllowedView checks if a User is allowed to view an Action
  144. func IsAllowedView(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool {
  145. if action.Hidden {
  146. return false
  147. }
  148. return aclCheck(View, cfg.DefaultPermissions.View, cfg, "isAllowedView", user, action)
  149. }
  150. func IsAllowedKill(cfg *config.Config, user *AuthenticatedUser, action *config.Action) bool {
  151. return aclCheck(Kill, cfg.DefaultPermissions.Kill, cfg, "isAllowedKill", user, action)
  152. }
  153. func getMetadataKeyOrEmpty(md metadata.MD, key string) string {
  154. mdValues := md.Get(key)
  155. if len(mdValues) > 0 {
  156. return mdValues[0]
  157. }
  158. return ""
  159. }
  160. func UserFromUnauthenticatedUser(cfg *config.Config, unauthenticatedUser UnauthenticatedUser) *AuthenticatedUser {
  161. ret := &AuthenticatedUser{}
  162. ret.Username = unauthenticatedUser.Username
  163. ret.UsergroupLine = unauthenticatedUser.Usergroup
  164. ret.Provider = unauthenticatedUser.Provider
  165. ret.SID = unauthenticatedUser.Sid
  166. if ret.Username == "" {
  167. ret = UserGuest(cfg)
  168. }
  169. buildUserAcls(cfg, ret)
  170. log.WithFields(log.Fields{
  171. "username": ret.Username,
  172. "usergroupLine": ret.UsergroupLine,
  173. "provider": ret.Provider,
  174. "acls": ret.Acls,
  175. }).Debugf("UserFromUnauthenticatedUser")
  176. return ret
  177. }
  178. // UserFromContext tries to find a user from a grpc context
  179. func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser {
  180. var ret *AuthenticatedUser
  181. md, ok := metadata.FromIncomingContext(ctx)
  182. if ok {
  183. ret = &AuthenticatedUser{}
  184. ret.Username = getMetadataKeyOrEmpty(md, "username")
  185. ret.UsergroupLine = getMetadataKeyOrEmpty(md, "usergroup")
  186. ret.Provider = getMetadataKeyOrEmpty(md, "provider")
  187. buildUserAcls(cfg, ret)
  188. }
  189. if !ok || ret.Username == "" {
  190. ret = UserGuest(cfg)
  191. }
  192. log.WithFields(log.Fields{
  193. "username": ret.Username,
  194. "usergroupLine": ret.UsergroupLine,
  195. "provider": ret.Provider,
  196. "acls": ret.Acls,
  197. }).Debugf("UserFromContext")
  198. return ret
  199. }
  200. func UserGuest(cfg *config.Config) *AuthenticatedUser {
  201. ret := &AuthenticatedUser{}
  202. ret.Username = "guest"
  203. ret.UsergroupLine = "guest"
  204. ret.Provider = "system"
  205. buildUserAcls(cfg, ret)
  206. return ret
  207. }
  208. func UserFromSystem(cfg *config.Config, username string) *AuthenticatedUser {
  209. ret := &AuthenticatedUser{
  210. Username: username,
  211. UsergroupLine: "system",
  212. Provider: "system",
  213. }
  214. buildUserAcls(cfg, ret)
  215. return ret
  216. }
  217. func buildUserAcls(cfg *config.Config, user *AuthenticatedUser) {
  218. for _, acl := range cfg.AccessControlLists {
  219. if slices.Contains(acl.MatchUsernames, user.Username) {
  220. user.Acls = append(user.Acls, acl.Name)
  221. continue
  222. }
  223. if user.matchesUsergroupAcl(acl.MatchUsergroups, cfg.AuthHttpHeaderUserGroupSep) {
  224. user.Acls = append(user.Acls, acl.Name)
  225. continue
  226. }
  227. }
  228. user.EffectivePolicy = getEffectivePolicy(cfg, user)
  229. }
  230. func isACLRelevantToAction(cfg *config.Config, actionAcls []string, acl *config.AccessControlList, user *AuthenticatedUser) bool {
  231. if !slices.Contains(user.Acls, acl.Name) {
  232. // If the user does not have this ACL, then it is not relevant
  233. return false
  234. }
  235. if acl.AddToEveryAction {
  236. return true
  237. }
  238. if slices.Contains(actionAcls, acl.Name) {
  239. return true
  240. }
  241. return false
  242. }
  243. func getRelevantAcls(cfg *config.Config, actionAcls []string, user *AuthenticatedUser) []*config.AccessControlList {
  244. var ret []*config.AccessControlList
  245. for _, acl := range cfg.AccessControlLists {
  246. if isACLRelevantToAction(cfg, actionAcls, acl, user) {
  247. ret = append(ret, acl)
  248. }
  249. }
  250. return ret
  251. }
  252. func getEffectivePolicy(cfg *config.Config, user *AuthenticatedUser) *config.ConfigurationPolicy {
  253. ret := &config.ConfigurationPolicy{
  254. ShowDiagnostics: cfg.DefaultPolicy.ShowDiagnostics,
  255. ShowLogList: cfg.DefaultPolicy.ShowLogList,
  256. }
  257. for _, acl := range cfg.AccessControlLists {
  258. if slices.Contains(user.Acls, acl.Name) {
  259. logAclMatched(cfg, "GetEffectivePolicy", user, nil, acl)
  260. ret = buildConfigurationPolicy(ret, acl.Policy)
  261. }
  262. }
  263. return ret
  264. }
  265. func buildConfigurationPolicy(ret *config.ConfigurationPolicy, policy config.ConfigurationPolicy) *config.ConfigurationPolicy {
  266. if policy.ShowDiagnostics {
  267. ret.ShowDiagnostics = policy.ShowDiagnostics
  268. }
  269. if policy.ShowLogList {
  270. ret.ShowLogList = policy.ShowLogList
  271. }
  272. return ret
  273. }