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 }