...

Source file src/edge-infra.dev/pkg/edge/api/services/clustersecrets/lease.go

Documentation: edge-infra.dev/pkg/edge/api/services/clustersecrets

     1  package clustersecrets
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"errors"
     7  	"time"
     8  
     9  	"github.com/google/uuid"
    10  
    11  	sqlerr "edge-infra.dev/pkg/edge/api/apierror/sql"
    12  	"edge-infra.dev/pkg/edge/api/graph/model"
    13  	"edge-infra.dev/pkg/edge/api/middleware"
    14  )
    15  
    16  var (
    17  	// ErrLeaseAlreadyInUse is thrown if the cluster secret lease already has a lease owner
    18  	ErrLeaseAlreadyInUse = errors.New("lease is already owned by another user")
    19  	// ErrUserDoesNotOwnLease is thrown if the current user attempting to release the lease does not have
    20  	// ownership over the cluster secret lease
    21  	ErrUserDoesNotOwnLease = errors.New("user does not own the lease")
    22  	// ErrCannotRevokeUser is thrown if a lease is being revoked but user to be revoked does not own the lease
    23  	ErrCannotRevokeUser = errors.New("cannot revoke user from lease as they do not own it")
    24  	// ErrFailedToUpdateDatabase is thrown if no rows are returned from an SQL query where we expect rows to be updated
    25  	ErrFailedToUpdateDatabase = errors.New("failed to update database")
    26  )
    27  
    28  // Obtain lease will attempt to obtain the cluster secret lease
    29  func (s *clusterSecretService) ObtainLease(ctx context.Context, clusterEdgeID string) (bool, error) {
    30  	lease, err := s.FetchLease(ctx, clusterEdgeID)
    31  	if err != nil {
    32  		return false, err
    33  	}
    34  	currentLeaseExpirationTime, err := time.Parse(time.RFC3339, lease.ExpiresAt)
    35  	if err != nil {
    36  		return false, err
    37  	}
    38  	user := middleware.ForContext(ctx)
    39  	currentTime := time.Now().UTC()
    40  	if currentLeaseExpirationTime.After(currentTime) {
    41  		if lease.Owner == user.Username {
    42  			// user already owns the lease so no need to obtain the lease
    43  			return false, nil
    44  		}
    45  		return false, ErrLeaseAlreadyInUse
    46  	}
    47  
    48  	maxLeaseValidityPeriod, err := time.ParseDuration(s.Config.EdgeMaxLeaseValidityPeriod)
    49  	if err != nil {
    50  		return false, err
    51  	}
    52  	newExpirationTime := currentTime.Add(maxLeaseValidityPeriod).Format(time.RFC3339)
    53  	result, err := s.SQLDB.ExecContext(ctx, ObtainLeaseQuery, newExpirationTime, user.Username, currentTime.Format(time.RFC3339), clusterEdgeID)
    54  	if err != nil {
    55  		return false, err
    56  	}
    57  	rows, err := result.RowsAffected()
    58  	if err != nil {
    59  		return false, err
    60  	}
    61  	if rows == 0 {
    62  		return false, ErrFailedToUpdateDatabase
    63  	}
    64  	if lease.Owner != "" {
    65  		return true, nil
    66  	}
    67  	return false, nil
    68  }
    69  
    70  // ReleaseLease will remove the current user from lease ownership and trigger secret rotation by expiring the secret lease
    71  func (s *clusterSecretService) ReleaseLease(ctx context.Context, clusterEdgeID string) error {
    72  	user := middleware.ForContext(ctx)
    73  	currentTime := time.Now().UTC().Format(time.RFC3339)
    74  	result, err := s.SQLDB.ExecContext(ctx, ExpireLeaseQuery, currentTime, currentTime, clusterEdgeID, user.Username)
    75  	if err != nil {
    76  		return err
    77  	}
    78  	rows, err := result.RowsAffected()
    79  	if err != nil {
    80  		return err
    81  	}
    82  	if rows == 0 {
    83  		return ErrUserDoesNotOwnLease
    84  	}
    85  	return nil
    86  }
    87  
    88  // RevokeLease will remove the owner from a lease and trigger secret rotation by expiring the secret lease
    89  func (s *clusterSecretService) RevokeLease(ctx context.Context, clusterEdgeID string, username string) error {
    90  	currentTime := time.Now().UTC().Format(time.RFC3339)
    91  	result, err := s.SQLDB.ExecContext(ctx, ExpireLeaseQuery, currentTime, currentTime, clusterEdgeID, username)
    92  	if err != nil {
    93  		return err
    94  	}
    95  	rows, err := result.RowsAffected()
    96  	if err != nil {
    97  		return err
    98  	}
    99  	if rows == 0 {
   100  		return ErrCannotRevokeUser
   101  	}
   102  	return nil
   103  }
   104  
   105  // RemoveUserFromLease deletes the username from an expired lease
   106  func (s *clusterSecretService) RemoveUserFromLease(ctx context.Context, clusterSecretLeaseEdgeID string) error {
   107  	currentTime := time.Now().UTC().Format(time.RFC3339)
   108  	result, err := s.SQLDB.ExecContext(ctx, RemoveUserFromLeaseQuery, currentTime, clusterSecretLeaseEdgeID)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	rows, err := result.RowsAffected()
   113  	if err != nil {
   114  		return err
   115  	}
   116  	if rows == 0 {
   117  		return ErrFailedToUpdateDatabase
   118  	}
   119  	return nil
   120  }
   121  
   122  // FetchLease will attempt to query for a cluster secrets lease including the lease owner and expiration date
   123  func (s *clusterSecretService) FetchLease(ctx context.Context, clusterEdgeID string) (model.ClusterSecretLease, error) {
   124  	secretLease := model.ClusterSecretLease{}
   125  	row := s.SQLDB.QueryRowContext(ctx, FetchLeaseQuery, clusterEdgeID)
   126  	var leaseID, expiration, owner string
   127  	if err := row.Scan(&leaseID, &expiration, &owner); err != nil {
   128  		return secretLease, err
   129  	}
   130  	var secretTypes []string
   131  	rows, err := s.SQLDB.QueryContext(ctx, FetchLeaseSecretTypesQuery, clusterEdgeID, leaseID)
   132  	if err != nil {
   133  		return secretLease, err
   134  	}
   135  	defer rows.Close()
   136  	for rows.Next() {
   137  		var secretType string
   138  		if err := rows.Scan(&secretType); err != nil {
   139  			return secretLease, err
   140  		}
   141  		secretTypes = append(secretTypes, secretType)
   142  	}
   143  	if err := rows.Err(); err != nil {
   144  		return secretLease, sqlerr.Wrap(err)
   145  	}
   146  
   147  	return model.ClusterSecretLease{
   148  		ExpiresAt:   expiration,
   149  		Owner:       owner,
   150  		SecretTypes: secretTypes,
   151  	}, nil
   152  }
   153  
   154  // CreateLease will create a new lease for a cluster with no user set
   155  func (s *clusterSecretService) CreateLease(ctx context.Context, clusterEdgeID string) (string, error) {
   156  	leaseID := uuid.NewString()
   157  	currentTime := time.Now().UTC().Format(time.RFC3339)
   158  	result, err := s.SQLDB.ExecContext(ctx, CreateClusterSecretLeaseQuery, leaseID, clusterEdgeID, currentTime, currentTime, currentTime)
   159  	if err != nil {
   160  		return "", err
   161  	}
   162  	rows, err := result.RowsAffected()
   163  	if err != nil {
   164  		return "", err
   165  	}
   166  	if rows == 0 {
   167  		return "", ErrFailedToUpdateDatabase
   168  	}
   169  	return leaseID, nil
   170  }
   171  
   172  // FetchLeaseID will get the clusterSecretLeaseID for a lease
   173  func (s *clusterSecretService) FetchLeaseID(ctx context.Context, clusterEdgeID string) (string, error) {
   174  	row := s.SQLDB.QueryRowContext(ctx, FetchLeaseIDQuery, clusterEdgeID)
   175  	var leaseID string
   176  	if err := row.Scan(&leaseID); err != nil {
   177  		return "", err
   178  	}
   179  	return leaseID, nil
   180  }
   181  
   182  // VerifyLeaseExists checks that a lease exists for a cluster, and if not creates one
   183  func (s *clusterSecretService) VerifyLeaseExists(ctx context.Context, clusterEdgeID string) (string, error) {
   184  	var leaseID string
   185  	_, err := s.FetchLease(ctx, clusterEdgeID)
   186  	if errors.Is(err, sql.ErrNoRows) {
   187  		leaseID, err = s.CreateLease(ctx, clusterEdgeID)
   188  		if err != nil {
   189  			return "", err
   190  		}
   191  	} else if err != nil {
   192  		return "", err
   193  	}
   194  	return leaseID, nil
   195  }
   196  

View as plain text