github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/btpmanager/credentials/manager.go (about) 1 package btpmgrcreds 2 3 import ( 4 "context" 5 "fmt" 6 "reflect" 7 "strings" 8 "time" 9 10 "github.com/kyma-project/control-plane/components/provisioner/pkg/gqlschema" 11 "github.com/kyma-project/kyma-environment-broker/internal" 12 "github.com/kyma-project/kyma-environment-broker/internal/provisioner" 13 "github.com/kyma-project/kyma-environment-broker/internal/storage" 14 "github.com/kyma-project/kyma-environment-broker/internal/storage/dbmodel" 15 "github.com/sirupsen/logrus" 16 apicorev1 "k8s.io/api/core/v1" 17 v1 "k8s.io/api/core/v1" 18 "k8s.io/apimachinery/pkg/api/errors" 19 apierrors "k8s.io/apimachinery/pkg/api/errors" 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 22 "k8s.io/apimachinery/pkg/runtime/schema" 23 "k8s.io/client-go/tools/clientcmd" 24 "sigs.k8s.io/controller-runtime/pkg/client" 25 ) 26 27 const ( 28 BtpManagerSecretName = "sap-btp-manager" 29 BtpManagerSecretNamespace = "kyma-system" 30 ) 31 32 var ( 33 BtpManagerLabels = map[string]string{"app.kubernetes.io/managed-by": keb, "app.kubernetes.io/watched-by": keb} 34 BtpManagerAnnotations = map[string]string{"Warning": "This secret is generated. Do not edit!"} 35 KymaGvk = schema.GroupVersionKind{Group: "operator.kyma-project.io", Version: "v1beta2", Kind: "Kyma"} 36 ) 37 38 const ( 39 keb = "kcp-kyma-environment-broker" 40 kcpNamespace = "kcp-system" 41 instanceIdLabel = "kyma-project.io/instance-id" 42 ) 43 44 const ( 45 secretClientId = "clientid" 46 secretClientSecret = "clientsecret" 47 secretSmUrl = "sm_url" 48 secretTokenUrl = "tokenurl" 49 secretClusterId = "cluster_id" 50 ) 51 52 type Manager struct { 53 ctx context.Context 54 instances storage.Instances 55 kcpK8sClient client.Client 56 dryRun bool 57 provisioner provisioner.Client 58 logger *logrus.Logger 59 } 60 61 func NewManager(ctx context.Context, kcpK8sClient client.Client, instanceDb storage.Instances, logs *logrus.Logger, dryRun bool, provisioner provisioner.Client) *Manager { 62 return &Manager{ 63 ctx: ctx, 64 instances: instanceDb, 65 kcpK8sClient: kcpK8sClient, 66 dryRun: dryRun, 67 provisioner: provisioner, 68 logger: logs, 69 } 70 } 71 72 func (s *Manager) MatchInstance(kymaName string) (*internal.Instance, error) { 73 kyma := &unstructured.Unstructured{} 74 kyma.SetGroupVersionKind(KymaGvk) 75 err := s.kcpK8sClient.Get(s.ctx, client.ObjectKey{ 76 Namespace: kcpNamespace, 77 Name: kymaName, 78 }, kyma) 79 if err != nil && errors.IsNotFound(err) { 80 s.logger.Errorf("not found secret with name %s on cluster : %s", kymaName, err) 81 return nil, err 82 } else if err != nil { 83 s.logger.Errorf("unexpected error while getting secret %s from cluster : %s", kymaName, err) 84 return nil, err 85 } 86 s.logger.Infof("found kyma CR on kcp for kyma name: %s", kymaName) 87 labels := kyma.GetLabels() 88 instanceId, ok := labels[instanceIdLabel] 89 if !ok { 90 s.logger.Errorf("not found instance for kyma name %s : %s", kymaName, err) 91 return nil, err 92 } 93 s.logger.Infof("found instance id %s for kyma name %s", instanceId, kymaName) 94 instance, err := s.instances.GetByID(instanceId) 95 if err != nil { 96 s.logger.Errorf("while getting instance %s from db %s", instanceId, err) 97 return nil, err 98 } 99 s.logger.Infof("instance %s found in db", instance.InstanceID) 100 return instance, err 101 } 102 103 func (s *Manager) ReconcileAll(jobReconciliationDelay time.Duration) (int, int, int, int, error) { 104 instances, err := s.GetReconcileCandidates() 105 if err != nil { 106 return 0, 0, 0, 0, err 107 } 108 s.logger.Infof("processing %d instances as candidates", len(instances)) 109 110 updateDone, updateNotDoneDueError, updateNotDoneDueOkState := 0, 0, 0 111 for _, instance := range instances { 112 time.Sleep(jobReconciliationDelay) 113 updated, err := s.ReconcileSecretForInstance(&instance) 114 if err != nil { 115 s.logger.Errorf("while doing update, for instance: %s, %s", instance.InstanceID, err) 116 updateNotDoneDueError++ 117 continue 118 } 119 if updated { 120 s.logger.Infof("update done for instance %s", instance.InstanceID) 121 updateDone++ 122 } else { 123 s.logger.Infof("no need to update instance %s", instance.InstanceID) 124 updateNotDoneDueOkState++ 125 } 126 } 127 s.logger.Infof("(runtime-reconciler summary) from total %d instances: %d are OK, update was needed (and done with success) for %d instances, errors occur for %d instances", 128 len(instances), updateNotDoneDueOkState, updateDone, updateNotDoneDueError) 129 return len(instances), updateDone, updateNotDoneDueError, updateNotDoneDueOkState, nil 130 } 131 132 func (s *Manager) GetReconcileCandidates() ([]internal.Instance, error) { 133 allInstances, _, _, err := s.instances.List(dbmodel.InstanceFilter{}) 134 if err != nil { 135 return nil, fmt.Errorf("while getting all instances %s", err) 136 } 137 s.logger.Infof("total number of instances in db: %d", len(allInstances)) 138 139 var instancesWithinRuntime []internal.Instance 140 for _, instance := range allInstances { 141 if !instance.Reconcilable { 142 s.logger.Infof("skipping instance %s because it is not reconilable (no runtimeId,last op was deprovisoning or op is in progress)", instance.InstanceID) 143 continue 144 } 145 146 if instance.Parameters.ErsContext.SMOperatorCredentials == nil || instance.InstanceDetails.ServiceManagerClusterID == "" { 147 s.logger.Warnf("skipping instance %s because there are no needed data attached to instance", instance.InstanceID) 148 continue 149 } 150 151 instancesWithinRuntime = append(instancesWithinRuntime, instance) 152 s.logger.Infof("adding instance %s as candidate for reconcilation", instance.InstanceID) 153 } 154 155 s.logger.Infof("from total number of instances (%d) took %d as candidates", len(allInstances), len(instancesWithinRuntime)) 156 return instancesWithinRuntime, nil 157 } 158 159 func (s *Manager) ReconcileSecretForInstance(instance *internal.Instance) (bool, error) { 160 s.logger.Infof("reconcilation of btp-manager secret started for %s", instance.InstanceID) 161 162 futureSecret, err := PrepareSecret(instance.Parameters.ErsContext.SMOperatorCredentials, instance.InstanceDetails.ServiceManagerClusterID) 163 if err != nil { 164 return false, err 165 } 166 167 k8sClient, err := s.getSkrK8sClient(instance) 168 if err != nil { 169 return false, fmt.Errorf("while getting k8sClient for %s : %w", instance.InstanceID, err) 170 } 171 s.logger.Infof("connected to skr with success for instance %s", instance.InstanceID) 172 173 currentSecret := &v1.Secret{} 174 err = k8sClient.Get(context.Background(), client.ObjectKey{Name: BtpManagerSecretName, Namespace: BtpManagerSecretNamespace}, currentSecret) 175 if err != nil && errors.IsNotFound(err) { 176 s.logger.Infof("not found btp-manager secret on cluster for instance: %s", instance.InstanceID) 177 if s.dryRun { 178 s.logger.Infof("[dry-run] secret for instance %s would be created", instance.InstanceID) 179 } else { 180 if err := CreateOrUpdateSecret(k8sClient, futureSecret, s.logger); err != nil { 181 s.logger.Errorf("while creating secret in cluster for %s", instance.InstanceID) 182 return false, err 183 } 184 s.logger.Infof("created btp-manager secret on cluster for instance %s successfully", instance.InstanceID) 185 } 186 return true, nil 187 } else if err != nil { 188 return false, fmt.Errorf("while getting secret from cluster for instance %s : %s", instance.InstanceID, err) 189 } 190 191 notMatchingKeys, err := s.compareSecrets(currentSecret, futureSecret) 192 if err != nil { 193 return false, fmt.Errorf("validation of secrets failed with unexpected reason for instance: %s : %s", instance.InstanceID, err) 194 } else if len(notMatchingKeys) > 0 { 195 s.logger.Infof("btp-manager secret on cluster does not match for instance credentials in db : %s, incorrect values for keys: %s ", instance.InstanceID, strings.Join(notMatchingKeys, ",")) 196 if s.dryRun { 197 s.logger.Infof("[dry-run] secret for instance %s would be updated", instance.InstanceID) 198 } else { 199 if err := CreateOrUpdateSecret(k8sClient, futureSecret, s.logger); err != nil { 200 s.logger.Errorf("while updating secret in cluster for %s %s", instance.InstanceID, err) 201 return false, err 202 } 203 s.logger.Infof("btp-manager secret on cluster updated for %s to match state from instances db", instance.InstanceID) 204 } 205 return true, nil 206 } else { 207 s.logger.Infof("instance %s OK: btp-manager secret on cluster match within expected data", instance.InstanceID) 208 } 209 210 return false, nil 211 } 212 213 func (s *Manager) getSkrK8sClient(instance *internal.Instance) (client.Client, error) { 214 secretName := getKubeConfigSecretName(instance.RuntimeID) 215 kubeConfigSecret := &v1.Secret{} 216 err := s.kcpK8sClient.Get(s.ctx, client.ObjectKey{Name: secretName, Namespace: kcpNamespace}, kubeConfigSecret) 217 if err != nil && !errors.IsNotFound(err) { 218 return nil, fmt.Errorf("while getting secret from kcp for %s : %w", instance.InstanceID, err) 219 } 220 221 var kubeConfig []byte 222 if errors.IsNotFound(err) { 223 s.logger.Infof("not found secret for %s, now it will be executed try to get kubeConfig from provisioner.", instance.InstanceID) 224 status, err := CallWithRetry(func() (gqlschema.RuntimeStatus, error) { 225 return s.provisioner.RuntimeStatus(instance.Parameters.ErsContext.GlobalAccountID, instance.RuntimeID) 226 }, 5, time.Second*5) 227 if err != nil { 228 return nil, fmt.Errorf("while getting runtime status from provisioner for %s : %s", instance.InstanceID, err) 229 } 230 231 if status.RuntimeConfiguration.Kubeconfig == nil { 232 return nil, fmt.Errorf("kubeconfig empty in provisioner response for %s", instance.InstanceID) 233 } 234 235 s.logger.Infof("found kubeconfig in provisioner for %s", instance.InstanceID) 236 kubeConfig = []byte(*status.RuntimeConfiguration.Kubeconfig) 237 } else { 238 s.logger.Infof("found secret %s on kcp cluster for %s", secretName, instance.InstanceID) 239 240 config, ok := kubeConfigSecret.Data["config"] 241 if !ok { 242 return nil, fmt.Errorf("while getting 'config' from secret from %s for %s", secretName, instance.InstanceID) 243 } 244 245 s.logger.Infof("found kubeconfig in secret %s for %s", secretName, instance.InstanceID) 246 kubeConfig = config 247 } 248 249 if kubeConfig == nil || len(kubeConfig) == 0 { 250 return nil, fmt.Errorf("not found kubeConfig as secret nor in provisioner or is empty for %s", instance.InstanceID) 251 } 252 253 k8sClient, err := CallWithRetry(func() (client.Client, error) { 254 restCfg, err := clientcmd.RESTConfigFromKubeConfig(kubeConfig) 255 if err != nil { 256 return nil, fmt.Errorf("while making REST cfg from kube config string for %s : %s", instance.InstanceID, err) 257 } 258 k8sClient, err := client.New(restCfg, client.Options{}) 259 if err != nil { 260 return nil, fmt.Errorf("while creating k8sClient from REST config for %s : %s", instance.InstanceID, err) 261 } 262 return k8sClient, nil 263 }, 5, time.Second*5) 264 265 if err != nil { 266 return nil, fmt.Errorf("while creating k8sClient from REST config for %s : %s", instance.InstanceID, err) 267 } 268 269 return k8sClient, nil 270 } 271 272 func (s *Manager) compareSecrets(s1, s2 *v1.Secret) ([]string, error) { 273 areSecretEqualByKey := func(key string) (bool, error) { 274 currentValue, ok := s1.Data[key] 275 if !ok { 276 return false, fmt.Errorf("while getting the value for the key %s in the first secret", key) 277 } 278 expectedValue, ok := s2.Data[key] 279 if !ok { 280 return false, fmt.Errorf("while getting the value for the key %s in the second secret", key) 281 } 282 return reflect.DeepEqual(currentValue, expectedValue), nil 283 } 284 285 notEqual := make([]string, 0) 286 for _, key := range []string{secretClientSecret, secretClientId, secretSmUrl, secretTokenUrl, secretClusterId} { 287 equal, err := areSecretEqualByKey(key) 288 if err != nil { 289 s.logger.Errorf("getting value for key %s", key) 290 return nil, err 291 } 292 if !equal { 293 notEqual = append(notEqual, key) 294 } 295 } 296 297 return notEqual, nil 298 } 299 300 func getKubeConfigSecretName(runtimeId string) string { 301 return fmt.Sprintf("kubeconfig-%s", runtimeId) 302 } 303 304 func PrepareSecret(credentials *internal.ServiceManagerOperatorCredentials, clusterID string) (*apicorev1.Secret, error) { 305 if credentials == nil || clusterID == "" { 306 return nil, fmt.Errorf("empty params given") 307 } 308 if credentials.ClientID == "" { 309 return nil, fmt.Errorf("client Id not set") 310 } 311 if credentials.ClientSecret == "" { 312 return nil, fmt.Errorf("clients ecret not set") 313 } 314 if credentials.ServiceManagerURL == "" { 315 return nil, fmt.Errorf("service manager url not set") 316 } 317 if credentials.URL == "" { 318 return nil, fmt.Errorf("url not set") 319 } 320 321 return &v1.Secret{ 322 TypeMeta: metav1.TypeMeta{Kind: "Secret"}, 323 ObjectMeta: metav1.ObjectMeta{ 324 Name: BtpManagerSecretName, 325 Namespace: BtpManagerSecretNamespace, 326 Labels: BtpManagerLabels, 327 Annotations: BtpManagerAnnotations, 328 }, 329 Data: map[string][]byte{ 330 secretClientId: []byte(credentials.ClientID), 331 secretClientSecret: []byte(credentials.ClientSecret), 332 secretSmUrl: []byte(credentials.ServiceManagerURL), 333 secretTokenUrl: []byte(credentials.URL), 334 secretClusterId: []byte(clusterID), 335 }, 336 Type: apicorev1.SecretTypeOpaque, 337 }, nil 338 } 339 340 func CreateOrUpdateSecret(k8sClient client.Client, futureSecret *apicorev1.Secret, log logrus.FieldLogger) error { 341 if futureSecret == nil { 342 return fmt.Errorf("empty secret data given") 343 } 344 currentSecret := apicorev1.Secret{} 345 getErr := k8sClient.Get(context.Background(), client.ObjectKey{Namespace: BtpManagerSecretNamespace, Name: BtpManagerSecretName}, ¤tSecret) 346 switch { 347 case getErr != nil && !apierrors.IsNotFound(getErr): 348 return fmt.Errorf("failed to get the secret for BTP Manager: %s", getErr) 349 case getErr != nil && apierrors.IsNotFound(getErr): 350 namespace := &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: BtpManagerSecretNamespace}} 351 createErr := k8sClient.Create(context.Background(), namespace) 352 if createErr != nil && !apierrors.IsAlreadyExists(createErr) { 353 return fmt.Errorf("could not create %s namespace: %s", BtpManagerSecretNamespace, createErr) 354 } 355 356 createErr = k8sClient.Create(context.Background(), futureSecret) 357 if createErr != nil { 358 return fmt.Errorf("failed to create the secret for BTP Manager: %s", createErr) 359 } 360 361 log.Info("the secret for BTP Manager created") 362 return nil 363 default: 364 if !reflect.DeepEqual(currentSecret.Labels, BtpManagerLabels) { 365 log.Warnf("the secret %s was not created by KEB and its data will be overwritten", BtpManagerSecretName) 366 } 367 368 currentSecret.Data = futureSecret.Data 369 currentSecret.ObjectMeta.Labels = futureSecret.ObjectMeta.Labels 370 currentSecret.ObjectMeta.Annotations = futureSecret.ObjectMeta.Annotations 371 updateErr := k8sClient.Update(context.Background(), ¤tSecret) 372 if updateErr != nil { 373 return fmt.Errorf("failed to update the secret for BTP Manager: %s", updateErr) 374 } 375 376 log.Info("the secret for BTP Manager updated") 377 return nil 378 } 379 }