github.com/cilium/cilium@v1.16.2/pkg/endpoint/api.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 // This file contains functions related to conversion of information about 5 // an Endpoint to its corresponding Cilium API representation. 6 7 package endpoint 8 9 import ( 10 "context" 11 "fmt" 12 "net/netip" 13 "sort" 14 "strconv" 15 16 "github.com/sirupsen/logrus" 17 18 "github.com/cilium/cilium/api/v1/models" 19 "github.com/cilium/cilium/pkg/endpoint/regeneration" 20 "github.com/cilium/cilium/pkg/identity/cache" 21 identitymodel "github.com/cilium/cilium/pkg/identity/model" 22 "github.com/cilium/cilium/pkg/labels" 23 "github.com/cilium/cilium/pkg/labels/model" 24 "github.com/cilium/cilium/pkg/labelsfilter" 25 "github.com/cilium/cilium/pkg/mac" 26 "github.com/cilium/cilium/pkg/option" 27 "github.com/cilium/cilium/pkg/policy" 28 "github.com/cilium/cilium/pkg/types" 29 "github.com/cilium/cilium/pkg/u8proto" 30 ) 31 32 // GetLabelsModel returns the labels of the endpoint in their representation 33 // for the Cilium API. Returns an error if the Endpoint is being deleted. 34 func (e *Endpoint) GetLabelsModel() (*models.LabelConfiguration, error) { 35 if err := e.rlockAlive(); err != nil { 36 return nil, err 37 } 38 spec := &models.LabelConfigurationSpec{ 39 User: e.OpLabels.Custom.GetModel(), 40 } 41 42 cfg := models.LabelConfiguration{ 43 Spec: spec, 44 Status: &models.LabelConfigurationStatus{ 45 Realized: spec, 46 SecurityRelevant: e.OpLabels.OrchestrationIdentity.GetModel(), 47 Derived: e.OpLabels.OrchestrationInfo.GetModel(), 48 Disabled: e.OpLabels.Disabled.GetModel(), 49 }, 50 } 51 e.runlock() 52 return &cfg, nil 53 } 54 55 func parsePrefixOrAddr(ip string) (netip.Addr, error) { 56 prefix, err := netip.ParsePrefix(ip) 57 if err != nil { 58 return netip.ParseAddr(ip) 59 } 60 return prefix.Addr(), nil 61 } 62 63 // NewEndpointFromChangeModel creates a new endpoint from a request 64 func NewEndpointFromChangeModel(ctx context.Context, owner regeneration.Owner, policyGetter policyRepoGetter, namedPortsGetter namedPortsGetter, proxy EndpointProxy, allocator cache.IdentityAllocator, base *models.EndpointChangeRequest) (*Endpoint, error) { 65 if base == nil { 66 return nil, nil 67 } 68 69 ep := createEndpoint(owner, policyGetter, namedPortsGetter, proxy, allocator, uint16(base.ID), base.InterfaceName) 70 ep.ifIndex = int(base.InterfaceIndex) 71 ep.containerIfName = base.ContainerInterfaceName 72 if base.ContainerName != "" { 73 ep.containerName.Store(&base.ContainerName) 74 } 75 if base.ContainerID != "" { 76 ep.containerID.Store(&base.ContainerID) 77 } 78 ep.dockerNetworkID = base.DockerNetworkID 79 ep.dockerEndpointID = base.DockerEndpointID 80 ep.K8sPodName = base.K8sPodName 81 ep.K8sNamespace = base.K8sNamespace 82 ep.K8sUID = base.K8sUID 83 ep.disableLegacyIdentifiers = base.DisableLegacyIdentifiers 84 85 if base.Mac != "" { 86 m, err := mac.ParseMAC(base.Mac) 87 if err != nil { 88 return nil, err 89 } 90 ep.mac = m 91 } 92 93 if base.HostMac != "" { 94 m, err := mac.ParseMAC(base.HostMac) 95 if err != nil { 96 return nil, err 97 } 98 ep.nodeMAC = m 99 } 100 101 if base.NetnsCookie != "" { 102 cookie64, err := strconv.ParseInt(base.NetnsCookie, 10, 64) 103 if err != nil { 104 // Don't return on error (and block the endpoint creation) as this 105 // is an unusual case where data could have been malformed. Defer error 106 // logging to individual features depending on the metadata. 107 log.WithError(err).WithFields(logrus.Fields{ 108 "netns_cookie": base.NetnsCookie, 109 "ep_id": base.ID, 110 }).Error("unable to parse netns cookie for ep") 111 } else { 112 ep.NetNsCookie = uint64(cookie64) 113 } 114 } 115 116 if base.Addressing != nil { 117 if ip := base.Addressing.IPV6; ip != "" { 118 ip6, err := parsePrefixOrAddr(ip) 119 if err != nil { 120 return nil, err 121 } 122 if !ip6.Is6() { 123 return nil, fmt.Errorf("invalid IPv6 address %q", ip) 124 } 125 ep.IPv6 = ip6 126 ep.IPv6IPAMPool = base.Addressing.IPV6PoolName 127 } 128 129 if ip := base.Addressing.IPV4; ip != "" { 130 ip4, err := parsePrefixOrAddr(ip) 131 if err != nil { 132 return nil, err 133 } 134 if !ip4.Is4() { 135 return nil, fmt.Errorf("invalid IPv4 address %q", ip) 136 } 137 ep.IPv4 = ip4 138 ep.IPv4IPAMPool = base.Addressing.IPV4PoolName 139 } 140 } 141 142 if base.DatapathConfiguration != nil { 143 ep.DatapathConfiguration = *base.DatapathConfiguration 144 // We need to make sure DatapathConfiguration.DisableSipVerification value 145 // overrides the value of SourceIPVerification runtime option of the endpoint. 146 if ep.DatapathConfiguration.DisableSipVerification { 147 ep.updateAndOverrideEndpointOptions(option.OptionMap{option.SourceIPVerification: option.OptionDisabled}) 148 } 149 } 150 151 if base.Labels != nil { 152 lbls := labels.NewLabelsFromModel(base.Labels) 153 identityLabels, infoLabels := labelsfilter.Filter(lbls) 154 ep.OpLabels.OrchestrationIdentity = identityLabels 155 ep.OpLabels.OrchestrationInfo = infoLabels 156 } 157 158 if base.State != nil { 159 ep.setState(State(*base.State), "Endpoint creation") 160 } 161 162 if base.Properties != nil { 163 ep.properties = base.Properties 164 } 165 166 return ep, nil 167 } 168 169 func (e *Endpoint) getModelEndpointIdentitiersRLocked() *models.EndpointIdentifiers { 170 identifiers := &models.EndpointIdentifiers{ 171 CniAttachmentID: e.GetCNIAttachmentID(), 172 DockerEndpointID: e.dockerEndpointID, 173 DockerNetworkID: e.dockerNetworkID, 174 } 175 176 // Use legacy endpoint identifiers only if the endpoint has not opted out 177 if !e.disableLegacyIdentifiers { 178 identifiers.ContainerID = e.GetContainerID() 179 identifiers.ContainerName = e.GetContainerName() 180 identifiers.PodName = e.GetK8sNamespaceAndPodName() 181 identifiers.K8sPodName = e.K8sPodName 182 identifiers.K8sNamespace = e.K8sNamespace 183 } 184 185 return identifiers 186 } 187 188 func (e *Endpoint) getModelNetworkingRLocked() *models.EndpointNetworking { 189 return &models.EndpointNetworking{ 190 Addressing: []*models.AddressPair{{ 191 IPV4: e.GetIPv4Address(), 192 IPV4PoolName: e.IPv4IPAMPool, 193 IPV6: e.GetIPv6Address(), 194 IPV6PoolName: e.IPv6IPAMPool, 195 }}, 196 InterfaceIndex: int64(e.ifIndex), 197 InterfaceName: e.ifName, 198 ContainerInterfaceName: e.containerIfName, 199 Mac: e.mac.String(), 200 HostMac: e.nodeMAC.String(), 201 } 202 } 203 204 func (e *Endpoint) getModelCurrentStateRLocked() models.EndpointState { 205 currentState := models.EndpointState(e.state) 206 if currentState == models.EndpointStateReady && e.status.CurrentStatus() != OK { 207 return models.EndpointStateNotDashReady 208 } 209 return currentState 210 } 211 212 // GetModelRLocked returns the API model of endpoint e. 213 // e.mutex must be RLocked. 214 func (e *Endpoint) GetModelRLocked() *models.Endpoint { 215 if e == nil { 216 return nil 217 } 218 219 // This returns the most recent log entry for this endpoint. It is backwards 220 // compatible with the json from before we added `cilium endpoint log` but it 221 // only returns 1 entry. 222 statusLog := e.status.GetModel() 223 if len(statusLog) > 0 { 224 statusLog = statusLog[:1] 225 } 226 227 lblMdl := model.NewModel(&e.OpLabels) 228 229 // Sort these slices since they come out in random orders. This allows 230 // reflect.DeepEqual to succeed. 231 sort.StringSlice(lblMdl.Realized.User).Sort() 232 sort.StringSlice(lblMdl.Disabled).Sort() 233 sort.StringSlice(lblMdl.SecurityRelevant).Sort() 234 sort.StringSlice(lblMdl.Derived).Sort() 235 236 controllerMdl := e.controllers.GetStatusModel() 237 sort.Slice(controllerMdl, func(i, j int) bool { return controllerMdl[i].Name < controllerMdl[j].Name }) 238 239 spec := &models.EndpointConfigurationSpec{ 240 LabelConfiguration: lblMdl.Realized, 241 } 242 243 if e.Options != nil { 244 spec.Options = *e.Options.GetMutableModel() 245 } 246 247 mdl := &models.Endpoint{ 248 ID: int64(e.ID), 249 Spec: spec, 250 Status: &models.EndpointStatus{ 251 // FIXME GH-3280 When we begin implementing revision numbers this will 252 // diverge from models.Endpoint.Spec to reflect the in-datapath config 253 Realized: spec, 254 Identity: identitymodel.CreateModel(e.SecurityIdentity), 255 Labels: lblMdl, 256 Networking: e.getModelNetworkingRLocked(), 257 ExternalIdentifiers: e.getModelEndpointIdentitiersRLocked(), 258 // FIXME GH-3280 When we begin returning endpoint revisions this should 259 // change to return the configured and in-datapath policies. 260 Policy: e.GetPolicyModel(), 261 Log: statusLog, 262 Controllers: controllerMdl, 263 State: e.getModelCurrentStateRLocked().Pointer(), // TODO: Validate 264 Health: e.getHealthModel(), 265 NamedPorts: e.getNamedPortsModel(), 266 }, 267 } 268 269 return mdl 270 } 271 272 // GetHealthModel returns the endpoint's health object. 273 // 274 // Must be called with e.mutex RLock()ed. 275 func (e *Endpoint) getHealthModel() *models.EndpointHealth { 276 // Duplicated from GetModelRLocked. 277 currentState := models.EndpointState(e.state) 278 if currentState == models.EndpointStateReady && e.status.CurrentStatus() != OK { 279 currentState = models.EndpointStateNotDashReady 280 } 281 282 h := models.EndpointHealth{ 283 Bpf: models.EndpointHealthStatusDisabled, 284 Policy: models.EndpointHealthStatusDisabled, 285 Connected: false, 286 OverallHealth: models.EndpointHealthStatusDisabled, 287 } 288 switch currentState { 289 case models.EndpointStateRegenerating, models.EndpointStateWaitingDashToDashRegenerate, models.EndpointStateDisconnecting: 290 h = models.EndpointHealth{ 291 Bpf: models.EndpointHealthStatusPending, 292 Policy: models.EndpointHealthStatusPending, 293 Connected: true, 294 OverallHealth: models.EndpointHealthStatusPending, 295 } 296 case models.EndpointStateWaitingDashForDashIdentity: 297 h = models.EndpointHealth{ 298 Bpf: models.EndpointHealthStatusDisabled, 299 Policy: models.EndpointHealthStatusBootstrap, 300 Connected: true, 301 OverallHealth: models.EndpointHealthStatusDisabled, 302 } 303 case models.EndpointStateNotDashReady: 304 h = models.EndpointHealth{ 305 Bpf: models.EndpointHealthStatusWarning, 306 Policy: models.EndpointHealthStatusWarning, 307 Connected: true, 308 OverallHealth: models.EndpointHealthStatusWarning, 309 } 310 case models.EndpointStateDisconnected: 311 h = models.EndpointHealth{ 312 Bpf: models.EndpointHealthStatusDisabled, 313 Policy: models.EndpointHealthStatusDisabled, 314 Connected: false, 315 OverallHealth: models.EndpointHealthStatusDisabled, 316 } 317 case models.EndpointStateReady: 318 h = models.EndpointHealth{ 319 Bpf: models.EndpointHealthStatusOK, 320 Policy: models.EndpointHealthStatusOK, 321 Connected: true, 322 OverallHealth: models.EndpointHealthStatusOK, 323 } 324 } 325 326 return &h 327 } 328 329 // GetHealthModel returns the endpoint's health object. 330 func (e *Endpoint) GetHealthModel() *models.EndpointHealth { 331 // NOTE: Using rlock on mutex directly because getHealthModel handles removed endpoint properly 332 e.mutex.RLock() 333 defer e.mutex.RUnlock() 334 return e.getHealthModel() 335 } 336 337 // getNamedPortsModel returns the endpoint's NamedPorts object. 338 func (e *Endpoint) getNamedPortsModel() (np models.NamedPorts) { 339 var k8sPorts types.NamedPortMap 340 if p := e.k8sPorts.Load(); p != nil { 341 k8sPorts = *p 342 } 343 344 // keep named ports ordered to avoid the unnecessary updates to 345 // kube-apiserver 346 names := make([]string, 0, len(k8sPorts)) 347 for name := range k8sPorts { 348 names = append(names, name) 349 } 350 sort.Strings(names) 351 352 np = make(models.NamedPorts, 0, len(k8sPorts)) 353 for _, name := range names { 354 value := k8sPorts[name] 355 np = append(np, &models.Port{ 356 Name: name, 357 Port: value.Port, 358 Protocol: u8proto.U8proto(value.Proto).String(), 359 }) 360 } 361 return np 362 } 363 364 // GetNamedPortsModel returns the endpoint's NamedPorts object. 365 func (e *Endpoint) GetNamedPortsModel() models.NamedPorts { 366 if err := e.rlockAlive(); err != nil { 367 return nil 368 } 369 defer e.runlock() 370 return e.getNamedPortsModel() 371 } 372 373 // GetModel returns the API model of endpoint e. 374 func (e *Endpoint) GetModel() *models.Endpoint { 375 if e == nil { 376 return nil 377 } 378 // NOTE: Using rlock on mutex directly because GetModelRLocked handles removed endpoint properly 379 e.mutex.RLock() 380 defer e.mutex.RUnlock() 381 382 return e.GetModelRLocked() 383 } 384 385 // GetPolicyModel returns the endpoint's policy as an API model. 386 // 387 // Must be called with e.mutex RLock()ed. 388 func (e *Endpoint) GetPolicyModel() *models.EndpointPolicyStatus { 389 if e == nil { 390 return nil 391 } 392 393 if e.SecurityIdentity == nil { 394 return nil 395 } 396 397 realizedLog := log.WithField("map-name", "realized").Logger 398 realizedIngressIdentities, realizedEgressIdentities := 399 e.realizedPolicy.GetPolicyMap().GetIdentities(realizedLog) 400 401 realizedDenyIngressIdentities, realizedDenyEgressIdentities := 402 e.realizedPolicy.GetPolicyMap().GetDenyIdentities(realizedLog) 403 404 desiredLog := log.WithField("map-name", "desired").Logger 405 desiredIngressIdentities, desiredEgressIdentities := 406 e.desiredPolicy.GetPolicyMap().GetIdentities(desiredLog) 407 408 desiredDenyIngressIdentities, desiredDenyEgressIdentities := 409 e.desiredPolicy.GetPolicyMap().GetDenyIdentities(desiredLog) 410 411 policyEnabled := e.policyStatus() 412 413 e.proxyStatisticsMutex.RLock() 414 proxyStats := make([]*models.ProxyStatistics, 0, len(e.proxyStatistics)) 415 for _, stats := range e.proxyStatistics { 416 proxyStats = append(proxyStats, stats.DeepCopy()) 417 } 418 e.proxyStatisticsMutex.RUnlock() 419 sortProxyStats(proxyStats) 420 421 var ( 422 realizedL4Policy *policy.L4Policy 423 ) 424 if e.realizedPolicy != nil { 425 realizedL4Policy = &e.realizedPolicy.L4Policy 426 } 427 428 mdl := &models.EndpointPolicy{ 429 ID: int64(e.SecurityIdentity.ID), 430 // This field should be removed. 431 Build: int64(e.policyRevision), 432 PolicyRevision: int64(e.policyRevision), 433 AllowedIngressIdentities: realizedIngressIdentities, 434 AllowedEgressIdentities: realizedEgressIdentities, 435 DeniedIngressIdentities: realizedDenyIngressIdentities, 436 DeniedEgressIdentities: realizedDenyEgressIdentities, 437 L4: realizedL4Policy.GetModel(), 438 PolicyEnabled: policyEnabled, 439 } 440 441 var ( 442 desiredL4Policy *policy.L4Policy 443 ) 444 if e.desiredPolicy != nil { 445 desiredL4Policy = &e.desiredPolicy.L4Policy 446 } 447 448 desiredMdl := &models.EndpointPolicy{ 449 ID: int64(e.SecurityIdentity.ID), 450 // This field should be removed. 451 Build: int64(e.nextPolicyRevision), 452 PolicyRevision: int64(e.nextPolicyRevision), 453 AllowedIngressIdentities: desiredIngressIdentities, 454 AllowedEgressIdentities: desiredEgressIdentities, 455 DeniedIngressIdentities: desiredDenyIngressIdentities, 456 DeniedEgressIdentities: desiredDenyEgressIdentities, 457 L4: desiredL4Policy.GetModel(), 458 PolicyEnabled: policyEnabled, 459 } 460 // FIXME GH-3280 Once we start returning revisions Realized should be the 461 // policy implemented in the data path 462 return &models.EndpointPolicyStatus{ 463 Spec: desiredMdl, 464 Realized: mdl, 465 ProxyPolicyRevision: int64(e.proxyPolicyRevision), 466 ProxyStatistics: proxyStats, 467 } 468 } 469 470 // policyStatus returns the endpoint's policy status 471 // 472 // Must be called with e.mutex RLock()ed. 473 func (e *Endpoint) policyStatus() models.EndpointPolicyEnabled { 474 policyEnabled := models.EndpointPolicyEnabledNone 475 switch { 476 case e.realizedPolicy.IngressPolicyEnabled && e.realizedPolicy.EgressPolicyEnabled: 477 policyEnabled = models.EndpointPolicyEnabledBoth 478 case e.realizedPolicy.IngressPolicyEnabled: 479 policyEnabled = models.EndpointPolicyEnabledIngress 480 case e.realizedPolicy.EgressPolicyEnabled: 481 policyEnabled = models.EndpointPolicyEnabledEgress 482 } 483 484 if e.Options.IsEnabled(option.PolicyAuditMode) { 485 switch policyEnabled { 486 case models.EndpointPolicyEnabledIngress: 487 return models.EndpointPolicyEnabledAuditDashIngress 488 case models.EndpointPolicyEnabledEgress: 489 return models.EndpointPolicyEnabledAuditDashEgress 490 case models.EndpointPolicyEnabledBoth: 491 return models.EndpointPolicyEnabledAuditDashBoth 492 } 493 } 494 495 return policyEnabled 496 } 497 498 // ProcessChangeRequest handles the update logic for performing a PATCH operation 499 // on a given Endpoint. Returns the reason which will be used for informational 500 // purposes should a caller choose to try to regenerate this endpoint, as well 501 // as an error if the Endpoint is being deleted, since there is no point in 502 // changing an Endpoint if it is going to be deleted. 503 // 504 // Before adding any new fields here, check to see if they are assumed to be mutable after 505 // endpoint creation! 506 func (e *Endpoint) ProcessChangeRequest(newEp *Endpoint, validPatchTransitionState bool) (string, error) { 507 var ( 508 changed bool 509 reason string 510 ) 511 512 if err := e.lockAlive(); err != nil { 513 return "", err 514 } 515 defer e.unlock() 516 517 if newEp.ifIndex != 0 && e.ifIndex != newEp.ifIndex { 518 e.ifIndex = newEp.ifIndex 519 changed = true 520 } 521 522 if newEp.ifName != "" && e.ifName != newEp.ifName { 523 e.ifName = newEp.ifName 524 changed = true 525 } 526 527 // Only support transition to waiting-for-identity state, also 528 // if the request is for ready state, as we will check the 529 // existence of the security label below. Other transitions 530 // are always internally managed, but we do not error out for 531 // backwards compatibility. 532 if newEp.state != "" && 533 validPatchTransitionState && 534 e.getState() != StateWaitingForIdentity { 535 // Will not change state if the current state does not allow the transition. 536 if e.setState(StateWaitingForIdentity, "Update endpoint from API PATCH") { 537 changed = true 538 } 539 } 540 541 if newContainerName := newEp.containerName.Load(); newContainerName != nil && *newContainerName != "" { 542 e.containerName.Store(newContainerName) 543 // no need to set changed here 544 } 545 546 if newContainerID := newEp.containerID.Load(); newContainerID != nil && *newContainerID != "" { 547 e.containerID.Store(newContainerID) 548 // no need to set changed here 549 } 550 551 e.replaceInformationLabels(labels.LabelSourceAny, newEp.OpLabels.OrchestrationInfo) 552 rev := e.replaceIdentityLabels(labels.LabelSourceAny, newEp.OpLabels.IdentityLabels()) 553 if rev != 0 { 554 // Run as a goroutine since the runIdentityResolver needs to get the lock 555 go e.runIdentityResolver(e.aliveCtx, false) 556 } 557 558 // If desired state is waiting-for-identity but identity is already 559 // known, bump it to ready state immediately to force re-generation 560 if newEp.state == StateWaitingForIdentity && e.SecurityIdentity != nil { 561 e.setState(StateReady, "Preparing to force endpoint regeneration because identity is known while handling API PATCH") 562 changed = true 563 } 564 565 if changed { 566 // Force policy regeneration as endpoint's configuration was changed. 567 // Other endpoints need not be regenerated as no labels were changed. 568 // Note that we still need to (eventually) regenerate the endpoint for 569 // the changes to take effect. 570 e.forcePolicyComputation() 571 572 // Transition to waiting-to-regenerate if ready. 573 if e.getState() == StateReady { 574 e.setState(StateWaitingToRegenerate, "Forcing endpoint regeneration because identity is known while handling API PATCH") 575 } 576 577 switch e.getState() { 578 case StateWaitingToRegenerate: 579 reason = "Waiting on endpoint regeneration because identity is known while handling API PATCH" 580 case StateWaitingForIdentity: 581 reason = "Waiting on endpoint initial program regeneration while handling API PATCH" 582 default: 583 // Caller skips regeneration if reason == "". Bump the skipped regeneration level so that next 584 // regeneration will realise endpoint changes. 585 if e.skippedRegenerationLevel < regeneration.RegenerateWithDatapath { 586 e.skippedRegenerationLevel = regeneration.RegenerateWithDatapath 587 } 588 } 589 } 590 591 e.UpdateLogger(nil) 592 593 return reason, nil 594 } 595 596 // GetConfigurationStatus returns the Cilium API representation of the 597 // configuration of this endpoint. 598 func (e *Endpoint) GetConfigurationStatus() *models.EndpointConfigurationStatus { 599 return &models.EndpointConfigurationStatus{ 600 Realized: &models.EndpointConfigurationSpec{ 601 LabelConfiguration: &models.LabelConfigurationSpec{ 602 User: e.OpLabels.Custom.GetModel(), 603 }, 604 Options: *e.Options.GetMutableModel(), 605 }, 606 Immutable: *e.Options.GetImmutableModel(), 607 } 608 } 609 610 // ApplyUserLabelChanges changes the label configuration of the endpoint per the 611 // provided labels. Returns labels that were added and deleted. Returns an 612 // error if the endpoint is being deleted. 613 func (e *Endpoint) ApplyUserLabelChanges(lbls labels.Labels) (add, del labels.Labels, err error) { 614 if err := e.rlockAlive(); err != nil { 615 return nil, nil, err 616 } 617 defer e.runlock() 618 add, del = e.OpLabels.SplitUserLabelChanges(lbls) 619 return 620 } 621 622 // GetStatusModel returns the model of the status of this endpoint. 623 func (e *Endpoint) GetStatusModel() []*models.EndpointStatusChange { 624 return e.status.GetModel() 625 }