knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/apis/duck/v1/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 v1
    18  
    19  import (
    20  	"context"
    21  	"crypto/x509"
    22  	"encoding/pem"
    23  
    24  	"knative.dev/pkg/apis"
    25  )
    26  
    27  // Destination represents a target of an invocation over HTTP.
    28  type Destination struct {
    29  	// Ref points to an Addressable.
    30  	// +optional
    31  	Ref *KReference `json:"ref,omitempty"`
    32  
    33  	// 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.
    34  	// +optional
    35  	URI *apis.URL `json:"uri,omitempty"`
    36  
    37  	// CACerts are Certification Authority (CA) certificates in PEM format
    38  	// according to https://www.rfc-editor.org/rfc/rfc7468.
    39  	// If set, these CAs are appended to the set of CAs provided
    40  	// by the Addressable target, if any.
    41  	// +optional
    42  	CACerts *string `json:"CACerts,omitempty"`
    43  
    44  	// Audience is the OIDC audience.
    45  	// This need only be set, if the target is not an Addressable
    46  	// and thus the Audience can't be received from the Addressable itself.
    47  	// In case the Addressable specifies an Audience too, the Destinations
    48  	// Audience takes preference.
    49  	// +optional
    50  	Audience *string `json:"audience,omitempty"`
    51  }
    52  
    53  // Validate the Destination has all the necessary fields and check the
    54  // Namespace matches that of the parent object (using apis.ParentMeta).
    55  func (d *Destination) Validate(ctx context.Context) *apis.FieldError {
    56  	if d == nil {
    57  		return nil
    58  	}
    59  	return ValidateDestination(ctx, *d).ViaField(apis.CurrentField)
    60  }
    61  
    62  // ValidateDestination validates Destination.
    63  func ValidateDestination(ctx context.Context, dest Destination) *apis.FieldError {
    64  	ref := dest.Ref
    65  	uri := dest.URI
    66  	caCerts := dest.CACerts
    67  	if ref == nil && uri == nil {
    68  		return apis.ErrGeneric("expected at least one, got none", "ref", "uri")
    69  	}
    70  
    71  	if ref != nil && uri != nil && uri.URL().IsAbs() {
    72  		return apis.ErrGeneric("Absolute URI is not allowed when Ref or [apiVersion, kind, name] is present", "[apiVersion, kind, name]", "ref", "uri")
    73  	}
    74  	// IsAbs() check whether the URL has a non-empty scheme. Besides the non-empty scheme, we also require uri has a non-empty host
    75  	if ref == nil && uri != nil && (!uri.URL().IsAbs() || uri.Host == "") {
    76  		return apis.ErrInvalidValue("Relative URI is not allowed when Ref and [apiVersion, kind, name] is absent", "uri")
    77  	}
    78  	if ref != nil && uri == nil {
    79  		return ref.Validate(ctx).ViaField("ref")
    80  	}
    81  	if caCerts != nil {
    82  		return validateCACerts(caCerts)
    83  	}
    84  	return nil
    85  }
    86  
    87  // GetRef gets the KReference from this Destination, if one is present. If no ref is present,
    88  // then nil is returned.
    89  func (d *Destination) GetRef() *KReference {
    90  	if d == nil {
    91  		return nil
    92  	}
    93  	return d.Ref
    94  }
    95  
    96  func (d *Destination) SetDefaults(ctx context.Context) {
    97  	if d == nil {
    98  		return
    99  	}
   100  
   101  	if d.Ref != nil && d.Ref.Namespace == "" {
   102  		d.Ref.Namespace = apis.ParentMeta(ctx).Namespace
   103  	}
   104  }
   105  
   106  func validateCACerts(caCert *string) *apis.FieldError {
   107  	// Check the object.
   108  	var errs *apis.FieldError
   109  
   110  	block, err := pem.Decode([]byte(*caCert))
   111  	if err != nil && block == nil {
   112  		errs = errs.Also(apis.ErrInvalidValue("CA Cert provided is invalid", "caCert"))
   113  		return errs
   114  	}
   115  	if block.Type != "CERTIFICATE" {
   116  		errs = errs.Also(apis.ErrInvalidValue("CA Cert provided is not a certificate", "caCert"))
   117  	} else if _, err := x509.ParseCertificate(block.Bytes); err != nil {
   118  		errs = errs.Also(apis.ErrInvalidValue("CA Cert provided is invalid", "caCert"))
   119  	}
   120  	return errs
   121  }