github.com/nginxinc/kubernetes-ingress@v1.12.5/internal/k8s/secrets/validation.go (about)

     1  package secrets
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"encoding/pem"
     7  	"fmt"
     8  	"regexp"
     9  
    10  	api_v1 "k8s.io/api/core/v1"
    11  )
    12  
    13  // JWTKeyKey is the key of the data field of a Secret where the JWK must be stored.
    14  const JWTKeyKey = "jwk"
    15  
    16  // CAKey is the key of the data field of a Secret where the certificate authority must be stored.
    17  const CAKey = "ca.crt"
    18  
    19  // ClientSecretKey is the key of the data field of a Secret where the OIDC client secret must be stored.
    20  const ClientSecretKey = "client-secret"
    21  
    22  // SecretTypeCA contains a certificate authority for TLS certificate verification. #nosec G101
    23  const SecretTypeCA api_v1.SecretType = "nginx.org/ca"
    24  
    25  // SecretTypeJWK contains a JWK (JSON Web Key) for validating JWTs (JSON Web Tokens). #nosec G101
    26  const SecretTypeJWK api_v1.SecretType = "nginx.org/jwk"
    27  
    28  // SecretTypeOIDC contains an OIDC client secret for use in oauth flows. #nosec G101
    29  const SecretTypeOIDC api_v1.SecretType = "nginx.org/oidc"
    30  
    31  // ValidateTLSSecret validates the secret. If it is valid, the function returns nil.
    32  func ValidateTLSSecret(secret *api_v1.Secret) error {
    33  	if secret.Type != api_v1.SecretTypeTLS {
    34  		return fmt.Errorf("TLS Secret must be of the type %v", api_v1.SecretTypeTLS)
    35  	}
    36  
    37  	// Kubernetes ensures that 'tls.crt' and 'tls.key' are present for secrets of api_v1.SecretTypeTLS type
    38  
    39  	_, err := tls.X509KeyPair(secret.Data[api_v1.TLSCertKey], secret.Data[api_v1.TLSPrivateKeyKey])
    40  	if err != nil {
    41  		return fmt.Errorf("Failed to validate TLS cert and key: %w", err)
    42  	}
    43  
    44  	return nil
    45  }
    46  
    47  // ValidateJWKSecret validates the secret. If it is valid, the function returns nil.
    48  func ValidateJWKSecret(secret *api_v1.Secret) error {
    49  	if secret.Type != SecretTypeJWK {
    50  		return fmt.Errorf("JWK secret must be of the type %v", SecretTypeJWK)
    51  	}
    52  
    53  	if _, exists := secret.Data[JWTKeyKey]; !exists {
    54  		return fmt.Errorf("JWK secret must have the data field %v", JWTKeyKey)
    55  	}
    56  
    57  	// we don't validate the contents of secret.Data[JWTKeyKey], because invalid contents will not make NGINX Plus
    58  	// fail to reload: NGINX Plus will return 500 responses for the affected URLs.
    59  
    60  	return nil
    61  }
    62  
    63  // ValidateCASecret validates the secret. If it is valid, the function returns nil.
    64  func ValidateCASecret(secret *api_v1.Secret) error {
    65  	if secret.Type != SecretTypeCA {
    66  		return fmt.Errorf("CA secret must be of the type %v", SecretTypeCA)
    67  	}
    68  
    69  	if _, exists := secret.Data[CAKey]; !exists {
    70  		return fmt.Errorf("CA secret must have the data field %v", CAKey)
    71  	}
    72  
    73  	block, _ := pem.Decode(secret.Data[CAKey])
    74  	if block == nil {
    75  		return fmt.Errorf("The data field %s must hold a valid CERTIFICATE PEM block", CAKey)
    76  	}
    77  	if block.Type != "CERTIFICATE" {
    78  		return fmt.Errorf("The data field %s must hold a valid CERTIFICATE PEM block, but got '%s'", CAKey, block.Type)
    79  	}
    80  
    81  	_, err := x509.ParseCertificate(block.Bytes)
    82  	if err != nil {
    83  		return fmt.Errorf("Failed to validate certificate: %w", err)
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  // ValidateOIDCSecret validates the secret. If it is valid, the function returns nil.
    90  func ValidateOIDCSecret(secret *api_v1.Secret) error {
    91  	if secret.Type != SecretTypeOIDC {
    92  		return fmt.Errorf("OIDC secret must be of the type %v", SecretTypeOIDC)
    93  	}
    94  
    95  	clientSecret, exists := secret.Data[ClientSecretKey]
    96  	if !exists {
    97  		return fmt.Errorf("OIDC secret must have the data field %v", ClientSecretKey)
    98  	}
    99  
   100  	if msg, ok := isValidClientSecretValue(string(clientSecret)); !ok {
   101  		return fmt.Errorf("OIDC client secret is invalid: %s", msg)
   102  	}
   103  	return nil
   104  }
   105  
   106  // IsSupportedSecretType checks if the secret type is supported.
   107  func IsSupportedSecretType(secretType api_v1.SecretType) bool {
   108  	return secretType == api_v1.SecretTypeTLS ||
   109  		secretType == SecretTypeCA ||
   110  		secretType == SecretTypeJWK ||
   111  		secretType == SecretTypeOIDC
   112  }
   113  
   114  // ValidateSecret validates the secret. If it is valid, the function returns nil.
   115  func ValidateSecret(secret *api_v1.Secret) error {
   116  	switch secret.Type {
   117  	case api_v1.SecretTypeTLS:
   118  		return ValidateTLSSecret(secret)
   119  	case SecretTypeJWK:
   120  		return ValidateJWKSecret(secret)
   121  	case SecretTypeCA:
   122  		return ValidateCASecret(secret)
   123  	case SecretTypeOIDC:
   124  		return ValidateOIDCSecret(secret)
   125  	}
   126  
   127  	return fmt.Errorf("Secret is of the unsupported type %v", secret.Type)
   128  }
   129  
   130  var clientSecretValueFmtRegexp = regexp.MustCompile(`^([^"$\\\s]|\\[^$])*$`)
   131  
   132  func isValidClientSecretValue(s string) (string, bool) {
   133  	if ok := clientSecretValueFmtRegexp.MatchString(s); !ok {
   134  		return `It must contain valid ASCII characters, must have all '"' escaped and must not contain any '$' or whitespaces ('\n', '\t' etc.) or end with an unescaped '\'`, false
   135  	}
   136  	return "", true
   137  }