2023-03-28 20:12:41 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2023-01-18 22:14:34 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2023-04-24 20:22:55 +00:00
|
|
|
"fmt"
|
2023-01-18 22:14:34 +00:00
|
|
|
"time"
|
2023-04-24 20:22:55 +00:00
|
|
|
|
|
|
|
"golang.org/x/exp/slices"
|
2023-01-18 22:14:34 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ResourceReference is a reference to a ConfigEntry
|
|
|
|
// with an optional reference to a subsection of that ConfigEntry
|
|
|
|
// that can be specified as SectionName
|
|
|
|
type ResourceReference struct {
|
|
|
|
// Kind is the kind of ConfigEntry that this resource refers to.
|
|
|
|
Kind string
|
|
|
|
// Name is the identifier for the ConfigEntry this resource refers to.
|
|
|
|
Name string
|
|
|
|
// SectionName is a generic subresource identifier that specifies
|
|
|
|
// a subset of the ConfigEntry to which this reference applies. Usage
|
|
|
|
// of this field should be up to the controller that leverages it. If
|
|
|
|
// unused, this should be blank.
|
|
|
|
SectionName string
|
|
|
|
|
|
|
|
// Partition is the partition the config entry is associated with.
|
|
|
|
// Partitioning is a Consul Enterprise feature.
|
|
|
|
Partition string `json:",omitempty"`
|
|
|
|
|
|
|
|
// Namespace is the namespace the config entry is associated with.
|
|
|
|
// Namespacing is a Consul Enterprise feature.
|
|
|
|
Namespace string `json:",omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConfigEntryStatus is used for propagating back asynchronously calculated
|
|
|
|
// messages from control loops to a user
|
|
|
|
type ConfigEntryStatus struct {
|
|
|
|
// Conditions is the set of condition objects associated with
|
|
|
|
// a ConfigEntry status.
|
|
|
|
Conditions []Condition
|
|
|
|
}
|
|
|
|
|
|
|
|
// Condition is used for a single message and state associated
|
|
|
|
// with an object. For example, a ConfigEntry that references
|
|
|
|
// multiple other resources may have different statuses with
|
|
|
|
// respect to each of those resources.
|
|
|
|
type Condition struct {
|
2023-02-08 19:50:17 +00:00
|
|
|
// Type is a value from a bounded set of types that an object might have
|
|
|
|
Type string
|
2023-01-18 22:14:34 +00:00
|
|
|
// Status is a value from a bounded set of statuses that an object might have
|
2023-04-24 20:22:55 +00:00
|
|
|
Status ConditionStatus
|
2023-01-18 22:14:34 +00:00
|
|
|
// Reason is a value from a bounded set of reasons for a given status
|
|
|
|
Reason string
|
|
|
|
// Message is a message that gives more detailed information about
|
|
|
|
// why a Condition has a given status and reason
|
|
|
|
Message string
|
|
|
|
// Resource is an optional reference to a resource for which this
|
|
|
|
// condition applies
|
|
|
|
Resource *ResourceReference
|
|
|
|
// LastTransitionTime is the time at which this Condition was created
|
|
|
|
LastTransitionTime *time.Time
|
|
|
|
}
|
2023-04-24 20:22:55 +00:00
|
|
|
|
|
|
|
type (
|
|
|
|
ConditionStatus string
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
ConditionStatusTrue ConditionStatus = "True"
|
|
|
|
ConditionStatusFalse ConditionStatus = "False"
|
|
|
|
ConditionStatusUnknown ConditionStatus = "Unknown"
|
|
|
|
)
|
|
|
|
|
|
|
|
// GatewayConditionType is a type of condition associated with a
|
|
|
|
// Gateway. This type should be used with the GatewayStatus.Conditions
|
|
|
|
// field.
|
|
|
|
type GatewayConditionType string
|
|
|
|
|
|
|
|
// GatewayConditionReason defines the set of reasons that explain why a
|
|
|
|
// particular Gateway condition type has been raised.
|
|
|
|
type GatewayConditionReason string
|
|
|
|
|
|
|
|
// the following are directly from the k8s spec
|
|
|
|
const (
|
|
|
|
// This condition is true when the controller managing the Gateway is
|
|
|
|
// syntactically and semantically valid enough to produce some configuration
|
|
|
|
// in the underlying data plane. This does not indicate whether or not the
|
|
|
|
// configuration has been propagated to the data plane.
|
|
|
|
//
|
|
|
|
// Possible reasons for this condition to be True are:
|
|
|
|
//
|
|
|
|
// * "Accepted"
|
|
|
|
//
|
|
|
|
// Possible reasons for this condition to be False are:
|
|
|
|
//
|
|
|
|
// * InvalidCertificates
|
|
|
|
//
|
|
|
|
GatewayConditionAccepted GatewayConditionType = "Accepted"
|
|
|
|
|
|
|
|
// This reason is used with the "Accepted" condition when the condition is
|
|
|
|
// True.
|
|
|
|
GatewayReasonAccepted GatewayConditionReason = "Accepted"
|
|
|
|
|
|
|
|
// This reason is used with the "Accepted" condition when the gateway has multiple invalid
|
|
|
|
// certificates and cannot bind to any routes
|
|
|
|
GatewayReasonInvalidCertificates GatewayConditionReason = "InvalidCertificates"
|
|
|
|
|
2023-08-31 16:23:59 +00:00
|
|
|
// This reason is used with the "Accepted" condition when the gateway has multiple invalid
|
|
|
|
// JWT providers and cannot bind to any routes
|
|
|
|
GatewayReasonInvalidJWTProviders GatewayConditionReason = "InvalidJWTProviders"
|
|
|
|
|
2023-04-24 20:22:55 +00:00
|
|
|
// This condition indicates that the gateway was unable to resolve
|
|
|
|
// conflicting specification requirements for this Listener. If a
|
|
|
|
// Listener is conflicted, its network port should not be configured
|
|
|
|
// on any network elements.
|
|
|
|
//
|
|
|
|
// Possible reasons for this condition to be true are:
|
|
|
|
//
|
|
|
|
// * "RouteConflict"
|
|
|
|
//
|
|
|
|
// Possible reasons for this condition to be False are:
|
|
|
|
//
|
|
|
|
// * "NoConflict"
|
|
|
|
//
|
|
|
|
// Controllers may raise this condition with other reasons,
|
|
|
|
// but should prefer to use the reasons listed above to improve
|
|
|
|
// interoperability.
|
|
|
|
GatewayConditionConflicted GatewayConditionType = "Conflicted"
|
|
|
|
// This reason is used with the "Conflicted" condition when the condition
|
|
|
|
// is False.
|
|
|
|
GatewayReasonNoConflict GatewayConditionReason = "NoConflict"
|
|
|
|
// This reason is used with the "Conflicted" condition when the route is
|
|
|
|
// in a conflicted state, such as when a TCPListener attempts to bind to two routes
|
|
|
|
GatewayReasonRouteConflict GatewayConditionReason = "RouteConflict"
|
|
|
|
|
|
|
|
// This condition indicates whether the controller was able to
|
|
|
|
// resolve all the object references for the Gateway. When setting this
|
|
|
|
// condition to False, a ResourceReference to the misconfigured Listener should
|
|
|
|
// be provided.
|
|
|
|
//
|
|
|
|
// Possible reasons for this condition to be true are:
|
|
|
|
//
|
|
|
|
// * "ResolvedRefs"
|
|
|
|
//
|
|
|
|
// Possible reasons for this condition to be False are:
|
|
|
|
//
|
|
|
|
// * "InvalidCertificateRef"
|
|
|
|
// * "InvalidRouteKinds"
|
|
|
|
// * "RefNotPermitted"
|
|
|
|
//
|
|
|
|
GatewayConditionResolvedRefs GatewayConditionType = "ResolvedRefs"
|
|
|
|
|
|
|
|
// This reason is used with the "ResolvedRefs" condition when the condition
|
|
|
|
// is true.
|
|
|
|
GatewayReasonResolvedRefs GatewayConditionReason = "ResolvedRefs"
|
|
|
|
|
|
|
|
// This reason is used with the "ResolvedRefs" condition when a
|
|
|
|
// Listener has a TLS configuration with at least one TLS CertificateRef
|
|
|
|
// that is invalid or does not exist.
|
|
|
|
// A CertificateRef is considered invalid when it refers to a nonexistent
|
|
|
|
// or unsupported resource or kind, or when the data within that resource
|
|
|
|
// is malformed.
|
|
|
|
// This reason must be used only when the reference is allowed, either by
|
|
|
|
// referencing an object in the same namespace as the Gateway, or when
|
|
|
|
// a cross-namespace reference has been explicitly allowed by a ReferenceGrant.
|
|
|
|
// If the reference is not allowed, the reason RefNotPermitted must be used
|
|
|
|
// instead.
|
|
|
|
GatewayListenerReasonInvalidCertificateRef GatewayConditionReason = "InvalidCertificateRef"
|
2023-08-31 16:23:59 +00:00
|
|
|
|
|
|
|
// This reason is used with the "ResolvedRefs" condition when a
|
|
|
|
// Listener has a JWT configuration with at least one JWTProvider
|
|
|
|
// that is invalid or does not exist.
|
|
|
|
// A JWTProvider is considered invalid when it refers to a nonexistent
|
|
|
|
// or unsupported resource or kind, or when the data within that resource
|
|
|
|
// is malformed.
|
|
|
|
GatewayListenerReasonInvalidJWTProviderRef GatewayConditionReason = "InvalidJWTProviderRef"
|
2023-04-24 20:22:55 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var validGatewayConditionReasonsMapping = map[GatewayConditionType]map[ConditionStatus][]GatewayConditionReason{
|
|
|
|
GatewayConditionAccepted: {
|
|
|
|
ConditionStatusTrue: {
|
|
|
|
GatewayReasonAccepted,
|
|
|
|
},
|
|
|
|
ConditionStatusFalse: {
|
|
|
|
GatewayReasonInvalidCertificates,
|
2023-08-31 16:23:59 +00:00
|
|
|
GatewayReasonInvalidJWTProviders,
|
2023-04-24 20:22:55 +00:00
|
|
|
},
|
|
|
|
ConditionStatusUnknown: {},
|
|
|
|
},
|
|
|
|
GatewayConditionConflicted: {
|
|
|
|
ConditionStatusTrue: {
|
|
|
|
GatewayReasonRouteConflict,
|
|
|
|
},
|
|
|
|
ConditionStatusFalse: {
|
|
|
|
GatewayReasonNoConflict,
|
|
|
|
},
|
|
|
|
ConditionStatusUnknown: {},
|
|
|
|
},
|
|
|
|
GatewayConditionResolvedRefs: {
|
|
|
|
ConditionStatusTrue: {
|
|
|
|
GatewayReasonResolvedRefs,
|
|
|
|
},
|
|
|
|
ConditionStatusFalse: {
|
|
|
|
GatewayListenerReasonInvalidCertificateRef,
|
2023-08-31 16:23:59 +00:00
|
|
|
GatewayListenerReasonInvalidJWTProviderRef,
|
2023-04-24 20:22:55 +00:00
|
|
|
},
|
|
|
|
ConditionStatusUnknown: {},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func ValidateGatewayConditionReason(name GatewayConditionType, status ConditionStatus, reason GatewayConditionReason) error {
|
|
|
|
if err := checkConditionStatus(status); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
reasons, ok := validGatewayConditionReasonsMapping[name]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("unrecognized GatewayConditionType %q", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
reasonsForStatus, ok := reasons[status]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("unrecognized ConditionStatus %q", status)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !slices.Contains(reasonsForStatus, reason) {
|
|
|
|
return fmt.Errorf("gateway condition reason %q not allowed for gateway condition type %q with status %q", reason, name, status)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RouteConditionType is a type of condition for a route.
|
|
|
|
type RouteConditionType string
|
|
|
|
|
|
|
|
// RouteConditionReason is a reason for a route condition.
|
|
|
|
type RouteConditionReason string
|
|
|
|
|
|
|
|
// The following statuses are taken from the K8's Spec
|
|
|
|
// With the exception of: "RouteReasonInvalidDiscoveryChain" and "NoUpstreamServicesTargeted"
|
|
|
|
const (
|
|
|
|
// This condition indicates whether the route has been accepted or rejected
|
|
|
|
// by a Gateway, and why.
|
|
|
|
//
|
|
|
|
// Possible reasons for this condition to be true are:
|
|
|
|
//
|
|
|
|
// * "Accepted"
|
|
|
|
//
|
|
|
|
// Possible reasons for this condition to be False are:
|
|
|
|
//
|
|
|
|
// * "InvalidDiscoveryChain"
|
|
|
|
// * "NoUpstreamServicesTargeted"
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// Controllers may raise this condition with other reasons,
|
|
|
|
// but should prefer to use the reasons listed above to improve
|
|
|
|
// interoperability.
|
|
|
|
RouteConditionAccepted RouteConditionType = "Accepted"
|
|
|
|
|
|
|
|
// This reason is used with the "Accepted" condition when the Route has been
|
|
|
|
// accepted by the Gateway.
|
|
|
|
RouteReasonAccepted RouteConditionReason = "Accepted"
|
|
|
|
|
|
|
|
// This reason is used with the "Accepted" condition when the route has an
|
|
|
|
// invalid discovery chain, this includes conditions like the protocol being invalid
|
|
|
|
// or the discovery chain failing to compile
|
|
|
|
RouteReasonInvalidDiscoveryChain RouteConditionReason = "InvalidDiscoveryChain"
|
|
|
|
|
|
|
|
// This reason is used with the "Accepted" condition when the route
|
|
|
|
RouteReasonNoUpstreamServicesTargeted RouteConditionReason = "NoUpstreamServicesTargeted"
|
|
|
|
)
|
|
|
|
|
|
|
|
// the following statuses are custom to Consul
|
|
|
|
const (
|
|
|
|
// This condition indicates whether the route was able to successfully bind the
|
|
|
|
// Listener on the gateway
|
|
|
|
// Possible reasons for this condition to be true are:
|
|
|
|
//
|
|
|
|
// * "Bound"
|
|
|
|
//
|
|
|
|
// Possible reasons for this condition to be false are:
|
|
|
|
//
|
|
|
|
// * "FailedToBind"
|
|
|
|
// * "GatewayNotFound"
|
|
|
|
//
|
|
|
|
RouteConditionBound RouteConditionType = "Bound"
|
|
|
|
|
|
|
|
// This reason is used with the "Bound" condition when the condition
|
|
|
|
// is true
|
|
|
|
RouteReasonBound RouteConditionReason = "Bound"
|
|
|
|
|
|
|
|
// This reason is used with the "Bound" condition when the route failed
|
|
|
|
// to bind to the gateway
|
|
|
|
RouteReasonFailedToBind RouteConditionReason = "FailedToBind"
|
|
|
|
|
|
|
|
// This reason is used with the "Bound" condition when the route fails
|
|
|
|
// to find the gateway
|
|
|
|
RouteReasonGatewayNotFound RouteConditionReason = "GatewayNotFound"
|
2023-08-31 16:23:59 +00:00
|
|
|
|
|
|
|
// This reason is used with the "Accepted" condition when the route references non-existent
|
|
|
|
// JWTProviders
|
|
|
|
RouteReasonJWTProvidersNotFound RouteConditionReason = "JWTProvidersNotFound"
|
2023-04-24 20:22:55 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var validRouteConditionReasonsMapping = map[RouteConditionType]map[ConditionStatus][]RouteConditionReason{
|
|
|
|
RouteConditionAccepted: {
|
|
|
|
ConditionStatusTrue: {
|
|
|
|
RouteReasonAccepted,
|
|
|
|
},
|
|
|
|
ConditionStatusFalse: {
|
|
|
|
RouteReasonInvalidDiscoveryChain,
|
|
|
|
RouteReasonNoUpstreamServicesTargeted,
|
|
|
|
},
|
|
|
|
ConditionStatusUnknown: {},
|
|
|
|
},
|
|
|
|
RouteConditionBound: {
|
|
|
|
ConditionStatusTrue: {
|
|
|
|
RouteReasonBound,
|
|
|
|
},
|
|
|
|
ConditionStatusFalse: {
|
|
|
|
RouteReasonGatewayNotFound,
|
|
|
|
RouteReasonFailedToBind,
|
2023-08-31 16:23:59 +00:00
|
|
|
RouteReasonJWTProvidersNotFound,
|
2023-04-24 20:22:55 +00:00
|
|
|
},
|
|
|
|
ConditionStatusUnknown: {},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
func ValidateRouteConditionReason(name RouteConditionType, status ConditionStatus, reason RouteConditionReason) error {
|
|
|
|
if err := checkConditionStatus(status); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
reasons, ok := validRouteConditionReasonsMapping[name]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("unrecognized RouteConditionType %s", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
reasonsForStatus, ok := reasons[status]
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("unrecognized ConditionStatus %s", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !slices.Contains(reasonsForStatus, reason) {
|
|
|
|
return fmt.Errorf("route condition reason %s not allowed for route condition type %s with status %s", reason, name, status)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkConditionStatus(status ConditionStatus) error {
|
|
|
|
switch status {
|
|
|
|
case ConditionStatusTrue, ConditionStatusFalse, ConditionStatusUnknown:
|
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unrecognized condition status: %q", status)
|
|
|
|
}
|
|
|
|
}
|