endpoint_status.go
// Copyright 2019-2020 Authors of Cilium
//
// 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 endpoint
import (
"context"
"sort"
"strings"
"time"
"github.com/cilium/cilium/api/v1/models"
"github.com/cilium/cilium/pkg/identity"
"github.com/cilium/cilium/pkg/identity/cache"
identitymodel "github.com/cilium/cilium/pkg/identity/model"
cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
"github.com/cilium/cilium/pkg/labels"
"github.com/cilium/cilium/pkg/node"
"github.com/cilium/cilium/pkg/option"
"github.com/cilium/cilium/pkg/policy"
)
func getEndpointStatusControllers(mdlControllers models.ControllerStatuses) (controllers cilium_v2.ControllerList) {
for _, c := range mdlControllers {
if c.Status == nil {
continue
}
if c.Status.ConsecutiveFailureCount > 0 {
s := cilium_v2.ControllerStatus{
Configuration: c.Configuration,
Name: c.Name,
UUID: string(c.UUID),
Status: cilium_v2.ControllerStatusStatus{
ConsecutiveFailureCount: c.Status.ConsecutiveFailureCount,
FailureCount: c.Status.FailureCount,
LastFailureMsg: c.Status.LastFailureMsg,
LastFailureTimestamp: c.Status.LastFailureTimestamp.String(),
LastSuccessTimestamp: c.Status.LastSuccessTimestamp.String(),
SuccessCount: c.Status.SuccessCount,
},
}
if controllers == nil {
controllers = cilium_v2.ControllerList{s}
} else {
controllers = append(controllers, s)
}
}
}
if controllers != nil {
controllers.Sort()
}
return
}
func (e *Endpoint) getEndpointStatusLog() (log []*models.EndpointStatusChange) {
added := 0
if s := e.status; s != nil {
s.indexMU.RLock()
defer s.indexMU.RUnlock()
for i := s.lastIndex(); ; i-- {
if i < 0 {
i = maxLogs - 1
}
if i < len(s.Log) && s.Log[i] != nil {
l := &models.EndpointStatusChange{
Timestamp: s.Log[i].Timestamp.Format(time.RFC3339),
Code: s.Log[i].Status.Code.String(),
Message: s.Log[i].Status.Msg,
State: models.EndpointState(s.Log[i].Status.State),
}
if strings.ToLower(l.Code) != models.EndpointStatusChangeCodeOk {
if log == nil {
log = []*models.EndpointStatusChange{l}
} else {
log = append(log, l)
}
// Limit the number of endpoint log
// entries to keep the size of the
// EndpointStatus low.
added++
if added >= cilium_v2.EndpointStatusLogEntries {
break
}
}
}
if i == s.Index {
break
}
}
}
return
}
func getEndpointIdentity(mdlIdentity *models.Identity) (identity *cilium_v2.EndpointIdentity) {
if mdlIdentity == nil {
return
}
identity = &cilium_v2.EndpointIdentity{
ID: mdlIdentity.ID,
}
identity.Labels = make([]string, len(mdlIdentity.Labels))
copy(identity.Labels, mdlIdentity.Labels)
sort.Strings(identity.Labels)
return
}
func getEndpointNetworking(mdlNetworking *models.EndpointNetworking) (networking *cilium_v2.EndpointNetworking) {
if mdlNetworking == nil {
return nil
}
networking = &cilium_v2.EndpointNetworking{
Addressing: make(cilium_v2.AddressPairList, len(mdlNetworking.Addressing)),
}
if option.Config.EnableIPv4 {
networking.NodeIP = node.GetIPv4().String()
} else {
networking.NodeIP = node.GetIPv6().String()
}
for i, pair := range mdlNetworking.Addressing {
networking.Addressing[i] = &cilium_v2.AddressPair{
IPV4: pair.IPV4,
IPV6: pair.IPV6,
}
}
networking.Addressing.Sort()
return
}
// updateLabels inserts the labels correnspoding to the specified identity into
// the AllowedIdentityTuple.
func updateLabels(allocator cache.IdentityAllocator, allowedIdentityTuple *cilium_v2.IdentityTuple, secID identity.NumericIdentity) {
// IdentityUnknown denotes that this is an L4-only BPF
// allow, so it applies to all identities. In this case
// we should skip resolving the labels, because the
// value 0 does not denote an allow for the "unknown"
// identity, but instead an allow of all identities for
// that port.
if secID != identity.IdentityUnknown {
identity := allocator.LookupIdentityByID(context.TODO(), secID)
if identity != nil {
var l labels.Labels
if identity.CIDRLabel != nil {
l = identity.CIDRLabel
} else {
l = identity.Labels
}
allowedIdentityTuple.IdentityLabels = l.StringMap()
}
}
}
// populateResponseWithPolicyKey inserts an AllowedIdentityTuple element into
// 'policy' which corresponds to the specified 'desiredPolicy'. If 'isDeny' is
// true, it will insert the policyKey into the 'Denied'.
func populateResponseWithPolicyKey(
allocator cache.IdentityAllocator,
policy *cilium_v2.EndpointPolicy,
policyKey *policy.Key,
isDeny bool,
) {
identityTuple := cilium_v2.IdentityTuple{
DestPort: policyKey.DestPort,
Protocol: policyKey.Nexthdr,
Identity: uint64(policyKey.Identity),
}
secID := identity.NumericIdentity(policyKey.Identity)
updateLabels(allocator, &identityTuple, secID)
switch {
case policyKey.IsIngress():
if isDeny {
if policy.Ingress.Denied == nil {
policy.Ingress.Denied = cilium_v2.DenyIdentityList{identityTuple}
} else {
policy.Ingress.Denied = append(policy.Ingress.Denied, identityTuple)
}
} else {
if policy.Ingress.Allowed == nil {
policy.Ingress.Allowed = cilium_v2.AllowedIdentityList{identityTuple}
} else {
policy.Ingress.Allowed = append(policy.Ingress.Allowed, identityTuple)
}
}
case policyKey.IsEgress():
if isDeny {
if policy.Egress.Denied == nil {
policy.Egress.Denied = cilium_v2.DenyIdentityList{identityTuple}
} else {
policy.Egress.Denied = append(policy.Egress.Denied, identityTuple)
}
} else {
if policy.Egress.Allowed == nil {
policy.Egress.Allowed = cilium_v2.AllowedIdentityList{identityTuple}
} else {
policy.Egress.Allowed = append(policy.Egress.Allowed, identityTuple)
}
}
}
}
// getEndpointPolicy returns an API representation of the policy that the
// received Endpoint intends to apply.
func (e *Endpoint) getEndpointPolicy() (ep *cilium_v2.EndpointPolicy) {
if e.desiredPolicy == nil {
return
}
ep = &cilium_v2.EndpointPolicy{
Ingress: &cilium_v2.EndpointPolicyDirection{
Enforcing: !e.Options.IsEnabled(option.PolicyAuditMode) &&
e.desiredPolicy.IngressPolicyEnabled,
},
Egress: &cilium_v2.EndpointPolicyDirection{
Enforcing: !e.Options.IsEnabled(option.PolicyAuditMode) &&
e.desiredPolicy.EgressPolicyEnabled,
},
}
// Handle allow-all cases
allowsAllIngress, allowsAllEgress := e.desiredPolicy.AllowsIdentity(identity.IdentityUnknown)
if allowsAllIngress {
ep.Ingress.Allowed = cilium_v2.AllowedIdentityList{{}}
ep.Ingress.Denied = cilium_v2.DenyIdentityList{{}}
}
if allowsAllEgress {
ep.Egress.Allowed = cilium_v2.AllowedIdentityList{{}}
ep.Egress.Denied = cilium_v2.DenyIdentityList{{}}
}
// If either ingress or egress policy is enabled, go through
// the desired policy to populate the values.
if !allowsAllIngress || !allowsAllEgress {
allowsWorldIngress, allowsWorldEgress := e.desiredPolicy.AllowsIdentity(identity.ReservedIdentityWorld)
for policyKey, policyValue := range e.desiredPolicy.PolicyMapState {
// Skip listing identities if enforcement is disabled in direction,
// or if the identity corresponds to a CIDR identity and the world is allowed.
id := identity.NumericIdentity(policyKey.Identity)
switch {
case policyKey.IsIngress():
if allowsAllIngress || (id.HasLocalScope() && allowsWorldIngress) {
continue
}
case policyKey.IsEgress():
if allowsAllEgress || (id.HasLocalScope() && allowsWorldEgress) {
continue
}
}
populateResponseWithPolicyKey(e.allocator, ep, &policyKey, policyValue.IsDeny)
}
}
if ep.Ingress.Allowed != nil {
ep.Ingress.Allowed.Sort()
}
if ep.Ingress.Denied != nil {
ep.Ingress.Denied.Sort()
}
if ep.Egress.Allowed != nil {
ep.Egress.Allowed.Sort()
}
if ep.Egress.Denied != nil {
ep.Egress.Denied.Sort()
}
return
}
func (e *Endpoint) getEndpointVisibilityPolicyStatus() *string {
if e.visibilityPolicy == nil {
return nil
}
var str string
if e.visibilityPolicy.Error == nil {
str = "OK"
} else {
str = e.visibilityPolicy.Error.Error()
}
return &str
}
// EndpointStatusConfiguration is the configuration interface that a caller of
// to GetCiliumEndpointStatus() must implement
type EndpointStatusConfiguration interface {
// EndpointStatusIsEnabled must return true if a particular
// option.EndpointStatus* feature is enabled
EndpointStatusIsEnabled(option string) bool
}
func compressEndpointState(state models.EndpointState) string {
switch state {
case models.EndpointStateRestoring, models.EndpointStateWaitingToRegenerate,
models.EndpointStateRegenerating, models.EndpointStateReady,
models.EndpointStateDisconnecting, models.EndpointStateDisconnected:
return string(models.EndpointStateReady)
}
return string(state)
}
// GetCiliumEndpointStatus creates a cilium_v2.EndpointStatus of an endpoint.
// See cilium_v2.EndpointStatus for a detailed explanation of each field.
func (e *Endpoint) GetCiliumEndpointStatus(conf EndpointStatusConfiguration) *cilium_v2.EndpointStatus {
e.mutex.RLock()
defer e.mutex.RUnlock()
status := &cilium_v2.EndpointStatus{
ID: int64(e.ID),
ExternalIdentifiers: e.getModelEndpointIdentitiersRLocked(),
Identity: getEndpointIdentity(identitymodel.CreateModel(e.SecurityIdentity)),
Networking: getEndpointNetworking(e.getModelNetworkingRLocked()),
State: compressEndpointState(e.getModelCurrentStateRLocked()),
Encryption: cilium_v2.EncryptionSpec{Key: int(node.GetIPsecKeyIdentity())},
NamedPorts: e.getNamedPortsModel(),
}
if conf.EndpointStatusIsEnabled(option.EndpointStatusControllers) {
controllerMdl := e.controllers.GetStatusModel()
status.Controllers = getEndpointStatusControllers(controllerMdl)
}
if conf.EndpointStatusIsEnabled(option.EndpointStatusPolicy) {
status.Policy = e.getEndpointPolicy()
status.VisibilityPolicyStatus = e.getEndpointVisibilityPolicyStatus()
}
if conf.EndpointStatusIsEnabled(option.EndpointStatusHealth) {
status.Health = e.getHealthModel()
}
if conf.EndpointStatusIsEnabled(option.EndpointStatusLog) {
status.Log = e.getEndpointStatusLog()
}
if conf.EndpointStatusIsEnabled(option.EndpointStatusState) {
status.State = compressEndpointState(e.getModelCurrentStateRLocked())
}
return status
}