istio.io/istio@v0.0.0-20240520182934-d79c90f27776/istioctl/pkg/checkinject/checkinject.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 checkinject 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "reflect" 22 "sort" 23 "strings" 24 25 "github.com/fatih/color" 26 "github.com/spf13/cobra" 27 admitv1 "k8s.io/api/admissionregistration/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/labels" 30 31 "istio.io/api/label" 32 "istio.io/istio/istioctl/pkg/cli" 33 "istio.io/istio/istioctl/pkg/completion" 34 "istio.io/istio/istioctl/pkg/util" 35 "istio.io/istio/istioctl/pkg/writer/table" 36 analyzer_util "istio.io/istio/pkg/config/analysis/analyzers/util" 37 ) 38 39 var labelPairs string 40 41 func Cmd(ctx cli.Context) *cobra.Command { 42 cmd := &cobra.Command{ 43 Use: "check-inject [<type>/]<name>[.<namespace>]", 44 Short: "Check the injection status or inject-ability of a given resource, explains why it is (or will be) injected or not", 45 Long: ` 46 Checks associated resources of the given resource, and running webhooks to examine whether the pod can be or will be injected or not.`, 47 Example: ` # Check the injection status of a pod 48 istioctl experimental check-inject details-v1-fcff6c49c-kqnfk.test 49 50 # Check the injection status of a pod under a deployment 51 istioctl x check-inject deployment/details-v1 52 53 # Check the injection status of a pod under a deployment in namespace test 54 istioctl x check-inject deployment/details-v1 -n test 55 56 # Check the injection status of label pairs in a specific namespace before actual injection 57 istioctl x check-inject -n test -l app=helloworld,version=v1 58 `, 59 Args: func(cmd *cobra.Command, args []string) error { 60 if len(args) == 0 && labelPairs == "" || len(args) > 1 { 61 cmd.Println(cmd.UsageString()) 62 return fmt.Errorf("check-inject requires only [<resource-type>/]<resource-name>[.<namespace>], or specify labels flag") 63 } 64 return nil 65 }, 66 RunE: func(cmd *cobra.Command, args []string) error { 67 kubeClient, err := ctx.CLIClient() 68 if err != nil { 69 return err 70 } 71 var podName, podNs string 72 var podLabels, nsLabels map[string]string 73 if len(args) == 1 { 74 podName, podNs, err = ctx.InferPodInfoFromTypedResource(args[0], ctx.Namespace()) 75 if err != nil { 76 return err 77 } 78 pod, err := kubeClient.Kube().CoreV1().Pods(podNs).Get(context.TODO(), podName, metav1.GetOptions{}) 79 if err != nil { 80 return err 81 } 82 ns, err := kubeClient.Kube().CoreV1().Namespaces().Get(context.TODO(), podNs, metav1.GetOptions{}) 83 if err != nil { 84 return err 85 } 86 podLabels = pod.GetLabels() 87 nsLabels = ns.GetLabels() 88 } else { 89 namespace := ctx.NamespaceOrDefault(ctx.Namespace()) 90 ns, err := kubeClient.Kube().CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}) 91 if err != nil { 92 return err 93 } 94 ls, err := metav1.ParseToLabelSelector(labelPairs) 95 if err != nil { 96 return err 97 } 98 podLabels = ls.MatchLabels 99 nsLabels = ns.GetLabels() 100 } 101 whs, err := kubeClient.Kube().AdmissionregistrationV1().MutatingWebhookConfigurations().List(context.TODO(), metav1.ListOptions{}) 102 if err != nil { 103 return err 104 } 105 checkResults := analyzeRunningWebhooks(whs.Items, podLabels, nsLabels) 106 return printCheckInjectorResults(cmd.OutOrStdout(), checkResults) 107 }, 108 ValidArgsFunction: completion.ValidPodsNameArgs(ctx), 109 } 110 cmd.PersistentFlags().StringVarP(&labelPairs, "labels", "l", "", 111 "Check namespace and label pairs injection status, split multiple labels by commas") 112 return cmd 113 } 114 115 func printCheckInjectorResults(writer io.Writer, was []webhookAnalysis) error { 116 if len(was) == 0 { 117 fmt.Fprintf(writer, "ERROR: no Istio injection hooks present.\n") 118 return nil 119 } 120 w := table.NewStyleWriter(writer) 121 w.SetAddRowFunc(func(obj interface{}) table.Row { 122 wa := obj.(webhookAnalysis) 123 row := table.Row{ 124 Cells: make([]table.Cell, 0), 125 } 126 row.Cells = append(row.Cells, table.NewCell(wa.Name), table.NewCell(wa.Revision)) 127 if wa.Injected { 128 row.Cells = append(row.Cells, table.NewCell("✔", color.FgGreen)) 129 } else { 130 row.Cells = append(row.Cells, table.NewCell("✘", color.FgRed)) 131 } 132 row.Cells = append(row.Cells, table.NewCell(wa.Reason)) 133 return row 134 }) 135 w.AddHeader("WEBHOOK", "REVISION", "INJECTED", "REASON") 136 injectedTotal := 0 137 for _, ws := range was { 138 if ws.Injected { 139 injectedTotal++ 140 } 141 w.AddRow(ws) 142 } 143 w.Flush() 144 if injectedTotal > 1 { 145 fmt.Fprintf(writer, "ERROR: multiple webhooks will inject, which can lead to errors") 146 } 147 return nil 148 } 149 150 type webhookAnalysis struct { 151 Name string 152 Revision string 153 Injected bool 154 Reason string 155 } 156 157 func analyzeRunningWebhooks(whs []admitv1.MutatingWebhookConfiguration, podLabels, nsLabels map[string]string) []webhookAnalysis { 158 results := make([]webhookAnalysis, 0) 159 for _, mwc := range whs { 160 if !isIstioWebhook(&mwc) { 161 continue 162 } 163 rev := extractRevision(&mwc) 164 reason, injected := analyzeWebhooksMatchStatus(mwc.Webhooks, podLabels, nsLabels) 165 results = append(results, webhookAnalysis{ 166 Name: mwc.Name, 167 Revision: rev, 168 Injected: injected, 169 Reason: reason, 170 }) 171 } 172 sort.Slice(results, func(i, j int) bool { 173 return results[i].Name < results[j].Name 174 }) 175 return results 176 } 177 178 func analyzeWebhooksMatchStatus(whs []admitv1.MutatingWebhook, podLabels, nsLabels map[string]string) (reason string, injected bool) { 179 for _, wh := range whs { 180 nsMatched, nsLabel := extractMatchedSelectorInfo(wh.NamespaceSelector, nsLabels) 181 podMatched, podLabel := extractMatchedSelectorInfo(wh.ObjectSelector, podLabels) 182 if nsMatched && podMatched { 183 if nsLabel != "" && podLabel != "" { 184 return fmt.Sprintf("Namespace label %s matches, and pod label %s matches", nsLabel, podLabel), true 185 } else if nsLabel != "" { 186 outMsg := fmt.Sprintf("Namespace label %s matches", nsLabel) 187 if strings.Contains(nsLabel, "kubernetes.io/metadata.name") { 188 outMsg += " (Automatic injection is enabled in all namespaces)." 189 } 190 return outMsg, true 191 } else if podLabel != "" { 192 return fmt.Sprintf("Pod label %s matches", podLabel), true 193 } 194 } else if nsMatched { 195 for _, me := range wh.ObjectSelector.MatchExpressions { 196 switch me.Operator { 197 case metav1.LabelSelectorOpDoesNotExist: 198 v, ok := podLabels[me.Key] 199 if ok { 200 return fmt.Sprintf("Pod has %s=%s label, preventing injection", me.Key, v), false 201 } 202 case metav1.LabelSelectorOpNotIn: 203 v, ok := podLabels[me.Key] 204 if !ok { 205 continue 206 } 207 for _, nv := range me.Values { 208 if nv == v { 209 return fmt.Sprintf("Pod has %s=%s label, preventing injection", me.Key, v), false 210 } 211 } 212 } 213 } 214 } else if podMatched { 215 if v, ok := nsLabels[analyzer_util.InjectionLabelName]; ok { 216 if v != "enabled" { 217 return fmt.Sprintf("Namespace has %s=%s label, preventing injection", 218 analyzer_util.InjectionLabelName, v), false 219 } 220 } 221 } 222 } 223 noMatchingReason := func(whs []admitv1.MutatingWebhook) string { 224 nsMatchedLabels := make([]string, 0) 225 podMatchedLabels := make([]string, 0) 226 extractMatchLabels := func(selector *metav1.LabelSelector) []string { 227 if selector == nil { 228 return nil 229 } 230 labels := make([]string, 0) 231 for _, me := range selector.MatchExpressions { 232 if me.Operator != metav1.LabelSelectorOpIn { 233 continue 234 } 235 for _, v := range me.Values { 236 labels = append(labels, fmt.Sprintf("%s=%s", me.Key, v)) 237 } 238 } 239 return labels 240 } 241 242 var isDeactivated bool 243 for _, wh := range whs { 244 if reflect.DeepEqual(wh.NamespaceSelector, util.NeverMatch) && reflect.DeepEqual(wh.ObjectSelector, util.NeverMatch) { 245 isDeactivated = true 246 } 247 nsMatchedLabels = append(nsMatchedLabels, extractMatchLabels(wh.NamespaceSelector)...) 248 podMatchedLabels = append(podMatchedLabels, extractMatchLabels(wh.ObjectSelector)...) 249 } 250 if isDeactivated { 251 return "The injection webhook is deactivated, and will never match labels." 252 } 253 return fmt.Sprintf("No matching namespace labels (%s) "+ 254 "or pod labels (%s)", strings.Join(nsMatchedLabels, ", "), strings.Join(podMatchedLabels, ", ")) 255 } 256 return noMatchingReason(whs), false 257 } 258 259 func extractMatchedSelectorInfo(ls *metav1.LabelSelector, objLabels map[string]string) (matched bool, injLabel string) { 260 if ls == nil { 261 return true, "" 262 } 263 selector, err := metav1.LabelSelectorAsSelector(ls) 264 if err != nil { 265 return false, "" 266 } 267 matched = selector.Matches(labels.Set(objLabels)) 268 if !matched { 269 return matched, "" 270 } 271 for _, me := range ls.MatchExpressions { 272 switch me.Operator { 273 case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn: 274 if v, exist := objLabels[me.Key]; exist { 275 return matched, fmt.Sprintf("%s=%s", me.Key, v) 276 } 277 } 278 } 279 return matched, "" 280 } 281 282 func extractRevision(wh *admitv1.MutatingWebhookConfiguration) string { 283 return wh.GetLabels()[label.IoIstioRev.Name] 284 } 285 286 func isIstioWebhook(wh *admitv1.MutatingWebhookConfiguration) bool { 287 for _, w := range wh.Webhooks { 288 if strings.HasSuffix(w.Name, "istio.io") { 289 return true 290 } 291 } 292 return false 293 }