github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/ias/bundle.go (about)

     1  package ias
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"strings"
     7  )
     8  
     9  type (
    10  	ProviderID string
    11  
    12  	Config struct {
    13  		URL                    string
    14  		UserSecret             string
    15  		UserID                 string
    16  		IdentityProvider       string
    17  		Disabled               bool
    18  		TLSRenegotiationEnable bool `envconfig:"default=false"`
    19  		SkipCertVerification   bool `envconfig:"default=false"`
    20  	}
    21  )
    22  
    23  //go:generate mockery --name=IASCLient --output=automock --outpkg=automock --case=underscore
    24  type IASCLient interface {
    25  	GetCompany() (*Company, error)
    26  	CreateServiceProvider(string, string) error
    27  	DeleteServiceProvider(string) error
    28  	DeleteSecret(SecretsRef) error
    29  	GenerateServiceProviderSecret(SecretConfiguration) (*ServiceProviderSecret, error)
    30  	AuthenticationURL(ProviderID) string
    31  	SetOIDCConfiguration(string, OIDCType) error
    32  	SetSAMLConfiguration(string, SAMLType) error
    33  	SetAssertionAttribute(string, PostAssertionAttributes) error
    34  	SetSubjectNameIdentifier(string, SubjectNameIdentifier) error
    35  	SetAuthenticationAndAccess(string, AuthenticationAndAccess) error
    36  	SetDefaultAuthenticatingIDP(DefaultAuthIDPConfig) error
    37  }
    38  
    39  type ServiceProviderBundle struct {
    40  	client                IASCLient
    41  	config                Config
    42  	serviceProvider       ServiceProvider
    43  	serviceProviderExist  bool
    44  	serviceProviderName   string
    45  	serviceProviderParams ServiceProviderParam
    46  	providerID            ProviderID
    47  	organization          string
    48  }
    49  
    50  // NewServiceProviderBundle returns pointer to new ServiceProviderBundle
    51  func NewServiceProviderBundle(bundleIdentifier string, spParams ServiceProviderParam, c IASCLient, cfg Config) *ServiceProviderBundle {
    52  	return &ServiceProviderBundle{
    53  		client:                c,
    54  		config:                cfg,
    55  		serviceProviderParams: spParams,
    56  		serviceProviderName:   fmt.Sprintf("SKR %s (instanceID: %s)", strings.Title(spParams.domain), bundleIdentifier),
    57  		organization:          "global",
    58  	}
    59  }
    60  
    61  // ServiceProviderName returns SP name which includes instance ID
    62  func (b *ServiceProviderBundle) ServiceProviderName() string {
    63  	return b.serviceProviderName
    64  }
    65  
    66  // ServiceProviserType returns SSO type (SAML or OIDC)
    67  func (b *ServiceProviderBundle) ServiceProviderType() string {
    68  	return b.serviceProviderParams.ssoType
    69  }
    70  
    71  // FetchServiceProviderData fetches all ServiceProviders and IdentityProviders for company
    72  // saves specific elements based on the name
    73  func (b *ServiceProviderBundle) FetchServiceProviderData() error {
    74  	company, err := b.client.GetCompany()
    75  	if err != nil {
    76  		return fmt.Errorf("while getting company: %w", err)
    77  	}
    78  
    79  	for _, identifiers := range company.IdentityProviders {
    80  		if identifiers.Name == b.config.IdentityProvider {
    81  			b.providerID = ProviderID(identifiers.ID)
    82  			break
    83  		}
    84  	}
    85  	if b.providerID == "" {
    86  		return fmt.Errorf("provider ID for %s name does not exist", b.config.IdentityProvider)
    87  	}
    88  
    89  	for _, provider := range company.ServiceProviders {
    90  		if provider.DisplayName == b.serviceProviderName {
    91  			b.serviceProvider = provider
    92  			b.serviceProviderExist = true
    93  			break
    94  		}
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  // ServiceProviderExist deteminates whether a particular item has been found
   101  func (b *ServiceProviderBundle) ServiceProviderExist() bool {
   102  	return b.serviceProviderExist
   103  }
   104  
   105  // CreateServiceProvider creates new ServiceProvider on IAS based on name
   106  // it will be create in specific company/organization
   107  func (b *ServiceProviderBundle) CreateServiceProvider() error {
   108  	err := b.client.CreateServiceProvider(b.serviceProviderName, b.organization)
   109  	if err != nil {
   110  		return fmt.Errorf("while creating ServiceProvider: %w", err)
   111  	}
   112  	err = b.FetchServiceProviderData()
   113  	if err != nil {
   114  		return fmt.Errorf("while fetching ServiceProvider: %w", err)
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  // DeleteServiceProvider removes ServiceProvider from IAS
   121  func (b *ServiceProviderBundle) DeleteServiceProvider() error {
   122  	err := b.FetchServiceProviderData()
   123  	if err != nil {
   124  		return fmt.Errorf("while fetching ServiceProvider before deleting: %w", err)
   125  	}
   126  	if !b.serviceProviderExist {
   127  		return nil
   128  	}
   129  
   130  	err = b.client.DeleteServiceProvider(b.serviceProvider.ID)
   131  	if err != nil {
   132  		return fmt.Errorf("while deleting ServiceProvider: %w", err)
   133  	}
   134  
   135  	return nil
   136  }
   137  
   138  func (b *ServiceProviderBundle) configureServiceProviderOIDCType(serviceProviderName string, redirectURI string) error {
   139  	iasType := OIDCType{
   140  		ServiceProviderName: serviceProviderName,
   141  		SsoType:             b.serviceProviderParams.ssoType,
   142  		OpenIDConnectConfig: OpenIDConnectConfig{
   143  			RedirectURIs: []string{redirectURI},
   144  		},
   145  	}
   146  
   147  	return b.client.SetOIDCConfiguration(b.serviceProvider.ID, iasType)
   148  }
   149  
   150  func (b *ServiceProviderBundle) configureServiceProviderSAMLType(serviceProviderName string, redirectURI string) error {
   151  	iasType := SAMLType{
   152  		ServiceProviderName: serviceProviderName,
   153  		ACSEndpoints: []ACSEndpoint{
   154  			{
   155  				Location:  redirectURI,
   156  				Index:     0,
   157  				IsDefault: true,
   158  			},
   159  		},
   160  	}
   161  
   162  	return b.client.SetSAMLConfiguration(b.serviceProvider.ID, iasType)
   163  }
   164  
   165  // ConfigureServiceProviderType sets SSO type, name and URLs based on provided URL for ServiceProvider
   166  func (b *ServiceProviderBundle) ConfigureServiceProviderType(dashboardURL string) error {
   167  	u, err := url.ParseRequestURI(dashboardURL)
   168  	if err != nil {
   169  		return fmt.Errorf("while parsing path for IAS Type: %w", err)
   170  	}
   171  	serviceProviderDNS := strings.Replace(u.Host, "console.", fmt.Sprintf("%s.", b.serviceProviderParams.domain), 1)
   172  	redirectURI := fmt.Sprintf("%s://%s%s", u.Scheme, serviceProviderDNS, b.serviceProviderParams.redirectPath)
   173  
   174  	switch b.serviceProviderParams.ssoType {
   175  	case SAML:
   176  		err = b.configureServiceProviderSAMLType(serviceProviderDNS, redirectURI)
   177  	case OIDC:
   178  		err = b.configureServiceProviderOIDCType(serviceProviderDNS, redirectURI)
   179  	default:
   180  		err = fmt.Errorf("Unrecognized ssoType: %s", b.serviceProviderParams.ssoType)
   181  	}
   182  
   183  	if err != nil {
   184  		return fmt.Errorf("while configuring IAS Type: %w", err)
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // ConfigureServiceProvider sets configuration such as assertion attributes, name identifier and
   191  // gropus allows to connect with specific ServiceProvider
   192  func (b *ServiceProviderBundle) ConfigureServiceProvider() error {
   193  	// set "AssertionAttributes"
   194  	attributeDeliver := NewAssertionAttributeDeliver()
   195  	sciAttributes := PostAssertionAttributes{
   196  		AssertionAttributes: attributeDeliver.GenerateAssertionAttribute(b.serviceProvider),
   197  	}
   198  	err := b.client.SetAssertionAttribute(b.serviceProvider.ID, sciAttributes)
   199  	if err != nil {
   200  		return fmt.Errorf("while configuring AssertionAttributes: %w", err)
   201  	}
   202  
   203  	// set "SubjectNameIdentifier"
   204  	subjectNameIdentifier := SubjectNameIdentifier{
   205  		NameIDAttribute: "mail",
   206  	}
   207  	err = b.client.SetSubjectNameIdentifier(b.serviceProvider.ID, subjectNameIdentifier)
   208  	if err != nil {
   209  		return fmt.Errorf("while configuring SubjectNameIdentifier: %w", err)
   210  	}
   211  
   212  	// set "DefaultAuthenticatingIDP"
   213  	defaultAuthIDP := DefaultAuthIDPConfig{
   214  		Organization:   b.organization,
   215  		ID:             b.serviceProvider.ID,
   216  		DefaultAuthIDP: b.client.AuthenticationURL(b.providerID),
   217  	}
   218  	err = b.client.SetDefaultAuthenticatingIDP(defaultAuthIDP)
   219  	if err != nil {
   220  		return fmt.Errorf("while configuring DefaultAuthenticatingIDP: %w", err)
   221  	}
   222  
   223  	// set "AuthenticationAndAccess"
   224  	if len(b.serviceProviderParams.allowedGroups) > 0 {
   225  		authenticationAndAccess := AuthenticationAndAccess{
   226  			ServiceProviderAccess: ServiceProviderAccess{
   227  				RBAConfig: RBAConfig{
   228  					RBARules:      make([]RBARules, len(b.serviceProviderParams.allowedGroups)),
   229  					DefaultAction: "Deny",
   230  				},
   231  			},
   232  		}
   233  		for i, group := range b.serviceProviderParams.allowedGroups {
   234  			authenticationAndAccess.ServiceProviderAccess.RBAConfig.RBARules[i] = RBARules{
   235  				Action:    "Allow",
   236  				Group:     group,
   237  				GroupType: "Cloud",
   238  			}
   239  		}
   240  		err = b.client.SetAuthenticationAndAccess(b.serviceProvider.ID, authenticationAndAccess)
   241  		if err != nil {
   242  			return fmt.Errorf("while configuring AuthenticationAndAccess: %w", err)
   243  		}
   244  	}
   245  
   246  	return nil
   247  }
   248  
   249  // GenerateSecret generates new ID and Secret for ServiceProvider, removes already existing secrets
   250  func (b *ServiceProviderBundle) GenerateSecret() (*ServiceProviderSecret, error) {
   251  	err := b.removeSecrets()
   252  	if err != nil {
   253  		return &ServiceProviderSecret{}, fmt.Errorf("while removing existing secrets: %w", err)
   254  	}
   255  
   256  	secretCfg := SecretConfiguration{
   257  		Organization: b.organization,
   258  		ID:           b.serviceProvider.ID,
   259  		RestAPIClientSecret: RestAPIClientSecret{
   260  			Description: "SAP Kyma Runtime Secret",
   261  			Scopes:      []string{"ManageApp", "ManageUsers", "OAuth"},
   262  		},
   263  	}
   264  
   265  	sps, err := b.client.GenerateServiceProviderSecret(secretCfg)
   266  	if err != nil {
   267  		return &ServiceProviderSecret{}, fmt.Errorf("while creating ServiceProviderSecret: %w", err)
   268  	}
   269  
   270  	return sps, nil
   271  }
   272  
   273  func (b *ServiceProviderBundle) removeSecrets() error {
   274  	if len(b.serviceProvider.Secret) == 0 {
   275  		return nil
   276  	}
   277  
   278  	var secretsIDs []string
   279  	for _, s := range b.serviceProvider.Secret {
   280  		secretsIDs = append(secretsIDs, s.SecretID)
   281  	}
   282  
   283  	deleteSecrets := SecretsRef{
   284  		ClientID:         b.serviceProvider.UserForRest,
   285  		ClientSecretsIDs: secretsIDs,
   286  	}
   287  	return b.client.DeleteSecret(deleteSecrets)
   288  }