rbac_translator.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. /*
  2. * Copyright 2021 gRPC authors.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. // Package authz exposes methods to manage authorization within gRPC.
  17. //
  18. // # Experimental
  19. //
  20. // Notice: This package is EXPERIMENTAL and may be changed or removed
  21. // in a later release.
  22. package authz
  23. import (
  24. "bytes"
  25. "encoding/json"
  26. "fmt"
  27. "strings"
  28. v1xdsudpatypepb "github.com/cncf/xds/go/udpa/type/v1"
  29. v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
  30. v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
  31. v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
  32. v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
  33. "google.golang.org/protobuf/types/known/anypb"
  34. "google.golang.org/protobuf/types/known/structpb"
  35. )
  36. // This is used when converting a custom config from raw JSON to a TypedStruct
  37. // The TypeURL of the TypeStruct will be "grpc.authz.audit_logging/<name>"
  38. const typeURLPrefix = "grpc.authz.audit_logging/"
  39. type header struct {
  40. Key string
  41. Values []string
  42. }
  43. type peer struct {
  44. Principals []string
  45. }
  46. type request struct {
  47. Paths []string
  48. Headers []header
  49. }
  50. type rule struct {
  51. Name string
  52. Source peer
  53. Request request
  54. }
  55. type auditLogger struct {
  56. Name string `json:"name"`
  57. Config *structpb.Struct `json:"config"`
  58. IsOptional bool `json:"is_optional"`
  59. }
  60. type auditLoggingOptions struct {
  61. AuditCondition string `json:"audit_condition"`
  62. AuditLoggers []*auditLogger `json:"audit_loggers"`
  63. }
  64. // Represents the SDK authorization policy provided by user.
  65. type authorizationPolicy struct {
  66. Name string
  67. DenyRules []rule `json:"deny_rules"`
  68. AllowRules []rule `json:"allow_rules"`
  69. AuditLoggingOptions auditLoggingOptions `json:"audit_logging_options"`
  70. }
  71. func principalOr(principals []*v3rbacpb.Principal) *v3rbacpb.Principal {
  72. return &v3rbacpb.Principal{
  73. Identifier: &v3rbacpb.Principal_OrIds{
  74. OrIds: &v3rbacpb.Principal_Set{
  75. Ids: principals,
  76. },
  77. },
  78. }
  79. }
  80. func permissionOr(permission []*v3rbacpb.Permission) *v3rbacpb.Permission {
  81. return &v3rbacpb.Permission{
  82. Rule: &v3rbacpb.Permission_OrRules{
  83. OrRules: &v3rbacpb.Permission_Set{
  84. Rules: permission,
  85. },
  86. },
  87. }
  88. }
  89. func permissionAnd(permission []*v3rbacpb.Permission) *v3rbacpb.Permission {
  90. return &v3rbacpb.Permission{
  91. Rule: &v3rbacpb.Permission_AndRules{
  92. AndRules: &v3rbacpb.Permission_Set{
  93. Rules: permission,
  94. },
  95. },
  96. }
  97. }
  98. func getStringMatcher(value string) *v3matcherpb.StringMatcher {
  99. switch {
  100. case value == "*":
  101. return &v3matcherpb.StringMatcher{
  102. MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{
  103. SafeRegex: &v3matcherpb.RegexMatcher{Regex: ".+"}},
  104. }
  105. case strings.HasSuffix(value, "*"):
  106. prefix := strings.TrimSuffix(value, "*")
  107. return &v3matcherpb.StringMatcher{
  108. MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: prefix},
  109. }
  110. case strings.HasPrefix(value, "*"):
  111. suffix := strings.TrimPrefix(value, "*")
  112. return &v3matcherpb.StringMatcher{
  113. MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: suffix},
  114. }
  115. default:
  116. return &v3matcherpb.StringMatcher{
  117. MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: value},
  118. }
  119. }
  120. }
  121. func getHeaderMatcher(key, value string) *v3routepb.HeaderMatcher {
  122. switch {
  123. case value == "*":
  124. return &v3routepb.HeaderMatcher{
  125. Name: key,
  126. HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SafeRegexMatch{
  127. SafeRegexMatch: &v3matcherpb.RegexMatcher{Regex: ".+"}},
  128. }
  129. case strings.HasSuffix(value, "*"):
  130. prefix := strings.TrimSuffix(value, "*")
  131. return &v3routepb.HeaderMatcher{
  132. Name: key,
  133. HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{PrefixMatch: prefix},
  134. }
  135. case strings.HasPrefix(value, "*"):
  136. suffix := strings.TrimPrefix(value, "*")
  137. return &v3routepb.HeaderMatcher{
  138. Name: key,
  139. HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SuffixMatch{SuffixMatch: suffix},
  140. }
  141. default:
  142. return &v3routepb.HeaderMatcher{
  143. Name: key,
  144. HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: value},
  145. }
  146. }
  147. }
  148. func parsePrincipalNames(principalNames []string) []*v3rbacpb.Principal {
  149. ps := make([]*v3rbacpb.Principal, 0, len(principalNames))
  150. for _, principalName := range principalNames {
  151. newPrincipalName := &v3rbacpb.Principal{
  152. Identifier: &v3rbacpb.Principal_Authenticated_{
  153. Authenticated: &v3rbacpb.Principal_Authenticated{
  154. PrincipalName: getStringMatcher(principalName),
  155. },
  156. }}
  157. ps = append(ps, newPrincipalName)
  158. }
  159. return ps
  160. }
  161. func parsePeer(source peer) *v3rbacpb.Principal {
  162. if len(source.Principals) == 0 {
  163. return &v3rbacpb.Principal{
  164. Identifier: &v3rbacpb.Principal_Any{
  165. Any: true,
  166. },
  167. }
  168. }
  169. return principalOr(parsePrincipalNames(source.Principals))
  170. }
  171. func parsePaths(paths []string) []*v3rbacpb.Permission {
  172. ps := make([]*v3rbacpb.Permission, 0, len(paths))
  173. for _, path := range paths {
  174. newPath := &v3rbacpb.Permission{
  175. Rule: &v3rbacpb.Permission_UrlPath{
  176. UrlPath: &v3matcherpb.PathMatcher{
  177. Rule: &v3matcherpb.PathMatcher_Path{Path: getStringMatcher(path)}}}}
  178. ps = append(ps, newPath)
  179. }
  180. return ps
  181. }
  182. func parseHeaderValues(key string, values []string) []*v3rbacpb.Permission {
  183. vs := make([]*v3rbacpb.Permission, 0, len(values))
  184. for _, value := range values {
  185. newHeader := &v3rbacpb.Permission{
  186. Rule: &v3rbacpb.Permission_Header{
  187. Header: getHeaderMatcher(key, value)}}
  188. vs = append(vs, newHeader)
  189. }
  190. return vs
  191. }
  192. var unsupportedHeaders = map[string]bool{
  193. "host": true,
  194. "connection": true,
  195. "keep-alive": true,
  196. "proxy-authenticate": true,
  197. "proxy-authorization": true,
  198. "te": true,
  199. "trailer": true,
  200. "transfer-encoding": true,
  201. "upgrade": true,
  202. }
  203. func unsupportedHeader(key string) bool {
  204. return key[0] == ':' || strings.HasPrefix(key, "grpc-") || unsupportedHeaders[key]
  205. }
  206. func parseHeaders(headers []header) ([]*v3rbacpb.Permission, error) {
  207. hs := make([]*v3rbacpb.Permission, 0, len(headers))
  208. for i, header := range headers {
  209. if header.Key == "" {
  210. return nil, fmt.Errorf(`"headers" %d: "key" is not present`, i)
  211. }
  212. header.Key = strings.ToLower(header.Key)
  213. if unsupportedHeader(header.Key) {
  214. return nil, fmt.Errorf(`"headers" %d: unsupported "key" %s`, i, header.Key)
  215. }
  216. if len(header.Values) == 0 {
  217. return nil, fmt.Errorf(`"headers" %d: "values" is not present`, i)
  218. }
  219. values := parseHeaderValues(header.Key, header.Values)
  220. hs = append(hs, permissionOr(values))
  221. }
  222. return hs, nil
  223. }
  224. func parseRequest(request request) (*v3rbacpb.Permission, error) {
  225. var and []*v3rbacpb.Permission
  226. if len(request.Paths) > 0 {
  227. and = append(and, permissionOr(parsePaths(request.Paths)))
  228. }
  229. if len(request.Headers) > 0 {
  230. headers, err := parseHeaders(request.Headers)
  231. if err != nil {
  232. return nil, err
  233. }
  234. and = append(and, permissionAnd(headers))
  235. }
  236. if len(and) > 0 {
  237. return permissionAnd(and), nil
  238. }
  239. return &v3rbacpb.Permission{
  240. Rule: &v3rbacpb.Permission_Any{
  241. Any: true,
  242. },
  243. }, nil
  244. }
  245. func parseRules(rules []rule, prefixName string) (map[string]*v3rbacpb.Policy, error) {
  246. policies := make(map[string]*v3rbacpb.Policy)
  247. for i, rule := range rules {
  248. if rule.Name == "" {
  249. return policies, fmt.Errorf(`%d: "name" is not present`, i)
  250. }
  251. permission, err := parseRequest(rule.Request)
  252. if err != nil {
  253. return nil, fmt.Errorf("%d: %v", i, err)
  254. }
  255. policyName := prefixName + "_" + rule.Name
  256. policies[policyName] = &v3rbacpb.Policy{
  257. Principals: []*v3rbacpb.Principal{parsePeer(rule.Source)},
  258. Permissions: []*v3rbacpb.Permission{permission},
  259. }
  260. }
  261. return policies, nil
  262. }
  263. // Parse auditLoggingOptions to the associated RBAC protos. The single
  264. // auditLoggingOptions results in two different parsed protos, one for the allow
  265. // policy and one for the deny policy
  266. func (options *auditLoggingOptions) toProtos() (allow *v3rbacpb.RBAC_AuditLoggingOptions, deny *v3rbacpb.RBAC_AuditLoggingOptions, err error) {
  267. allow = &v3rbacpb.RBAC_AuditLoggingOptions{}
  268. deny = &v3rbacpb.RBAC_AuditLoggingOptions{}
  269. if options.AuditCondition != "" {
  270. rbacCondition, ok := v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition_value[options.AuditCondition]
  271. if !ok {
  272. return nil, nil, fmt.Errorf("failed to parse AuditCondition %v. Allowed values {NONE, ON_DENY, ON_ALLOW, ON_DENY_AND_ALLOW}", options.AuditCondition)
  273. }
  274. allow.AuditCondition = v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition(rbacCondition)
  275. deny.AuditCondition = toDenyCondition(v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition(rbacCondition))
  276. }
  277. for i, config := range options.AuditLoggers {
  278. if config.Name == "" {
  279. return nil, nil, fmt.Errorf("missing required field: name in audit_logging_options.audit_loggers[%v]", i)
  280. }
  281. if config.Config == nil {
  282. config.Config = &structpb.Struct{}
  283. }
  284. typedStruct := &v1xdsudpatypepb.TypedStruct{
  285. TypeUrl: typeURLPrefix + config.Name,
  286. Value: config.Config,
  287. }
  288. customConfig, err := anypb.New(typedStruct)
  289. if err != nil {
  290. return nil, nil, fmt.Errorf("error parsing custom audit logger config: %v", err)
  291. }
  292. logger := &v3corepb.TypedExtensionConfig{Name: config.Name, TypedConfig: customConfig}
  293. rbacConfig := v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{
  294. IsOptional: config.IsOptional,
  295. AuditLogger: logger,
  296. }
  297. allow.LoggerConfigs = append(allow.LoggerConfigs, &rbacConfig)
  298. deny.LoggerConfigs = append(deny.LoggerConfigs, &rbacConfig)
  299. }
  300. return allow, deny, nil
  301. }
  302. // Maps the AuditCondition coming from AuditLoggingOptions to the proper
  303. // condition for the deny policy RBAC proto
  304. func toDenyCondition(condition v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition) v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition {
  305. // Mapping the overall policy AuditCondition to what it must be for the Deny and Allow RBAC
  306. // See gRPC A59 for details - https://github.com/grpc/proposal/pull/346/files
  307. // |Authorization Policy |DENY RBAC |ALLOW RBAC |
  308. // |----------------------|-------------------|---------------------|
  309. // |NONE |NONE |NONE |
  310. // |ON_DENY |ON_DENY |ON_DENY |
  311. // |ON_ALLOW |NONE |ON_ALLOW |
  312. // |ON_DENY_AND_ALLOW |ON_DENY |ON_DENY_AND_ALLOW |
  313. switch condition {
  314. case v3rbacpb.RBAC_AuditLoggingOptions_NONE:
  315. return v3rbacpb.RBAC_AuditLoggingOptions_NONE
  316. case v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY:
  317. return v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY
  318. case v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW:
  319. return v3rbacpb.RBAC_AuditLoggingOptions_NONE
  320. case v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY_AND_ALLOW:
  321. return v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY
  322. default:
  323. return v3rbacpb.RBAC_AuditLoggingOptions_NONE
  324. }
  325. }
  326. // translatePolicy translates SDK authorization policy in JSON format to two
  327. // Envoy RBAC polices (deny followed by allow policy) or only one Envoy RBAC
  328. // allow policy. Also returns the overall policy name. If the input policy
  329. // cannot be parsed or is invalid, an error will be returned.
  330. func translatePolicy(policyStr string) ([]*v3rbacpb.RBAC, string, error) {
  331. policy := &authorizationPolicy{}
  332. d := json.NewDecoder(bytes.NewReader([]byte(policyStr)))
  333. d.DisallowUnknownFields()
  334. if err := d.Decode(policy); err != nil {
  335. return nil, "", fmt.Errorf("failed to unmarshal policy: %v", err)
  336. }
  337. if policy.Name == "" {
  338. return nil, "", fmt.Errorf(`"name" is not present`)
  339. }
  340. if len(policy.AllowRules) == 0 {
  341. return nil, "", fmt.Errorf(`"allow_rules" is not present`)
  342. }
  343. allowLogger, denyLogger, err := policy.AuditLoggingOptions.toProtos()
  344. if err != nil {
  345. return nil, "", err
  346. }
  347. rbacs := make([]*v3rbacpb.RBAC, 0, 2)
  348. if len(policy.DenyRules) > 0 {
  349. denyPolicies, err := parseRules(policy.DenyRules, policy.Name)
  350. if err != nil {
  351. return nil, "", fmt.Errorf(`"deny_rules" %v`, err)
  352. }
  353. denyRBAC := &v3rbacpb.RBAC{
  354. Action: v3rbacpb.RBAC_DENY,
  355. Policies: denyPolicies,
  356. AuditLoggingOptions: denyLogger,
  357. }
  358. rbacs = append(rbacs, denyRBAC)
  359. }
  360. allowPolicies, err := parseRules(policy.AllowRules, policy.Name)
  361. if err != nil {
  362. return nil, "", fmt.Errorf(`"allow_rules" %v`, err)
  363. }
  364. allowRBAC := &v3rbacpb.RBAC{Action: v3rbacpb.RBAC_ALLOW, Policies: allowPolicies, AuditLoggingOptions: allowLogger}
  365. return append(rbacs, allowRBAC), policy.Name, nil
  366. }