github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/internal/authenticator/claims/validator.go (about)

     1  package claims
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/kyma-incubator/compass/components/director/pkg/idtokenclaims"
    10  
    11  	"github.com/kyma-incubator/compass/components/director/pkg/str"
    12  
    13  	"github.com/kyma-incubator/compass/components/director/internal/domain/scenarioassignment"
    14  
    15  	"github.com/kyma-incubator/compass/components/director/pkg/persistence"
    16  
    17  	"github.com/kyma-incubator/compass/components/director/internal/domain/tenant"
    18  	"github.com/kyma-incubator/compass/components/director/internal/labelfilter"
    19  	"github.com/kyma-incubator/compass/components/director/internal/model"
    20  	"github.com/kyma-incubator/compass/components/director/pkg/apperrors"
    21  	"github.com/kyma-incubator/compass/components/director/pkg/consumer"
    22  	"github.com/kyma-incubator/compass/components/director/pkg/log"
    23  	"github.com/kyma-incubator/compass/components/hydrator/pkg/tenantmapping"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  // RuntimeService is used to interact with runtimes.
    28  //go:generate mockery --name=RuntimeService --output=automock --outpkg=automock --case=underscore --disable-version-string
    29  type RuntimeService interface {
    30  	GetLabel(context.Context, string, string) (*model.Label, error)
    31  	GetByFilters(ctx context.Context, filters []*labelfilter.LabelFilter) (*model.Runtime, error)
    32  }
    33  
    34  // RuntimeCtxService is used to interact with runtime contexts.
    35  //go:generate mockery --name=RuntimeCtxService --output=automock --outpkg=automock --case=underscore --disable-version-string
    36  type RuntimeCtxService interface {
    37  	ListByFilter(ctx context.Context, runtimeID string, filter []*labelfilter.LabelFilter, pageSize int, cursor string) (*model.RuntimeContextPage, error)
    38  }
    39  
    40  // ApplicationTemplateService is used to interact with application templates.
    41  //go:generate mockery --name=ApplicationTemplateService --output=automock --outpkg=automock --case=underscore --disable-version-string
    42  type ApplicationTemplateService interface {
    43  	GetByFilters(ctx context.Context, filter []*labelfilter.LabelFilter) (*model.ApplicationTemplate, error)
    44  }
    45  
    46  // ApplicationService is responsible for the service-layer Application operations.
    47  //go:generate mockery --name=ApplicationService --output=automock --outpkg=automock --case=underscore --disable-version-string
    48  type ApplicationService interface {
    49  	ListAll(ctx context.Context) ([]*model.Application, error)
    50  }
    51  
    52  // IntegrationSystemService is used to check if integration system with a given ID exists.
    53  //go:generate mockery --name=IntegrationSystemService --output=automock --outpkg=automock --case=underscore --disable-version-string
    54  type IntegrationSystemService interface {
    55  	Exists(context.Context, string) (bool, error)
    56  }
    57  
    58  type validator struct {
    59  	transact                     persistence.Transactioner
    60  	runtimesSvc                  RuntimeService
    61  	runtimeCtxSvc                RuntimeCtxService
    62  	appTemplateSvc               ApplicationTemplateService
    63  	applicationSvc               ApplicationService
    64  	intSystemSvc                 IntegrationSystemService
    65  	subscriptionProviderLabelKey string
    66  	consumerSubaccountLabelKey   string
    67  	tokenPrefix                  string
    68  }
    69  
    70  // NewValidator creates new claims validator
    71  func NewValidator(transact persistence.Transactioner, runtimesSvc RuntimeService, runtimeCtxSvc RuntimeCtxService, appTemplateSvc ApplicationTemplateService, applicationSvc ApplicationService, intSystemSvc IntegrationSystemService, subscriptionProviderLabelKey, consumerSubaccountLabelKey, tokenPrefix string) *validator {
    72  	return &validator{
    73  		transact:                     transact,
    74  		runtimesSvc:                  runtimesSvc,
    75  		runtimeCtxSvc:                runtimeCtxSvc,
    76  		appTemplateSvc:               appTemplateSvc,
    77  		applicationSvc:               applicationSvc,
    78  		intSystemSvc:                 intSystemSvc,
    79  		subscriptionProviderLabelKey: subscriptionProviderLabelKey,
    80  		consumerSubaccountLabelKey:   consumerSubaccountLabelKey,
    81  		tokenPrefix:                  tokenPrefix,
    82  	}
    83  }
    84  
    85  // Validate validates given id_token claims
    86  func (v *validator) Validate(ctx context.Context, claims idtokenclaims.Claims) error {
    87  	if err := claims.Valid(); err != nil {
    88  		return errors.Wrapf(err, "while validating claims")
    89  	}
    90  
    91  	if claims.Tenant[tenantmapping.ConsumerTenantKey] == "" && claims.Tenant[tenantmapping.ExternalTenantKey] != "" {
    92  		return apperrors.NewTenantNotFoundError(claims.Tenant[tenantmapping.ExternalTenantKey])
    93  	}
    94  
    95  	if claims.OnBehalfOf == "" {
    96  		return nil
    97  	}
    98  
    99  	log.C(ctx).Infof("Consumer-Provider call by %s on behalf of REDACTED_%x. Proceeding with double authentication crosscheck...", claims.Tenant[tenantmapping.ProviderTenantKey], sha256.Sum256([]byte(claims.Tenant[tenantmapping.ConsumerTenantKey])))
   100  	switch claims.ConsumerType {
   101  	case consumer.Runtime, consumer.ExternalCertificate, consumer.SuperAdmin: // SuperAdmin consumer is needed only for testing purposes
   102  		errRuntimeConsumer := v.validateRuntimeConsumer(ctx, claims)
   103  		if errRuntimeConsumer == nil {
   104  			return nil
   105  		}
   106  		errAppProvider := v.validateApplicationProvider(ctx, claims)
   107  		if errAppProvider == nil {
   108  			return nil
   109  		}
   110  		return apperrors.NewUnauthorizedError(fmt.Sprintf("subscription record not found neither for application: %q nor for runtime: %q", errAppProvider.Error(), errRuntimeConsumer.Error()))
   111  	case consumer.IntegrationSystem:
   112  		return v.validateIntegrationSystemConsumer(ctx, claims)
   113  	default:
   114  		return apperrors.NewUnauthorizedError(fmt.Sprintf("consumer with type %s is not supported", claims.ConsumerType))
   115  	}
   116  }
   117  
   118  func (v *validator) validateRuntimeConsumer(ctx context.Context, claims idtokenclaims.Claims) error {
   119  	tx, err := v.transact.Begin()
   120  	if err != nil {
   121  		log.C(ctx).Errorf("An error has occurred while opening transaction: %v", err)
   122  		return errors.Wrapf(err, "An error has occurred while opening transaction")
   123  	}
   124  	defer v.transact.RollbackUnlessCommitted(ctx, tx)
   125  
   126  	ctx = persistence.SaveToContext(ctx, tx)
   127  
   128  	if len(claims.TokenClientID) == 0 {
   129  		log.C(ctx).Errorf("Could not find consumer token client ID")
   130  		return apperrors.NewUnauthorizedError("could not find consumer token client ID")
   131  	}
   132  	if len(claims.Region) == 0 {
   133  		log.C(ctx).Errorf("Could not determine consumer token's region")
   134  		return apperrors.NewUnauthorizedError("could not determine token's region")
   135  	}
   136  
   137  	tokenClientID := strings.TrimPrefix(claims.TokenClientID, v.tokenPrefix)
   138  	filters := []*labelfilter.LabelFilter{
   139  		labelfilter.NewForKeyWithQuery(v.subscriptionProviderLabelKey, fmt.Sprintf("\"%s\"", tokenClientID)),
   140  		labelfilter.NewForKeyWithQuery(tenant.RegionLabelKey, fmt.Sprintf("\"%s\"", claims.Region)),
   141  	}
   142  
   143  	providerInternalTenantID := claims.Tenant[tenantmapping.ProviderTenantKey]
   144  	providerExternalTenantID := claims.Tenant[tenantmapping.ProviderExternalTenantKey]
   145  	ctxWithProviderTenant := tenant.SaveToContext(ctx, providerInternalTenantID, providerExternalTenantID)
   146  
   147  	log.C(ctx).Infof("Getting runtime in provider tenant %s for labels %s: %s and %s: %s", providerInternalTenantID, tenant.RegionLabelKey, claims.Region, v.subscriptionProviderLabelKey, tokenClientID)
   148  	runtime, err := v.runtimesSvc.GetByFilters(ctxWithProviderTenant, filters)
   149  	if err != nil {
   150  		log.C(ctx).WithError(err).Errorf("Error while getting runtime in provider tenant %s for labels %s: %s and %s: %s: %v", providerInternalTenantID, tenant.RegionLabelKey, claims.Region, v.subscriptionProviderLabelKey, tokenClientID, err)
   151  		return errors.Wrapf(err, "failed to get runtime in tenant %s for labels %s: %s and %s: %s", providerInternalTenantID, tenant.RegionLabelKey, claims.Region, v.subscriptionProviderLabelKey, tokenClientID)
   152  	}
   153  	log.C(ctx).Infof("Found runtime with ID: %s in provider tenant %s for labels %s: %s and %s: %s", runtime.ID, providerInternalTenantID, tenant.RegionLabelKey, claims.Region, v.subscriptionProviderLabelKey, tokenClientID)
   154  
   155  	consumerInternalTenantID := claims.Tenant[tenantmapping.ConsumerTenantKey]
   156  	consumerExternalTenantID := claims.Tenant[tenantmapping.ExternalTenantKey]
   157  	ctxWithConsumerTenant := tenant.SaveToContext(ctx, consumerInternalTenantID, consumerExternalTenantID)
   158  
   159  	rtmCtxFilter := []*labelfilter.LabelFilter{
   160  		labelfilter.NewForKeyWithQuery(v.consumerSubaccountLabelKey, fmt.Sprintf("\"%s\"", consumerExternalTenantID)),
   161  	}
   162  
   163  	log.C(ctx).Infof("Listing runtime context(s) in the consumer tenant %q for runtime with ID: %q and label with key: %q and value: %q", consumerExternalTenantID, runtime.ID, v.consumerSubaccountLabelKey, consumerExternalTenantID)
   164  	rtmCtxPage, err := v.runtimeCtxSvc.ListByFilter(ctxWithConsumerTenant, runtime.ID, rtmCtxFilter, 100, "")
   165  	if err != nil {
   166  		log.C(ctx).Errorf("An error occurred while listing runtime context for runtime with ID: %q and filter with key: %q and value: %q", runtime.ID, v.consumerSubaccountLabelKey, consumerExternalTenantID)
   167  		return errors.Wrapf(err, "while listing runtime context for runtime with ID: %q and filter with key: %q and value: %q", runtime.ID, v.consumerSubaccountLabelKey, consumerExternalTenantID)
   168  	}
   169  	log.C(ctx).Infof("Found %d runtime context(s) for runtime with ID: %q", len(rtmCtxPage.Data), runtime.ID)
   170  
   171  	if len(rtmCtxPage.Data) == 0 {
   172  		log.C(ctx).Errorf("Consumer's external tenant %s was not found as subscription record in the runtime context table for the runtime with ID: %s in the provider tenant %s", consumerExternalTenantID, runtime.ID, providerInternalTenantID)
   173  		return apperrors.NewUnauthorizedError(fmt.Sprintf("Consumer's external tenant %s was not found as subscription record in the runtime context table for the runtime in the provider tenant %s", consumerExternalTenantID, providerInternalTenantID))
   174  	}
   175  
   176  	return tx.Commit()
   177  }
   178  
   179  func (v *validator) validateApplicationProvider(ctx context.Context, claims idtokenclaims.Claims) error {
   180  	tx, err := v.transact.Begin()
   181  	if err != nil {
   182  		log.C(ctx).Errorf("An error has occurred while opening transaction: %v", err)
   183  		return errors.Wrapf(err, "An error has occurred while opening transaction")
   184  	}
   185  	defer v.transact.RollbackUnlessCommitted(ctx, tx)
   186  
   187  	ctx = persistence.SaveToContext(ctx, tx)
   188  
   189  	if len(claims.TokenClientID) == 0 {
   190  		log.C(ctx).Errorf("Could not find consumer token client ID")
   191  		return apperrors.NewUnauthorizedError("could not find consumer token client ID")
   192  	}
   193  	if len(claims.Region) == 0 {
   194  		log.C(ctx).Errorf("Could not determine consumer token's region")
   195  		return apperrors.NewUnauthorizedError("could not determine token's region")
   196  	}
   197  
   198  	providerInternalTenantID := claims.Tenant[tenantmapping.ProviderTenantKey]
   199  	providerExternalTenantID := claims.Tenant[tenantmapping.ProviderExternalTenantKey]
   200  
   201  	tokenClientID := strings.TrimPrefix(claims.TokenClientID, v.tokenPrefix)
   202  	filters := []*labelfilter.LabelFilter{
   203  		labelfilter.NewForKeyWithQuery(v.subscriptionProviderLabelKey, fmt.Sprintf("\"%s\"", tokenClientID)),
   204  		labelfilter.NewForKeyWithQuery(tenant.RegionLabelKey, fmt.Sprintf("\"%s\"", claims.Region)),
   205  		labelfilter.NewForKeyWithQuery(scenarioassignment.SubaccountIDKey, fmt.Sprintf("\"%s\"", providerExternalTenantID)),
   206  	}
   207  
   208  	ctxWithProviderTenant := tenant.SaveToContext(ctx, providerInternalTenantID, providerExternalTenantID)
   209  
   210  	log.C(ctx).Infof("Get application template in provider tenant %s for labels %s: %s and %s: %s", providerInternalTenantID, tenant.RegionLabelKey, claims.Region, v.subscriptionProviderLabelKey, tokenClientID)
   211  	applicationTemplate, err := v.appTemplateSvc.GetByFilters(ctxWithProviderTenant, filters)
   212  	if err != nil {
   213  		log.C(ctx).WithError(err).Errorf("Error while getting application template in provider tenant %s for labels %s: %s and %s: %s: %v", providerInternalTenantID, tenant.RegionLabelKey, claims.Region, v.subscriptionProviderLabelKey, tokenClientID, err)
   214  		return errors.Wrapf(err, "failed to find application template in tenant %s associated with %s: %q and %s: %q", providerExternalTenantID, tenant.RegionLabelKey, claims.Region, v.subscriptionProviderLabelKey, tokenClientID)
   215  	}
   216  	log.C(ctx).Infof("Found application template with ID %q in provider tenant %s for labels %s: %s and %s: %s", applicationTemplate.ID, providerInternalTenantID, tenant.RegionLabelKey, claims.Region, v.subscriptionProviderLabelKey, tokenClientID)
   217  
   218  	consumerInternalTenantID := claims.Tenant[tenantmapping.ConsumerTenantKey]
   219  	consumerExternalTenantID := claims.Tenant[tenantmapping.ExternalTenantKey]
   220  	ctxWithConsumerTenant := tenant.SaveToContext(ctx, consumerInternalTenantID, consumerExternalTenantID)
   221  
   222  	log.C(ctx).Infof("Listing applications in the consumer tenant %q for application template with ID: %q and label with key: %q and value: %q", consumerExternalTenantID, applicationTemplate.ID, v.consumerSubaccountLabelKey, consumerExternalTenantID)
   223  	applications, err := v.applicationSvc.ListAll(ctxWithConsumerTenant)
   224  	if err != nil {
   225  		log.C(ctx).Errorf("An error occurred while listing applications for filter with key: %q and value: %q", v.consumerSubaccountLabelKey, consumerExternalTenantID)
   226  		return errors.Wrapf(err, "while listing applications for filter with key: %q and value: %q", v.consumerSubaccountLabelKey, consumerExternalTenantID)
   227  	}
   228  
   229  	log.C(ctx).Infof("Found %d applications in consumer tenant using label: %q and external tenant ID: %q", len(applications), v.consumerSubaccountLabelKey, consumerExternalTenantID)
   230  
   231  	appFound := false
   232  	for _, application := range applications {
   233  		if str.PtrStrToStr(application.ApplicationTemplateID) == applicationTemplate.ID {
   234  			appFound = true
   235  			break
   236  		}
   237  	}
   238  
   239  	if !appFound {
   240  		log.C(ctx).Errorf("Consumer's external tenant %s was not found as subscription record in the applications table for any application templates in the provider tenant %s", consumerExternalTenantID, providerInternalTenantID)
   241  		return apperrors.NewUnauthorizedError(fmt.Sprintf("Consumer's external tenant %s was not found as subscription record in the applications table for any application templates in the provider tenant %s", consumerExternalTenantID, providerInternalTenantID))
   242  	}
   243  
   244  	return tx.Commit()
   245  }
   246  
   247  func (v *validator) validateIntegrationSystemConsumer(ctx context.Context, claims idtokenclaims.Claims) error {
   248  	if claims.Tenant[tenantmapping.ProviderExternalTenantKey] == claims.ConsumerID {
   249  		return nil // consumer ID is a subaccount tenant
   250  	}
   251  
   252  	exists, err := v.intSystemSvc.Exists(ctx, claims.ConsumerID)
   253  	if err != nil {
   254  		return errors.Wrapf(err, "while checking if integration system with ID %s exists", claims.ConsumerID)
   255  	}
   256  	if !exists {
   257  		return apperrors.NewUnauthorizedError(fmt.Sprintf("integration system with ID %s does not exist", claims.ConsumerID))
   258  	}
   259  
   260  	return nil
   261  }