github.phpd.cn/cilium/cilium@v1.6.12/pkg/labels/filter.go (about)

     1  // Copyright 2016-2017 Authors of Cilium
     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 labels
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"os"
    21  	"regexp"
    22  	"strings"
    23  
    24  	k8sConst "github.com/cilium/cilium/pkg/k8s/apis/cilium.io"
    25  	"github.com/cilium/cilium/pkg/lock"
    26  	"github.com/cilium/cilium/pkg/logging"
    27  	"github.com/cilium/cilium/pkg/logging/logfields"
    28  )
    29  
    30  var (
    31  	log                  = logging.DefaultLogger.WithField(logfields.LogSubsys, "labels-filter")
    32  	validLabelPrefixesMU lock.RWMutex
    33  	validLabelPrefixes   *labelPrefixCfg // Label prefixes used to filter from all labels
    34  )
    35  
    36  const (
    37  	// LPCfgFileVersion represents the version of a Label Prefix Configuration File
    38  	LPCfgFileVersion = 1
    39  )
    40  
    41  // LabelPrefix is the cilium's representation of a container label.
    42  // +k8s:deepcopy-gen=false
    43  // +k8s:openapi-gen=false
    44  type LabelPrefix struct {
    45  	// Ignore if true will cause this prefix to be ignored insted of being accepted
    46  	Ignore bool   `json:"invert"`
    47  	Prefix string `json:"prefix"`
    48  	Source string `json:"source"`
    49  	expr   *regexp.Regexp
    50  }
    51  
    52  // String returns a human readable representation of the LabelPrefix
    53  func (p LabelPrefix) String() string {
    54  	s := fmt.Sprintf("%s:%s", p.Source, p.Prefix)
    55  	if p.Ignore {
    56  		s = "!" + s
    57  	}
    58  
    59  	return s
    60  }
    61  
    62  // matches returns true and the length of the matched section if the label is
    63  // matched by the LabelPrefix. The Ignore flag has no effect at this point.
    64  func (p LabelPrefix) matches(l Label) (bool, int) {
    65  	if p.Source != "" && p.Source != l.Source {
    66  		return false, 0
    67  	}
    68  
    69  	// If no regular expression is available, fall back to prefix matching
    70  	if p.expr == nil {
    71  		return strings.HasPrefix(l.Key, p.Prefix), len(p.Prefix)
    72  	}
    73  
    74  	res := p.expr.FindStringIndex(l.Key)
    75  
    76  	// No match if regexp was not found
    77  	if res == nil {
    78  		return false, 0
    79  	}
    80  
    81  	// Otherwise match if match was found at start of key
    82  	return res[0] == 0, res[1]
    83  }
    84  
    85  // parseLabelPrefix returns a LabelPrefix created from the string label parameter.
    86  func parseLabelPrefix(label string) (*LabelPrefix, error) {
    87  	labelPrefix := LabelPrefix{}
    88  	i := strings.IndexByte(label, ':')
    89  	if i >= 0 {
    90  		labelPrefix.Source = label[:i]
    91  		labelPrefix.Prefix = label[i+1:]
    92  	} else {
    93  		labelPrefix.Prefix = label
    94  	}
    95  
    96  	if labelPrefix.Prefix[0] == '!' {
    97  		labelPrefix.Ignore = true
    98  		labelPrefix.Prefix = labelPrefix.Prefix[1:]
    99  	}
   100  
   101  	r, err := regexp.Compile(labelPrefix.Prefix)
   102  	if err != nil {
   103  		return nil, fmt.Errorf("unable to compile regexp: %s", err)
   104  	}
   105  	labelPrefix.expr = r
   106  
   107  	return &labelPrefix, nil
   108  }
   109  
   110  // ParseLabelPrefixCfg parses valid label prefixes from a file and from a slice
   111  // of valid prefixes. Both are optional. If both are provided, both list are
   112  // appended together.
   113  func ParseLabelPrefixCfg(prefixes []string, file string) error {
   114  	cfg, err := readLabelPrefixCfgFrom(file)
   115  	if err != nil {
   116  		return fmt.Errorf("unable to read label prefix file: %s", err)
   117  	}
   118  
   119  	for _, label := range prefixes {
   120  		p, err := parseLabelPrefix(label)
   121  		if err != nil {
   122  			return err
   123  		}
   124  
   125  		if !p.Ignore {
   126  			cfg.whitelist = true
   127  		}
   128  
   129  		cfg.LabelPrefixes = append(cfg.LabelPrefixes, p)
   130  	}
   131  
   132  	validLabelPrefixes = cfg
   133  
   134  	log.Info("Valid label prefix configuration:")
   135  	for _, l := range validLabelPrefixes.LabelPrefixes {
   136  		log.Infof(" - %s", l)
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  // labelPrefixCfg is the label prefix configuration to filter labels of started
   143  // containers.
   144  // +k8s:openapi-gen=false
   145  type labelPrefixCfg struct {
   146  	Version       int            `json:"version"`
   147  	LabelPrefixes []*LabelPrefix `json:"valid-prefixes"`
   148  	// whitelist if true, indicates that an inclusive rule has to match
   149  	// in order for the label to be considered
   150  	whitelist bool
   151  }
   152  
   153  // defaultLabelPrefixCfg returns a default LabelPrefixCfg using the latest
   154  // LPCfgFileVersion
   155  func defaultLabelPrefixCfg() *labelPrefixCfg {
   156  	cfg := &labelPrefixCfg{
   157  		Version:       LPCfgFileVersion,
   158  		LabelPrefixes: []*LabelPrefix{},
   159  	}
   160  
   161  	expressions := []string{
   162  		k8sConst.PodNamespaceLabel,      // include io.kubernetes.pod.namespace
   163  		k8sConst.PodNamespaceMetaLabels, // include all namespace labels
   164  		k8sConst.AppKubernetes,          // include app.kubernetes.io
   165  		"!io.kubernetes",                // ignore all other io.kubernetes labels
   166  		"!kubernetes.io",                // ignore all other kubernetes.io labels
   167  		"!.*beta.kubernetes.io",         // ignore all beta.kubernetes.io labels
   168  		"!k8s.io",                       // ignore all k8s.io labels
   169  		"!pod-template-generation",      // ignore pod-template-generation
   170  		"!pod-template-hash",            // ignore pod-template-hash
   171  		"!controller-revision-hash",     // ignore controller-revision-hash
   172  		"!annotation.*",                 // ignore all annotation labels
   173  		"!etcd_node",                    // ignore etcd_node label
   174  	}
   175  
   176  	for _, e := range expressions {
   177  		p, err := parseLabelPrefix(e)
   178  		if err != nil {
   179  			msg := fmt.Sprintf("BUG: Unable to parse default label prefix '%s': %s", e, err)
   180  			panic(msg)
   181  		}
   182  		cfg.LabelPrefixes = append(cfg.LabelPrefixes, p)
   183  	}
   184  
   185  	return cfg
   186  }
   187  
   188  // readLabelPrefixCfgFrom reads a label prefix configuration file from fileName. If the
   189  // version is not supported by us it returns an error.
   190  func readLabelPrefixCfgFrom(fileName string) (*labelPrefixCfg, error) {
   191  	// if not file is specified, the default is empty
   192  	if fileName == "" {
   193  		return defaultLabelPrefixCfg(), nil
   194  	}
   195  
   196  	f, err := os.Open(fileName)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	defer f.Close()
   201  	lpc := labelPrefixCfg{}
   202  	err = json.NewDecoder(f).Decode(&lpc)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  	if lpc.Version != LPCfgFileVersion {
   207  		return nil, fmt.Errorf("unsupported version %d", lpc.Version)
   208  	}
   209  	for _, lp := range lpc.LabelPrefixes {
   210  		if lp.Prefix == "" {
   211  			return nil, fmt.Errorf("invalid label prefix file: prefix was empty")
   212  		}
   213  		if lp.Source == "" {
   214  			return nil, fmt.Errorf("invalid label prefix file: source was empty")
   215  		}
   216  		if !lp.Ignore {
   217  			lpc.whitelist = true
   218  		}
   219  	}
   220  	return &lpc, nil
   221  }
   222  
   223  func (cfg *labelPrefixCfg) filterLabels(lbls Labels) (identityLabels, informationLabels Labels) {
   224  	if lbls == nil {
   225  		return nil, nil
   226  	}
   227  
   228  	validLabelPrefixesMU.RLock()
   229  	defer validLabelPrefixesMU.RUnlock()
   230  
   231  	identityLabels = Labels{}
   232  	informationLabels = Labels{}
   233  	for k, v := range lbls {
   234  		included, ignored := 0, 0
   235  
   236  		for _, p := range cfg.LabelPrefixes {
   237  			if m, len := p.matches(v); m {
   238  				if p.Ignore {
   239  					// save length of shortest matching ignore
   240  					if ignored == 0 || len < ignored {
   241  						ignored = len
   242  					}
   243  				} else {
   244  					// save length of longest matching include
   245  					if len > included {
   246  						included = len
   247  					}
   248  				}
   249  			}
   250  		}
   251  
   252  		// A label is accepted if :
   253  		// - No inclusive LabelPrefix (Ignore flag not set) is
   254  		//   configured and label is not ignored.
   255  		// - An inclusive LabelPrefix matches the label
   256  		// - If both an inclusive and ignore LabelPrefix match, the
   257  		//   label is accepted if the matching section in the label
   258  		//   is greater than the ignored matching section in label,
   259  		//   e.g. when evaluating the label foo.bar, the prefix rules
   260  		//   {!foo, foo.bar} will cause the label to be accepted
   261  		//   because the inclusive prefix matches over a longer section.
   262  		if (!cfg.whitelist && ignored == 0) || included > ignored {
   263  			// Just want to make sure we don't have labels deleted in
   264  			// on side and disappearing in the other side...
   265  			identityLabels[k] = v
   266  		} else {
   267  			informationLabels[k] = v
   268  		}
   269  	}
   270  	return identityLabels, informationLabels
   271  }
   272  
   273  // FilterLabels returns Labels from the given labels that have the same source and the
   274  // same prefix as one of lpc valid prefixes, as well as labels that do not match
   275  // the aforementioned filtering criteria.
   276  func FilterLabels(lbls Labels) (identityLabels, informationLabels Labels) {
   277  	return validLabelPrefixes.filterLabels(lbls)
   278  }