| // Copyright Istio Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package model |
| |
| import ( |
| "fmt" |
| "strings" |
| ) |
| |
| import ( |
| rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" |
| authzpb "istio.io/api/security/v1beta1" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/pilot/pkg/security/trustdomain" |
| ) |
| |
| const ( |
| RBACTCPFilterStatPrefix = "tcp." |
| RBACShadowEngineResult = "shadow_engine_result" |
| RBACShadowEffectivePolicyID = "shadow_effective_policy_id" |
| RBACShadowRulesAllowStatPrefix = "istio_dry_run_allow_" |
| RBACShadowRulesDenyStatPrefix = "istio_dry_run_deny_" |
| RBACExtAuthzShadowRulesStatPrefix = "istio_ext_authz_" |
| |
| attrRequestHeader = "request.headers" // header name is surrounded by brackets, e.g. "request.headers[User-Agent]". |
| attrSrcIP = "source.ip" // supports both single ip and cidr, e.g. "10.1.2.3" or "10.1.0.0/16". |
| attrRemoteIP = "remote.ip" // original client ip determined from x-forwarded-for or proxy protocol. |
| attrSrcNamespace = "source.namespace" // e.g. "default". |
| attrSrcPrincipal = "source.principal" // source identity, e,g, "cluster.local/ns/default/sa/productpage". |
| attrRequestPrincipal = "request.auth.principal" // authenticated principal of the request. |
| attrRequestAudiences = "request.auth.audiences" // intended audience(s) for this authentication information. |
| attrRequestPresenter = "request.auth.presenter" // authorized presenter of the credential. |
| attrRequestClaims = "request.auth.claims" // claim name is surrounded by brackets, e.g. "request.auth.claims[iss]". |
| attrDestIP = "destination.ip" // supports both single ip and cidr, e.g. "10.1.2.3" or "10.1.0.0/16". |
| attrDestPort = "destination.port" // must be in the range [0, 65535]. |
| attrConnSNI = "connection.sni" // server name indication, e.g. "www.example.com". |
| attrEnvoyFilter = "experimental.envoy.filters." // an experimental attribute for checking Envoy Metadata directly. |
| |
| // Internal names used to generate corresponding Envoy matcher. |
| methodHeader = ":method" |
| pathMatcher = "path-matcher" |
| hostHeader = ":authority" |
| ) |
| |
| type rule struct { |
| key string |
| values []string |
| notValues []string |
| g generator |
| } |
| |
| type ruleList struct { |
| rules []*rule |
| } |
| |
| // Model represents a single rule from an authorization policy. The conditions of the rule are consolidated into |
| // permission or principal to align with the Envoy RBAC filter API. |
| type Model struct { |
| permissions []ruleList |
| principals []ruleList |
| } |
| |
| // New returns a model representing a single authorization policy. |
| func New(r *authzpb.Rule) (*Model, error) { |
| m := Model{} |
| |
| basePermission := ruleList{} |
| basePrincipal := ruleList{} |
| |
| // Each condition in the when needs to be consolidated into either permission or principal. |
| for _, when := range r.When { |
| k := when.Key |
| switch { |
| case k == attrDestIP: |
| basePermission.appendLast(destIPGenerator{}, k, when.Values, when.NotValues) |
| case k == attrDestPort: |
| basePermission.appendLast(destPortGenerator{}, k, when.Values, when.NotValues) |
| case k == attrConnSNI: |
| basePermission.appendLast(connSNIGenerator{}, k, when.Values, when.NotValues) |
| case strings.HasPrefix(k, attrEnvoyFilter): |
| basePermission.appendLast(envoyFilterGenerator{}, k, when.Values, when.NotValues) |
| case k == attrSrcIP: |
| basePrincipal.appendLast(srcIPGenerator{}, k, when.Values, when.NotValues) |
| case k == attrRemoteIP: |
| basePrincipal.appendLast(remoteIPGenerator{}, k, when.Values, when.NotValues) |
| case k == attrSrcNamespace: |
| basePrincipal.appendLast(srcNamespaceGenerator{}, k, when.Values, when.NotValues) |
| case k == attrSrcPrincipal: |
| basePrincipal.appendLast(srcPrincipalGenerator{}, k, when.Values, when.NotValues) |
| case k == attrRequestPrincipal: |
| basePrincipal.appendLast(requestPrincipalGenerator{}, k, when.Values, when.NotValues) |
| case k == attrRequestAudiences: |
| basePrincipal.appendLast(requestAudiencesGenerator{}, k, when.Values, when.NotValues) |
| case k == attrRequestPresenter: |
| basePrincipal.appendLast(requestPresenterGenerator{}, k, when.Values, when.NotValues) |
| case strings.HasPrefix(k, attrRequestHeader): |
| basePrincipal.appendLast(requestHeaderGenerator{}, k, when.Values, when.NotValues) |
| case strings.HasPrefix(k, attrRequestClaims): |
| basePrincipal.appendLast(requestClaimGenerator{}, k, when.Values, when.NotValues) |
| default: |
| return nil, fmt.Errorf("unknown attribute %s", when.Key) |
| } |
| } |
| |
| for _, from := range r.From { |
| merged := basePrincipal.copy() |
| if s := from.Source; s != nil { |
| merged.insertFront(srcIPGenerator{}, attrSrcIP, s.IpBlocks, s.NotIpBlocks) |
| merged.insertFront(remoteIPGenerator{}, attrRemoteIP, s.RemoteIpBlocks, s.NotRemoteIpBlocks) |
| merged.insertFront(srcNamespaceGenerator{}, attrSrcNamespace, s.Namespaces, s.NotNamespaces) |
| merged.insertFront(requestPrincipalGenerator{}, attrRequestPrincipal, s.RequestPrincipals, s.NotRequestPrincipals) |
| merged.insertFront(srcPrincipalGenerator{}, attrSrcPrincipal, s.Principals, s.NotPrincipals) |
| } |
| m.principals = append(m.principals, merged) |
| } |
| if len(r.From) == 0 { |
| m.principals = append(m.principals, basePrincipal) |
| } |
| |
| for _, to := range r.To { |
| merged := basePermission.copy() |
| if o := to.Operation; o != nil { |
| merged.insertFront(destPortGenerator{}, attrDestPort, o.Ports, o.NotPorts) |
| merged.insertFront(pathGenerator{}, pathMatcher, o.Paths, o.NotPaths) |
| merged.insertFront(methodGenerator{}, methodHeader, o.Methods, o.NotMethods) |
| merged.insertFront(hostGenerator{}, hostHeader, o.Hosts, o.NotHosts) |
| } |
| m.permissions = append(m.permissions, merged) |
| } |
| if len(r.To) == 0 { |
| m.permissions = append(m.permissions, basePermission) |
| } |
| |
| return &m, nil |
| } |
| |
| // MigrateTrustDomain replaces the trust domain in source principal based on the trust domain aliases information. |
| func (m *Model) MigrateTrustDomain(tdBundle trustdomain.Bundle) { |
| for _, p := range m.principals { |
| for _, r := range p.rules { |
| if r.key == attrSrcPrincipal { |
| if len(r.values) != 0 { |
| r.values = tdBundle.ReplaceTrustDomainAliases(r.values) |
| } |
| if len(r.notValues) != 0 { |
| r.notValues = tdBundle.ReplaceTrustDomainAliases(r.notValues) |
| } |
| } |
| } |
| } |
| } |
| |
| // Generate generates the Envoy RBAC config from the model. |
| func (m Model) Generate(forTCP bool, action rbacpb.RBAC_Action) (*rbacpb.Policy, error) { |
| var permissions []*rbacpb.Permission |
| for _, rl := range m.permissions { |
| permission, err := generatePermission(rl, forTCP, action) |
| if err != nil { |
| return nil, err |
| } |
| permissions = append(permissions, permission) |
| } |
| if len(permissions) == 0 { |
| return nil, fmt.Errorf("must have at least 1 permission") |
| } |
| |
| var principals []*rbacpb.Principal |
| for _, rl := range m.principals { |
| principal, err := generatePrincipal(rl, forTCP, action) |
| if err != nil { |
| return nil, err |
| } |
| principals = append(principals, principal) |
| } |
| if len(principals) == 0 { |
| return nil, fmt.Errorf("must have at least 1 principal") |
| } |
| |
| return &rbacpb.Policy{ |
| Permissions: permissions, |
| Principals: principals, |
| }, nil |
| } |
| |
| func generatePermission(rl ruleList, forTCP bool, action rbacpb.RBAC_Action) (*rbacpb.Permission, error) { |
| var and []*rbacpb.Permission |
| for _, r := range rl.rules { |
| ret, err := r.permission(forTCP, action) |
| if err != nil { |
| return nil, err |
| } |
| and = append(and, ret...) |
| } |
| if len(and) == 0 { |
| and = append(and, permissionAny()) |
| } |
| return permissionAnd(and), nil |
| } |
| |
| func generatePrincipal(rl ruleList, forTCP bool, action rbacpb.RBAC_Action) (*rbacpb.Principal, error) { |
| var and []*rbacpb.Principal |
| for _, r := range rl.rules { |
| ret, err := r.principal(forTCP, action) |
| if err != nil { |
| return nil, err |
| } |
| and = append(and, ret...) |
| } |
| if len(and) == 0 { |
| and = append(and, principalAny()) |
| } |
| return principalAnd(and), nil |
| } |
| |
| func (r rule) permission(forTCP bool, action rbacpb.RBAC_Action) ([]*rbacpb.Permission, error) { |
| var permissions []*rbacpb.Permission |
| var or []*rbacpb.Permission |
| for _, value := range r.values { |
| p, err := r.g.permission(r.key, value, forTCP) |
| if err := r.checkError(action, err); err != nil { |
| return nil, err |
| } |
| if p != nil { |
| or = append(or, p) |
| } |
| } |
| if len(or) > 0 { |
| permissions = append(permissions, permissionOr(or)) |
| } |
| |
| or = nil |
| for _, notValue := range r.notValues { |
| p, err := r.g.permission(r.key, notValue, forTCP) |
| if err := r.checkError(action, err); err != nil { |
| return nil, err |
| } |
| if p != nil { |
| or = append(or, p) |
| } |
| } |
| if len(or) > 0 { |
| permissions = append(permissions, permissionNot(permissionOr(or))) |
| } |
| return permissions, nil |
| } |
| |
| func (r rule) principal(forTCP bool, action rbacpb.RBAC_Action) ([]*rbacpb.Principal, error) { |
| var principals []*rbacpb.Principal |
| var or []*rbacpb.Principal |
| for _, value := range r.values { |
| p, err := r.g.principal(r.key, value, forTCP) |
| if err := r.checkError(action, err); err != nil { |
| return nil, err |
| } |
| if p != nil { |
| or = append(or, p) |
| } |
| } |
| if len(or) > 0 { |
| principals = append(principals, principalOr(or)) |
| } |
| |
| or = nil |
| for _, notValue := range r.notValues { |
| p, err := r.g.principal(r.key, notValue, forTCP) |
| if err := r.checkError(action, err); err != nil { |
| return nil, err |
| } |
| if p != nil { |
| or = append(or, p) |
| } |
| } |
| if len(or) > 0 { |
| principals = append(principals, principalNot(principalOr(or))) |
| } |
| return principals, nil |
| } |
| |
| func (r rule) checkError(action rbacpb.RBAC_Action, err error) error { |
| if action == rbacpb.RBAC_ALLOW { |
| // Return the error as-is for allow policy. This will make all rules in the current permission ignored, effectively |
| // result in a smaller allow policy (i.e. less likely to allow a request). |
| return err |
| } |
| |
| // Ignore the error for a deny or audit policy. This will make the current rule ignored and continue the generation of |
| // the next rule, effectively resulting in a wider deny or audit policy (i.e. more likely to deny or audit a request). |
| return nil |
| } |
| |
| func (p *ruleList) copy() ruleList { |
| r := ruleList{} |
| r.rules = append([]*rule{}, p.rules...) |
| return r |
| } |
| |
| func (p *ruleList) insertFront(g generator, key string, values, notValues []string) { |
| if len(values) == 0 && len(notValues) == 0 { |
| return |
| } |
| r := &rule{ |
| key: key, |
| values: values, |
| notValues: notValues, |
| g: g, |
| } |
| |
| p.rules = append([]*rule{r}, p.rules...) |
| } |
| |
| func (p *ruleList) appendLast(g generator, key string, values, notValues []string) { |
| if len(values) == 0 && len(notValues) == 0 { |
| return |
| } |
| r := &rule{ |
| key: key, |
| values: values, |
| notValues: notValues, |
| g: g, |
| } |
| |
| p.rules = append(p.rules, r) |
| } |