knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/duck/v1beta1/destination.go (about)

     1  /*
     2  Copyright 2019 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package v1beta1
    18  
    19  import (
    20  	"context"
    21  	"crypto/x509"
    22  	"encoding/pem"
    23  
    24  	corev1 "k8s.io/api/core/v1"
    25  	"knative.dev/pkg/apis"
    26  )
    27  
    28  // Destination represents a target of an invocation over HTTP.
    29  type Destination struct {
    30  	// Ref points to an Addressable.
    31  	// +optional
    32  	Ref *corev1.ObjectReference `json:"ref,omitempty"`
    33  
    34  	// +optional
    35  	DeprecatedAPIVersion string `json:"apiVersion,omitempty"`
    36  
    37  	// +optional
    38  	DeprecatedKind string `json:"kind,omitempty"`
    39  
    40  	// +optional
    41  	DeprecatedName string `json:"name,omitempty"`
    42  
    43  	// +optional
    44  	DeprecatedNamespace string `json:"namespace,omitempty"`
    45  
    46  	// URI can be an absolute URL(non-empty scheme and non-empty host) pointing to the target or a relative URI. Relative URIs will be resolved using the base URI retrieved from Ref.
    47  	// +optional
    48  	URI *apis.URL `json:"uri,omitempty"`
    49  
    50  	// CACerts are Certification Authority (CA) certificates in PEM format
    51  	// according to https://www.rfc-editor.org/rfc/rfc7468.
    52  	// If set, these CAs are appended to the set of CAs provided
    53  	// by the Addressable target, if any.
    54  	// +optional
    55  	CACerts *string `json:"CACerts,omitempty"`
    56  }
    57  
    58  func (dest *Destination) Validate(ctx context.Context) *apis.FieldError {
    59  	if dest == nil {
    60  		return nil
    61  	}
    62  	return ValidateDestination(*dest, true).ViaField(apis.CurrentField)
    63  }
    64  
    65  func (dest *Destination) ValidateDisallowDeprecated(ctx context.Context) *apis.FieldError {
    66  	if dest == nil {
    67  		return nil
    68  	}
    69  	return ValidateDestination(*dest, false).ViaField(apis.CurrentField)
    70  }
    71  
    72  // ValidateDestination validates Destination and either allows or disallows
    73  // Deprecated* fields depending on the flag.
    74  func ValidateDestination(dest Destination, allowDeprecatedFields bool) *apis.FieldError {
    75  	if !allowDeprecatedFields {
    76  		var errs *apis.FieldError
    77  		if dest.DeprecatedAPIVersion != "" {
    78  			errs = errs.Also(apis.ErrInvalidValue("apiVersion is not allowed here, it's a deprecated value", "apiVersion"))
    79  		}
    80  		if dest.DeprecatedKind != "" {
    81  			errs = errs.Also(apis.ErrInvalidValue("kind is not allowed here, it's a deprecated value", "kind"))
    82  		}
    83  		if dest.DeprecatedName != "" {
    84  			errs = errs.Also(apis.ErrInvalidValue("name is not allowed here, it's a deprecated value", "name"))
    85  		}
    86  		if dest.DeprecatedNamespace != "" {
    87  			errs = errs.Also(apis.ErrInvalidValue("namespace is not allowed here, it's a deprecated value", "namespace"))
    88  		}
    89  		if errs != nil {
    90  			return errs
    91  		}
    92  	}
    93  
    94  	deprecatedObjectReference := dest.deprecatedObjectReference()
    95  	if dest.Ref != nil && deprecatedObjectReference != nil {
    96  		return apis.ErrGeneric("Ref and [apiVersion, kind, name] can't be both present", "[apiVersion, kind, name]", "ref")
    97  	}
    98  
    99  	var ref *corev1.ObjectReference
   100  	if dest.Ref != nil {
   101  		ref = dest.Ref
   102  	} else {
   103  		ref = deprecatedObjectReference
   104  	}
   105  	if ref == nil && dest.URI == nil {
   106  		return apis.ErrGeneric("expected at least one, got none", "[apiVersion, kind, name]", "ref", "uri")
   107  	}
   108  
   109  	if ref != nil && dest.URI != nil && dest.URI.URL().IsAbs() {
   110  		return apis.ErrGeneric("Absolute URI is not allowed when Ref or [apiVersion, kind, name] is present", "[apiVersion, kind, name]", "ref", "uri")
   111  	}
   112  	// IsAbs() check whether the URL has a non-empty scheme. Besides the non-empty scheme, we also require dest.URI has a non-empty host
   113  	if ref == nil && dest.URI != nil && (!dest.URI.URL().IsAbs() || dest.URI.Host == "") {
   114  		return apis.ErrInvalidValue("Relative URI is not allowed when Ref and [apiVersion, kind, name] is absent", "uri")
   115  	}
   116  	if ref != nil && dest.URI == nil {
   117  		if dest.Ref != nil {
   118  			return validateDestinationRef(*ref).ViaField("ref")
   119  		}
   120  		return validateDestinationRef(*ref)
   121  	}
   122  	if dest.CACerts != nil {
   123  		return validateCACerts(dest.CACerts)
   124  	}
   125  	return nil
   126  }
   127  
   128  func (dest Destination) deprecatedObjectReference() *corev1.ObjectReference {
   129  	if dest.DeprecatedAPIVersion == "" && dest.DeprecatedKind == "" && dest.DeprecatedName == "" && dest.DeprecatedNamespace == "" {
   130  		return nil
   131  	}
   132  	return &corev1.ObjectReference{
   133  		Kind:       dest.DeprecatedKind,
   134  		APIVersion: dest.DeprecatedAPIVersion,
   135  		Name:       dest.DeprecatedName,
   136  		Namespace:  dest.DeprecatedNamespace,
   137  	}
   138  }
   139  
   140  // GetRef gets the ObjectReference from this Destination, if one is present. If no ref is present,
   141  // then nil is returned.
   142  // Note: this mostly exists to abstract away the deprecated ObjectReference fields. Once they are
   143  // removed, then this method should probably be removed too.
   144  func (dest *Destination) GetRef() *corev1.ObjectReference {
   145  	if dest == nil {
   146  		return nil
   147  	}
   148  	if dest.Ref != nil {
   149  		return dest.Ref
   150  	}
   151  	return dest.deprecatedObjectReference()
   152  }
   153  
   154  func validateDestinationRef(ref corev1.ObjectReference) *apis.FieldError {
   155  	// Check the object.
   156  	var errs *apis.FieldError
   157  	// Required Fields
   158  	if ref.Name == "" {
   159  		errs = errs.Also(apis.ErrMissingField("name"))
   160  	}
   161  	if ref.APIVersion == "" {
   162  		errs = errs.Also(apis.ErrMissingField("apiVersion"))
   163  	}
   164  	if ref.Kind == "" {
   165  		errs = errs.Also(apis.ErrMissingField("kind"))
   166  	}
   167  
   168  	return errs
   169  }
   170  
   171  func validateCACerts(caCert *string) *apis.FieldError {
   172  	// Check the object.
   173  	var errs *apis.FieldError
   174  
   175  	block, err := pem.Decode([]byte(*caCert))
   176  	if err != nil && block == nil {
   177  		errs = errs.Also(apis.ErrInvalidValue("CA Cert provided is invalid", "caCert"))
   178  		return errs
   179  	}
   180  	if block.Type != "CERTIFICATE" {
   181  		errs = errs.Also(apis.ErrInvalidValue("CA Cert provided is not a certificate", "caCert"))
   182  	} else if _, err := x509.ParseCertificate(block.Bytes); err != nil {
   183  		errs = errs.Also(apis.ErrInvalidValue("CA Cert provided is invalid", "caCert"))
   184  	}
   185  	return errs
   186  }