pkg/validations/extendedversion.go (120 lines of code) (raw):
package validations
import (
"context"
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
anywherev1 "github.com/aws/eks-anywhere/pkg/api/v1alpha1"
"github.com/aws/eks-anywhere/pkg/clients/kubernetes"
"github.com/aws/eks-anywhere/pkg/cluster"
"github.com/aws/eks-anywhere/pkg/constants"
"github.com/aws/eks-anywhere/pkg/semver"
"github.com/aws/eks-anywhere/pkg/signature"
"github.com/aws/eks-anywhere/release/api/v1alpha1"
)
// LicensePublicKey is the public key for verifying license token signature.
// this is injected at build time.
var LicensePublicKey string
// ValidateExtendedK8sVersionSupport validates all the validations needed for the support of extended kubernetes support.
func ValidateExtendedK8sVersionSupport(ctx context.Context, clusterSpec anywherev1.Cluster, bundle *v1alpha1.Bundles, k kubernetes.Client) error {
// Validate EKS-A bundle has not been modified by verifying the signature in the bundle annotation
if err := validateBundleSignature(bundle); err != nil {
return fmt.Errorf("validating bundle signature: %w", err)
}
// Check whether the kubernetes version for the cluster is currently under extended support by comparing the endOfStandardSupport date from the bundle with the current date.
isExtended, err := isExtendedSupport(clusterSpec.Spec.KubernetesVersion, bundle)
if err != nil {
return err
}
if isExtended {
token, err := getLicense(clusterSpec.Spec.LicenseToken)
if err != nil {
return fmt.Errorf("getting licenseToken: %w", err)
}
if err = validateLicense(token); err != nil {
return fmt.Errorf("validating licenseToken: %w", err)
}
if clusterSpec.IsManaged() {
if err := validateLicenseKeyIsUnique(ctx, clusterSpec.Name, clusterSpec.Spec.LicenseToken, k); err != nil {
return fmt.Errorf("validating licenseToken is unique for cluster %s: %w", clusterSpec.Name, err)
}
}
}
return nil
}
// validateBundleSignature validates bundles signature with the KMS public key.
func validateBundleSignature(bundle *v1alpha1.Bundles) error {
valid, err := signature.ValidateSignature(bundle, constants.KMSPublicKey)
if err != nil {
return err
}
if !valid {
return errors.New("signature on the bundle is invalid")
}
return nil
}
func isExtendedSupport(kubernetesVersion anywherev1.KubernetesVersion, bundle *v1alpha1.Bundles) (bool, error) {
versionsBundle, err := cluster.GetVersionsBundle(kubernetesVersion, bundle)
if err != nil {
return false, fmt.Errorf("getting versions bundle for %s kubernetes version: %w", kubernetesVersion, err)
}
endOfStandardSupport, err := time.Parse("2006-01-02", versionsBundle.EndOfStandardSupport)
if err != nil {
return false, fmt.Errorf("parsing EndOfStandardSupport field format: %w", err)
}
return isPastDateThanToday(endOfStandardSupport), nil
}
func getLicense(licenseToken string) (*jwt.Token, error) {
if licenseToken == "" {
return nil, errors.New("licenseToken is required for extended kubernetes support")
}
token, err := signature.ParseLicense(licenseToken, LicensePublicKey)
if err != nil {
return nil, fmt.Errorf("parsing licenseToken: %w", err)
}
return token, nil
}
func validateLicense(token *jwt.Token) error {
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return errors.New("could not parse the licenseToken claims")
}
endValidity, ok := claims["endValidity"].(string)
if !ok {
return errors.New("license validity field missing from the licenseToken, not a valid license")
}
validity, err := time.Parse(time.RFC3339, endValidity)
if err != nil {
return fmt.Errorf("parsing endValidity field from licenseToken: %w", err)
}
if isPastDateThanToday(validity) {
return errors.New("license is expired, please renew the license")
}
return nil
}
func isPastDateThanToday(dateToCompare time.Time) bool {
today := time.Now().Truncate(24 * time.Hour)
return dateToCompare.Before(today)
}
func validateLicenseKeyIsUnique(ctx context.Context, clusterName string, licenseToken string, k kubernetes.Client) error {
eksaClusters := &anywherev1.ClusterList{}
err := k.List(ctx, eksaClusters, kubernetes.ListOptions{})
if err != nil {
return fmt.Errorf("listing all EKS-A clusters: %w", err)
}
for _, eksaCluster := range eksaClusters.Items {
if eksaCluster.Name != clusterName && eksaCluster.Spec.LicenseToken == licenseToken {
return fmt.Errorf("license token %s is already in use by cluster %s", licenseToken, eksaCluster.Name)
}
}
return nil
}
// ShouldSkipBundleSignatureValidation returns true if the eksa version is less than v0.22.0
// and false otherwise. This is to skip signature validation for older eksa versions.
func ShouldSkipBundleSignatureValidation(eksaVersion *string) (bool, error) {
if eksaVersion == nil {
return true, nil
}
eksaVersionSemver, err := semver.New(string(*eksaVersion))
if err != nil {
return false, fmt.Errorf("getting semver for current eksa version %s: %v", *eksaVersion, err)
}
// v0.22.0 is the first version to support extended kubernetes support
semverV022, err := semver.New(releaseV022)
if err != nil {
return false, fmt.Errorf("parsing eks-a version: %v", err)
}
return eksaVersionSemver.Compare(semverV022) == -1, nil
}