istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/authz/listener.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package authz
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"regexp"
    21  	"sort"
    22  	"strings"
    23  	"text/tabwriter"
    24  
    25  	listener "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    26  	rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
    27  	rbachttp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3"
    28  	hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
    29  	rbactcp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/rbac/v3"
    30  	"google.golang.org/protobuf/proto"
    31  
    32  	"istio.io/istio/pkg/log"
    33  	"istio.io/istio/pkg/wellknown"
    34  )
    35  
    36  const (
    37  	anonymousName = "_anonymous_match_nothing_"
    38  )
    39  
    40  // Matches the policy name in RBAC filter config with format like ns[default]-policy[some-policy]-rule[1].
    41  var re = regexp.MustCompile(`ns\[(.+)\]-policy\[(.+)\]-rule\[(.+)\]`)
    42  
    43  type filterChain struct {
    44  	rbacHTTP []*rbachttp.RBAC
    45  	rbacTCP  []*rbactcp.RBAC
    46  }
    47  
    48  type parsedListener struct {
    49  	filterChains []*filterChain
    50  }
    51  
    52  func getFilterConfig(filter *listener.Filter, out proto.Message) error {
    53  	switch c := filter.ConfigType.(type) {
    54  	case *listener.Filter_TypedConfig:
    55  		if err := c.TypedConfig.UnmarshalTo(out); err != nil {
    56  			return err
    57  		}
    58  	}
    59  	return nil
    60  }
    61  
    62  func getHTTPConnectionManager(filter *listener.Filter) *hcm.HttpConnectionManager {
    63  	cm := &hcm.HttpConnectionManager{}
    64  	if err := getFilterConfig(filter, cm); err != nil {
    65  		log.Errorf("failed to get HTTP connection manager config: %s", err)
    66  		return nil
    67  	}
    68  	return cm
    69  }
    70  
    71  func getHTTPFilterConfig(filter *hcm.HttpFilter, out proto.Message) error {
    72  	switch c := filter.ConfigType.(type) {
    73  	case *hcm.HttpFilter_TypedConfig:
    74  		if err := c.TypedConfig.UnmarshalTo(out); err != nil {
    75  			return err
    76  		}
    77  	}
    78  	return nil
    79  }
    80  
    81  func parse(listeners []*listener.Listener) []*parsedListener {
    82  	var parsedListeners []*parsedListener
    83  	for _, l := range listeners {
    84  		parsed := &parsedListener{}
    85  		for _, fc := range l.FilterChains {
    86  			parsedFC := &filterChain{}
    87  			for _, filter := range fc.Filters {
    88  				switch filter.Name {
    89  				case wellknown.HTTPConnectionManager, "envoy.http_connection_manager":
    90  					if cm := getHTTPConnectionManager(filter); cm != nil {
    91  						for _, httpFilter := range cm.GetHttpFilters() {
    92  							switch httpFilter.GetName() {
    93  							case wellknown.HTTPRoleBasedAccessControl:
    94  								rbacHTTP := &rbachttp.RBAC{}
    95  								if err := getHTTPFilterConfig(httpFilter, rbacHTTP); err != nil {
    96  									log.Errorf("found RBAC HTTP filter but failed to parse: %s", err)
    97  								} else {
    98  									parsedFC.rbacHTTP = append(parsedFC.rbacHTTP, rbacHTTP)
    99  								}
   100  							}
   101  						}
   102  					}
   103  				case wellknown.RoleBasedAccessControl:
   104  					rbacTCP := &rbactcp.RBAC{}
   105  					if err := getFilterConfig(filter, rbacTCP); err != nil {
   106  						log.Errorf("found RBAC network filter but failed to parse: %s", err)
   107  					} else {
   108  						parsedFC.rbacTCP = append(parsedFC.rbacTCP, rbacTCP)
   109  					}
   110  				}
   111  			}
   112  
   113  			parsed.filterChains = append(parsed.filterChains, parsedFC)
   114  		}
   115  		parsedListeners = append(parsedListeners, parsed)
   116  	}
   117  	return parsedListeners
   118  }
   119  
   120  func extractName(name string) (string, string) {
   121  	// parts[1] is the namespace, parts[2] is the policy name, parts[3] is the rule index.
   122  	parts := re.FindStringSubmatch(name)
   123  	if len(parts) != 4 {
   124  		log.Errorf("failed to parse policy name: %s", name)
   125  		return "", ""
   126  	}
   127  	return fmt.Sprintf("%s.%s", parts[2], parts[1]), parts[3]
   128  }
   129  
   130  // Print prints the AuthorizationPolicy in the listener.
   131  func Print(writer io.Writer, listeners []*listener.Listener) {
   132  	parsedListeners := parse(listeners)
   133  	if parsedListeners == nil {
   134  		return
   135  	}
   136  
   137  	actionToPolicy := map[rbacpb.RBAC_Action]map[string]struct{}{}
   138  	policyToRule := map[string]map[string]struct{}{}
   139  
   140  	addPolicy := func(action rbacpb.RBAC_Action, name string, rule string) {
   141  		if actionToPolicy[action] == nil {
   142  			actionToPolicy[action] = map[string]struct{}{}
   143  		}
   144  		if policyToRule[name] == nil {
   145  			policyToRule[name] = map[string]struct{}{}
   146  		}
   147  		actionToPolicy[action][name] = struct{}{}
   148  		policyToRule[name][rule] = struct{}{}
   149  	}
   150  
   151  	for _, parsed := range parsedListeners {
   152  		for _, fc := range parsed.filterChains {
   153  			for _, rbacHTTP := range fc.rbacHTTP {
   154  				action := rbacHTTP.GetRules().GetAction()
   155  				for name := range rbacHTTP.GetRules().GetPolicies() {
   156  					nameOfPolicy, indexOfRule := extractName(name)
   157  					addPolicy(action, nameOfPolicy, indexOfRule)
   158  				}
   159  				if len(rbacHTTP.GetRules().GetPolicies()) == 0 {
   160  					addPolicy(action, anonymousName, "0")
   161  				}
   162  			}
   163  			for _, rbacTCP := range fc.rbacTCP {
   164  				action := rbacTCP.GetRules().GetAction()
   165  				for name := range rbacTCP.GetRules().GetPolicies() {
   166  					nameOfPolicy, indexOfRule := extractName(name)
   167  					addPolicy(action, nameOfPolicy, indexOfRule)
   168  				}
   169  				if len(rbacTCP.GetRules().GetPolicies()) == 0 {
   170  					addPolicy(action, anonymousName, "0")
   171  				}
   172  			}
   173  		}
   174  	}
   175  
   176  	buf := strings.Builder{}
   177  	buf.WriteString("ACTION\tAuthorizationPolicy\tRULES\n")
   178  	for _, action := range []rbacpb.RBAC_Action{rbacpb.RBAC_DENY, rbacpb.RBAC_ALLOW, rbacpb.RBAC_LOG} {
   179  		if names, ok := actionToPolicy[action]; ok {
   180  			sortedNames := make([]string, 0, len(names))
   181  			for name := range names {
   182  				sortedNames = append(sortedNames, name)
   183  			}
   184  			sort.Strings(sortedNames)
   185  			for _, name := range sortedNames {
   186  				buf.WriteString(fmt.Sprintf("%s\t%s\t%d\n", action, name, len(policyToRule[name])))
   187  			}
   188  		}
   189  	}
   190  
   191  	w := new(tabwriter.Writer).Init(writer, 0, 8, 3, ' ', 0)
   192  	if _, err := fmt.Fprint(w, buf.String()); err != nil {
   193  		log.Errorf("failed to print output: %s", err)
   194  	}
   195  	_ = w.Flush()
   196  }