...

Source file src/edge-infra.dev/pkg/edge/controllers/bannerctl/encryption_infra.go

Documentation: edge-infra.dev/pkg/edge/controllers/bannerctl

     1  package bannerctl
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"time"
    10  
    11  	"github.com/google/uuid"
    12  	"google.golang.org/grpc/codes"
    13  	"google.golang.org/grpc/status"
    14  
    15  	kms "cloud.google.com/go/kms/apiv1"
    16  	"cloud.google.com/go/kms/apiv1/kmspb"
    17  	"github.com/go-logr/logr"
    18  	"github.com/golang-jwt/jwt"
    19  	ctrl "sigs.k8s.io/controller-runtime"
    20  	"sigs.k8s.io/controller-runtime/pkg/client"
    21  
    22  	"edge-infra.dev/pkg/edge/api/services/channels"
    23  	bannerAPI "edge-infra.dev/pkg/edge/apis/banner/v1alpha1"
    24  	"edge-infra.dev/pkg/edge/edgeencrypt"
    25  	"edge-infra.dev/pkg/k8s/runtime/controller/reconcile"
    26  	"edge-infra.dev/pkg/k8s/runtime/controller/reconcile/recerr"
    27  )
    28  
    29  type EncryptionInfraReconciler struct {
    30  	client.Client
    31  	Log logr.Logger
    32  
    33  	Conditions       reconcile.Conditions
    34  	ForemanProjectID string
    35  	GCPRegion        string
    36  	SecretManager    secretManager
    37  	KmsClient        *kms.KeyManagementClient
    38  	kmsKey           edgeencrypt.KmsKey
    39  	SigningMethod    jwt.SigningMethod
    40  	ChannelService   channels.Service
    41  
    42  	IntervalTime    time.Duration
    43  	RequeueTime     time.Duration
    44  	ResourceTimeout time.Duration
    45  }
    46  
    47  func (r *EncryptionInfraReconciler) SetupWithManager(mgr ctrl.Manager) error {
    48  	r.kmsKey = edgeencrypt.KmsKey{ProjectID: r.ForemanProjectID, Location: r.GCPRegion}
    49  	return ctrl.NewControllerManagedBy(mgr).
    50  		For(&bannerAPI.Banner{}).
    51  		Complete(r)
    52  }
    53  
    54  func (r *EncryptionInfraReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, recErr error) { //nolint:dupl
    55  	banner := &bannerAPI.Banner{}
    56  	if err := r.Get(ctx, req.NamespacedName, banner); err != nil {
    57  		return ctrl.Result{}, client.IgnoreNotFound(err)
    58  	}
    59  	log := ctrl.Log.WithValues("banner", banner.Name)
    60  
    61  	// Get the list of channels for the banner
    62  	bcs, err := getBannerChannels(ctx, r.ChannelService, banner.Name)
    63  	if err != nil {
    64  		return ctrl.Result{RequeueAfter: r.IntervalTime}, nil
    65  	}
    66  	if len(bcs) == 0 {
    67  		return ctrl.Result{RequeueAfter: r.RequeueTime}, nil
    68  	}
    69  
    70  	recErr = r.reconcile(ctx, banner, bcs)
    71  	if recErr != nil {
    72  		log.Error(recErr, "failed to reconcile")
    73  		return ctrl.Result{RequeueAfter: r.IntervalTime}, nil
    74  	}
    75  
    76  	return ctrl.Result{RequeueAfter: r.RequeueTime}, nil
    77  }
    78  
    79  func (r *EncryptionInfraReconciler) reconcile(ctx context.Context, banner *bannerAPI.Banner, channels []channels.BannerChannel) recerr.Error {
    80  	secretClient, err := r.SecretManager.NewWithOptions(ctx, banner.Spec.GCP.ProjectID)
    81  	if err != nil {
    82  		return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
    83  	}
    84  	//defer secretClient.Close()
    85  
    86  	// create key ring per bearer
    87  	err = r.createKeyRing(ctx, banner.Name)
    88  	if err != nil {
    89  		return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
    90  	}
    91  
    92  	// create encryption jwt signing key per banner
    93  	err = r.createKMSSigningKey(ctx, secretClient, banner, edgeencrypt.EncryptionJWTSecret, edgeencrypt.EncryptionJWTSecretManager,
    94  		map[string]string{"banner": banner.Name})
    95  	if err != nil {
    96  		return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
    97  	}
    98  
    99  	bannerEdgeID, err := uuid.Parse(banner.Name)
   100  	if err != nil {
   101  		return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
   102  	}
   103  
   104  	for _, channel := range channels {
   105  		// create encryption key for data per channel per banner
   106  		err = r.createKMSEncryptionKey(ctx, secretClient, banner,
   107  			fmt.Sprintf(edgeencrypt.EncryptionSecret, channel.ID),
   108  			fmt.Sprintf(edgeencrypt.EncryptionSecretManager, channel.ID),
   109  			map[string]string{"channel": channel.Name},
   110  		)
   111  		if err != nil {
   112  			return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
   113  		}
   114  
   115  		// save channel key version to db
   116  		err = r.CreateChannelKeyVersion(ctx, bannerEdgeID, channel.ID, banner.Spec.GCP.ProjectID,
   117  			fmt.Sprintf(edgeencrypt.EncryptionSecretManager, channel.ID))
   118  		if err != nil {
   119  			return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
   120  		}
   121  
   122  		// create encryption bearer token for workloads per channel per banner
   123  		err = r.createBearerToken(ctx, secretClient, banner, edgeencrypt.EncryptionJWTSecret, channel.ID.String(),
   124  			channel.Name, fmt.Sprintf(edgeencrypt.EncryptionTokenSecretManager, channel.ID))
   125  		if err != nil {
   126  			return recerr.New(err, bannerAPI.PlatformSecretsCreationFailedReason)
   127  		}
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  // createKeyRing create key ring to store encryption keys
   134  func (r *EncryptionInfraReconciler) createKeyRing(ctx context.Context, bannerEdgeID string) error {
   135  	return createKeyRing(ctx, r.KmsClient, r.kmsKey, bannerEdgeID)
   136  }
   137  
   138  // createKeyRing create key ring to store encryption keys
   139  func createKeyRing(ctx context.Context, kmsClient *kms.KeyManagementClient, kmsKey edgeencrypt.KmsKey, ring string) error {
   140  	// check if key ring exists
   141  	_, err := kmsClient.GetKeyRing(ctx, &kmspb.GetKeyRingRequest{Name: kmsKey.Ring(ring)})
   142  	if err != nil && status.Code(err) == codes.NotFound {
   143  		_, err = kmsClient.CreateKeyRing(ctx, &kmspb.CreateKeyRingRequest{
   144  			Parent:    kmsKey.RingParent(),
   145  			KeyRingId: ring,
   146  		})
   147  	}
   148  	if err != nil {
   149  		return fmt.Errorf("failed to create key ring: %w", err)
   150  	}
   151  	return nil
   152  }
   153  
   154  // createKMSSigningKey create encryption jwt signing key
   155  func (r *EncryptionInfraReconciler) createKMSSigningKey(ctx context.Context, secretClient secretManagerClient, banner *bannerAPI.Banner, channel, secretID string, labels map[string]string) error {
   156  	return createKmsKey(ctx, r.KmsClient, secretClient, r.kmsKey, banner.Name, channel, labels, edgeencrypt.DefaultSigningCryptoKeyPurpose, edgeencrypt.DefaultSigningCryptoKeyAlgorithm, secretID, r.ResourceTimeout)
   157  }
   158  
   159  // createKMSEncryptionKey create encryption key
   160  func (r *EncryptionInfraReconciler) createKMSEncryptionKey(ctx context.Context, secretClient secretManagerClient, banner *bannerAPI.Banner, channel, secretID string, labels map[string]string) error {
   161  	return createKmsKey(ctx, r.KmsClient, secretClient, r.kmsKey, banner.Name, channel, labels, edgeencrypt.DefaultCryptoKeyPurpose, edgeencrypt.DefaultCryptoKeyAlgorithm, secretID, r.ResourceTimeout)
   162  }
   163  
   164  func createKmsKey(ctx context.Context, kmsClient *kms.KeyManagementClient, secretClient secretManagerClient, kmsKey edgeencrypt.KmsKey, ring, key string, labels map[string]string,
   165  	purpose kmspb.CryptoKey_CryptoKeyPurpose, algorithm kmspb.CryptoKeyVersion_CryptoKeyVersionAlgorithm, secretID string, timeout time.Duration) error {
   166  	_, err := secretClient.GetSecret(ctx, secretID)
   167  	if err == nil {
   168  		// secret already exists, rotate will take care of the rest
   169  		return nil
   170  	} else if status.Code(err) != codes.NotFound {
   171  		return fmt.Errorf("failed to get secret from secret manager: %w", err)
   172  	}
   173  
   174  	keyPath := kmsKey.KeyPath(ring, key, "1")
   175  
   176  	pubKey, err := kmsClient.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: keyPath})
   177  	if err != nil && status.Code(err) == codes.NotFound {
   178  		_, err = kmsClient.CreateCryptoKey(ctx, &kmspb.CreateCryptoKeyRequest{
   179  			Parent:      kmsKey.Ring(ring),
   180  			CryptoKeyId: key,
   181  			CryptoKey: &kmspb.CryptoKey{
   182  				Purpose:         purpose,
   183  				VersionTemplate: &kmspb.CryptoKeyVersionTemplate{Algorithm: algorithm},
   184  				Labels:          labels,
   185  			},
   186  		})
   187  		if err != nil {
   188  			return fmt.Errorf("failed to create key ring: %w", err)
   189  		}
   190  		// get public key
   191  		pubKey, err = getPublicKeyWithRetry(ctx, kmsClient, keyPath, timeout)
   192  	}
   193  	if err != nil {
   194  		return fmt.Errorf("failed to get public key from kms: %w", err)
   195  	}
   196  	pk, err := edgeencrypt.NewPublicKey(pubKey.Pem, "1")
   197  	if err != nil {
   198  		return fmt.Errorf("failed to create public key: %w", err)
   199  	}
   200  	data, err := json.Marshal(pk)
   201  	if err != nil {
   202  		return fmt.Errorf("failed to marshal public key: %w", err)
   203  	}
   204  	// save public key to secret manager
   205  	return saveToSecretManager(ctx, secretClient, secretID, data, 1, labels)
   206  }
   207  
   208  // createBearerToken create encryption bearer token
   209  func (r *EncryptionInfraReconciler) createBearerToken(ctx context.Context, secretClient secretManagerClient, banner *bannerAPI.Banner, keyName, channelID, channelName, secretID string) error {
   210  	key := r.kmsKey.KeyPath(banner.Name, keyName, "1")
   211  
   212  	return createBearerToken(ctx, secretClient, r.SigningMethod, key, channelID, channelName, secretID, edgeencrypt.Encryption, 1,
   213  		map[string]string{"channel": channelName}, banner.Name)
   214  }
   215  
   216  func createBearerToken(ctx context.Context, secretClient secretManagerClient, sm jwt.SigningMethod, key string, channelID, channelName, secretID string,
   217  	role edgeencrypt.Role, version int, labels map[string]string, banner ...string) error {
   218  	_, err := secretClient.GetSecret(ctx, secretID)
   219  	if err == nil {
   220  		// secret already exists, rotate will take care of the rest
   221  		return nil
   222  	} else if status.Code(err) != codes.NotFound {
   223  		return fmt.Errorf("failed to get secret from secret manager: %w", err)
   224  	}
   225  
   226  	token, err := edgeencrypt.CreateToken(sm, key, edgeencrypt.DefaultDuration, channelID, channelName, role, banner...)
   227  	if err != nil {
   228  		return fmt.Errorf("failed to create bearer token: %w", err)
   229  	}
   230  	t := &edgeencrypt.Token{BearerToken: token, Version: "1"}
   231  	data, err := json.Marshal(t)
   232  	if err != nil {
   233  		return fmt.Errorf("failed to marshal bearer token to json: %w", err)
   234  	}
   235  	return saveToSecretManager(ctx, secretClient, secretID, data, version, labels)
   236  }
   237  
   238  func saveToSecretManager(ctx context.Context, secretClient secretManagerClient, secretID string, data []byte, _ int, labels map[string]string) error {
   239  	// TODO fix version alias fmt.Sprintf("%d", version)
   240  	err := secretClient.AddSecret(ctx, secretID, data, labels, false, nil, "")
   241  	if err != nil {
   242  		return fmt.Errorf("failed to add secret to secret manager: %w", err)
   243  	}
   244  	return nil
   245  }
   246  
   247  func (r *EncryptionInfraReconciler) CreateChannelKeyVersion(ctx context.Context, bannerEdgeID, channelID uuid.UUID, project, secretID string) error {
   248  	ck := channels.ChannelKeyVersion{
   249  		ChannelID:         channelID,
   250  		BannerEdgeID:      bannerEdgeID,
   251  		Version:           1,
   252  		SecretManagerLink: secretManagerLink(project, secretID),
   253  	}
   254  	return saveChannelKeyVersion(ctx, r.ChannelService, bannerEdgeID, channelID, ck)
   255  }
   256  
   257  func getPublicKeyWithRetry(ctx context.Context, kmsClient *kms.KeyManagementClient, key string, timeout time.Duration) (*kmspb.PublicKey, error) {
   258  	var (
   259  		result *kmspb.PublicKey
   260  		err    error
   261  	)
   262  	for {
   263  		select {
   264  		case <-time.After(timeout):
   265  			if err != nil {
   266  				return nil, err
   267  			}
   268  			return nil, fmt.Errorf("timeout waiting for key %s to be ready", key)
   269  		default:
   270  			result, err = kmsClient.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: key})
   271  			if err == nil {
   272  				return result, nil
   273  			}
   274  			time.Sleep(2 * time.Second)
   275  		}
   276  	}
   277  }
   278  
   279  func saveChannelKeyVersion(ctx context.Context, cs channels.Service, bannerEdgeID, channelID uuid.UUID, ckv channels.ChannelKeyVersion) error {
   280  	_, err := cs.GetLatestChannelKeyVersion(ctx, bannerEdgeID, channelID)
   281  	if err != nil {
   282  		if !errors.Is(err, sql.ErrNoRows) {
   283  			return fmt.Errorf("failed to get latest channel key version: %w", err)
   284  		}
   285  
   286  		_, err = cs.CreateChannelKeyVersion(ctx, ckv)
   287  		if err != nil {
   288  			return fmt.Errorf("failed to create channel key version: %w", err)
   289  		}
   290  	}
   291  	return nil
   292  }
   293  
   294  func secretManagerLink(project, secretID string) string {
   295  	return fmt.Sprintf("projects/%s/secrets/%s", project, secretID)
   296  }
   297  
   298  func getBannerChannels(ctx context.Context, cs channels.Service, bannerEdgeID string) ([]channels.BannerChannel, error) {
   299  	bannerID, err := uuid.Parse(bannerEdgeID)
   300  	if err != nil {
   301  		return nil, fmt.Errorf("failed to parse banner edge id: %w", err)
   302  	}
   303  
   304  	bcs, err := cs.GetBannerChannels(ctx, bannerID)
   305  	if err != nil {
   306  		return nil, fmt.Errorf("failed to get banner channels: %w", err)
   307  	}
   308  	return bcs, nil
   309  }
   310  

View as plain text