github.com/jenkins-x/jx/v2@v2.1.155/pkg/vault/create/create.go (about) 1 package create 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/jenkins-x/jx/v2/pkg/errorutil" 8 9 "github.com/banzaicloud/bank-vaults/operator/pkg/apis/vault/v1alpha1" 10 "github.com/banzaicloud/bank-vaults/operator/pkg/client/clientset/versioned" 11 "github.com/google/uuid" 12 "github.com/jenkins-x/jx-logging/pkg/log" 13 "github.com/jenkins-x/jx/v2/pkg/cloud" 14 "github.com/jenkins-x/jx/v2/pkg/cloud/amazon/session" 15 awsvault "github.com/jenkins-x/jx/v2/pkg/cloud/amazon/vault" 16 "github.com/jenkins-x/jx/v2/pkg/cloud/gke" 17 gkevault "github.com/jenkins-x/jx/v2/pkg/cloud/gke/vault" 18 "github.com/jenkins-x/jx/v2/pkg/kube/serviceaccount" 19 "github.com/jenkins-x/jx/v2/pkg/kube/services" 20 "github.com/jenkins-x/jx/v2/pkg/kube/vault" 21 "github.com/jenkins-x/jx/v2/pkg/util" 22 "github.com/jenkins-x/jx/v2/pkg/versionstream" 23 "github.com/pkg/errors" 24 "k8s.io/client-go/kubernetes" 25 ) 26 27 const ( 28 autoCreateTableName = "vault-data" 29 ) 30 31 // VaultCreationParam encapsulates the parameters needed to create a Vault instance. 32 type VaultCreationParam struct { 33 VaultName string 34 ClusterName string 35 Namespace string 36 ServiceAccountName string 37 KubeProvider string 38 SecretsPathPrefix string 39 CreateCloudResources bool 40 Boot bool 41 BatchMode bool 42 VaultOperatorClient versioned.Interface 43 KubeClient kubernetes.Interface 44 VersionResolver versionstream.VersionResolver 45 FileHandles util.IOFileHandles 46 GKE *GKEParam 47 AWS *AWSParam 48 Azure *AzureParam 49 } 50 51 // GKEParam encapsulates the parameters needed to create a Vault instance on GKE. 52 type GKEParam struct { 53 ProjectID string 54 Zone string 55 BucketName string 56 KeyringName string 57 KeyName string 58 RecreateBucket bool 59 } 60 61 // GKEParam encapsulates the parameters needed to create a Vault instance on AWS. 62 type AWSParam struct { 63 IAMUsername string 64 S3Bucket string 65 S3Region string 66 S3Prefix string 67 TemplatesDir string 68 DynamoDBTable string 69 DynamoDBRegion string 70 KMSKeyID string 71 KMSRegion string 72 AccessKeyID string 73 SecretAccessKey string 74 AutoCreate bool 75 } 76 77 // AzureParam encapsulates the parameters needed to create a Vault instance on AWS. 78 type AzureParam struct { 79 TenantID string 80 StorageAccountKey string 81 StorageAccountName string 82 ContainerName string 83 VaultName string 84 KeyName string 85 } 86 87 // VaultCreator defines the interface to create and update Vault instances 88 type VaultCreator interface { 89 CreateOrUpdateVault(param VaultCreationParam) error 90 } 91 92 type defaultVaultCreator struct { 93 } 94 95 // NewVaultCreator creates an instance of the default VaultCreator. 96 func NewVaultCreator() VaultCreator { 97 return &defaultVaultCreator{} 98 } 99 100 func (p *VaultCreationParam) validate() error { 101 var validationErrors []error 102 if p.VaultName == "" { 103 validationErrors = append(validationErrors, errors.New("the Vault name needs to be provided")) 104 } 105 106 if p.Namespace == "" { 107 validationErrors = append(validationErrors, errors.New("the namespace to create the Vault instance into needs to be provided")) 108 } 109 110 if p.KubeClient == nil { 111 validationErrors = append(validationErrors, errors.New("a kube client needs to be provided")) 112 } 113 114 if p.VaultOperatorClient == nil { 115 validationErrors = append(validationErrors, errors.New("a vault operator client needs to be provided")) 116 } 117 118 if p.KubeProvider == "" { 119 validationErrors = append(validationErrors, errors.New("a kube/cloud provider needs be provided")) 120 } 121 122 if p.KubeProvider == cloud.GKE { 123 if p.GKE == nil { 124 validationErrors = append(validationErrors, errors.Errorf("%s selected as kube provider, but no %s specific parameters provided", cloud.GKE, cloud.GKE)) 125 } 126 err := p.GKE.validate() 127 if err != nil { 128 validationErrors = append(validationErrors, err) 129 } 130 } 131 132 if p.KubeProvider == cloud.AWS { 133 if p.AWS == nil { 134 validationErrors = append(validationErrors, errors.Errorf("%s selected as kube provider, but no %s specific parameters provided", cloud.AWS, cloud.AWS)) 135 } 136 if err := p.AWS.validate(); err != nil { 137 validationErrors = append(validationErrors, err) 138 } 139 } 140 141 return errorutil.CombineErrors(validationErrors...) 142 } 143 144 func (p *GKEParam) validate() error { 145 var validationErrors []error 146 if p == nil { 147 return nil 148 } 149 if p.ProjectID == "" { 150 validationErrors = append(validationErrors, errors.New("the GKE project ID needs to be provided")) 151 } 152 if p.Zone == "" { 153 validationErrors = append(validationErrors, errors.New("the GKE zone needs to be provided")) 154 } 155 return errorutil.CombineErrors(validationErrors...) 156 } 157 158 func (p *AWSParam) validate() error { 159 var validationErrors []error 160 if p == nil { 161 return nil 162 } 163 if p.TemplatesDir == "" { 164 validationErrors = append(validationErrors, errors.New("the cloud formation template dir needs to be provided")) 165 } 166 if p.AccessKeyID == "" { 167 validationErrors = append(validationErrors, errors.New("the AccessKeyID needs to be provided")) 168 } 169 if p.SecretAccessKey == "" { 170 validationErrors = append(validationErrors, errors.New("the SecretAccessKey needs to be provided")) 171 } 172 return errorutil.CombineErrors(validationErrors...) 173 } 174 175 // CreateOrUpdateVault creates or updates a Vault instance in the specified namespace. 176 func (v *defaultVaultCreator) CreateOrUpdateVault(param VaultCreationParam) error { 177 err := param.validate() 178 if err != nil { 179 return err 180 } 181 182 vaultAuthServiceAccount, err := v.createAuthServiceAccount(param.KubeClient, param.VaultName, param.ServiceAccountName, param.Namespace) 183 if err != nil { 184 return errors.Wrap(err, "creating Vault authentication service account") 185 } 186 log.Logger().Debugf("Created service account '%s' for Vault authentication", util.ColorInfo(vaultAuthServiceAccount)) 187 188 images, err := v.dockerImages(param.VersionResolver) 189 if err != nil { 190 return errors.Wrap(err, "loading docker images from versions repository") 191 } 192 193 vaultCRD, err := vault.NewVaultCRD(param.KubeClient, param.VaultName, param.Namespace, images, vaultAuthServiceAccount, param.Namespace, param.SecretsPathPrefix) 194 195 err = v.setCloudProviderSpecificSettings(vaultCRD, param) 196 if err != nil { 197 return errors.Wrap(err, "unable to set cloud provider specific Vault configuration") 198 } 199 200 err = vault.CreateOrUpdateVault(vaultCRD, param.VaultOperatorClient, param.Namespace) 201 if err != nil { 202 return errors.Wrap(err, "creating vault") 203 } 204 205 // wait for vault service to become ready before finishing the provisioning 206 err = services.WaitForService(param.KubeClient, param.VaultName, param.Namespace, 1*time.Minute) 207 if err != nil { 208 return errors.Wrap(err, "waiting for vault service") 209 } 210 211 return nil 212 } 213 214 func (v *defaultVaultCreator) dockerImages(resolver versionstream.VersionResolver) (map[string]string, error) { 215 images := map[string]string{ 216 vault.BankVaultsImage: "", 217 vault.VaultImage: "", 218 } 219 220 for image := range images { 221 version, err := resolver.ResolveDockerImage(image) 222 if err != nil { 223 return images, errors.Wrapf(err, "resolving docker image %q", image) 224 } 225 images[image] = version 226 } 227 return images, nil 228 } 229 230 // createAuthServiceAccount creates a Service Account for the Auth service for vault 231 func (v *defaultVaultCreator) createAuthServiceAccount(client kubernetes.Interface, vaultName, serviceAccountName string, namespace string) (string, error) { 232 if serviceAccountName == "" { 233 serviceAccountName = v.authServiceAccountName(vaultName) 234 } 235 236 _, err := serviceaccount.CreateServiceAccount(client, namespace, serviceAccountName) 237 if err != nil { 238 return "", errors.Wrap(err, "creating vault auth service account") 239 } 240 return serviceAccountName, nil 241 } 242 243 // authServiceAccountName creates a service account name for a given vault and cluster name 244 func (v *defaultVaultCreator) authServiceAccountName(vaultName string) string { 245 return fmt.Sprintf("%s-%s", vaultName, "auth-sa") 246 } 247 248 func (v *defaultVaultCreator) setCloudProviderSpecificSettings(vaultCRD *v1alpha1.Vault, param VaultCreationParam) error { 249 var cloudProviderConfig vault.CloudProviderConfig 250 var err error 251 252 if param.CreateCloudResources { 253 switch param.KubeProvider { 254 case cloud.GKE: 255 cloudProviderConfig, err = v.vaultGKEConfig(vaultCRD, param) 256 case cloud.AWS, cloud.EKS: 257 cloudProviderConfig, err = v.vaultAWSConfig(vaultCRD, param) 258 case cloud.AKS: 259 cloudProviderConfig, err = v.vaultAzureConfig(vaultCRD, param) 260 default: 261 err = errors.Errorf("vault is not supported on cloud provider %s", param.KubeProvider) 262 } 263 if err != nil { 264 return errors.Wrapf(err, "unable to apply cloud provider config") 265 } 266 } else { 267 log.Logger().Warn("Upgrading Vault CRD from within the pipeline. No changes to the cloud provider specific configuration will occur.") 268 269 existingVaultCRD, err := vault.GetVault(param.VaultOperatorClient, vaultCRD.Name, param.Namespace) 270 if err != nil { 271 return errors.Wrapf(err, "expected to find existing Vault configuration") 272 } 273 274 cloudProviderConfig, err = v.extractCloudProviderConfig(existingVaultCRD) 275 if err != nil { 276 return errors.Wrapf(err, "unable to extract cloud provider specific configuration from Vault CRD %s", vaultCRD.Name) 277 } 278 } 279 280 vaultCRD.Spec.Config["storage"] = cloudProviderConfig.Storage 281 vaultCRD.Spec.Config["seal"] = cloudProviderConfig.Seal 282 vaultCRD.Spec.UnsealConfig = cloudProviderConfig.UnsealConfig 283 vaultCRD.Spec.CredentialsConfig = cloudProviderConfig.CredentialsConfig 284 return nil 285 } 286 287 func (v *defaultVaultCreator) vaultGKEConfig(vaultCRD *v1alpha1.Vault, param VaultCreationParam) (vault.CloudProviderConfig, error) { 288 gcloud := &gke.GCloud{} 289 290 err := gcloud.Login("", true) 291 if err != nil { 292 return vault.CloudProviderConfig{}, errors.Wrap(err, "login into GCP") 293 } 294 295 args := []string{"config", "set", "project", param.GKE.ProjectID} 296 cmd := util.Command{ 297 Name: "gcloud", 298 Args: args, 299 } 300 _, err = cmd.RunWithoutRetry() 301 if err != nil { 302 return vault.CloudProviderConfig{}, err 303 } 304 305 log.Logger().Debugf("Ensure KMS API is enabled") 306 err = gcloud.EnableAPIs(param.GKE.ProjectID, "cloudkms") 307 if err != nil { 308 return vault.CloudProviderConfig{}, errors.Wrap(err, "unable to enable 'cloudkms' API") 309 } 310 311 log.Logger().Debugf("Creating GCP service account for Vault backend") 312 gcpServiceAccountSecretName, err := gkevault.CreateVaultGCPServiceAccount(gcloud, param.KubeClient, vaultCRD.Name, param.Namespace, param.ClusterName, param.GKE.ProjectID) 313 if err != nil { 314 return vault.CloudProviderConfig{}, errors.Wrap(err, "creating GCP service account") 315 } 316 log.Logger().Debugf("'%s' service account created", util.ColorInfo(gcpServiceAccountSecretName)) 317 318 log.Logger().Debugf("Setting up GCP KMS configuration") 319 kmsConfig, err := gkevault.CreateKmsConfig(gcloud, vaultCRD.Name, param.GKE.KeyringName, param.GKE.KeyName, param.GKE.ProjectID) 320 if err != nil { 321 return vault.CloudProviderConfig{}, errors.Wrap(err, "creating KMS configuration") 322 } 323 log.Logger().Debugf("KMS Key '%s' created in keying '%s'", util.ColorInfo(kmsConfig.Key), util.ColorInfo(kmsConfig.Keyring)) 324 325 vaultBucket, err := gkevault.CreateBucket(gcloud, vaultCRD.Name, param.GKE.BucketName, param.GKE.ProjectID, param.GKE.Zone, param.GKE.RecreateBucket, param.BatchMode, param.FileHandles) 326 if err != nil { 327 return vault.CloudProviderConfig{}, errors.Wrap(err, "creating Vault GCS data bucket") 328 } 329 log.Logger().Infof("GCS bucket '%s' was created for Vault backend", util.ColorInfo(vaultBucket)) 330 331 gcpConfig := &vault.GCPConfig{ 332 ProjectId: param.GKE.ProjectID, 333 KmsKeyring: kmsConfig.Keyring, 334 KmsKey: kmsConfig.Key, 335 KmsLocation: kmsConfig.Location, 336 GcsBucket: vaultBucket, 337 } 338 return vault.PrepareGKEVaultCRD(gcpServiceAccountSecretName, gcpConfig) 339 } 340 341 func (v *defaultVaultCreator) vaultAWSConfig(vaultCRD *v1alpha1.Vault, param VaultCreationParam) (vault.CloudProviderConfig, error) { 342 _, clusterRegion, err := session.GetCurrentlyConnectedRegionAndClusterName() 343 if err != nil { 344 return vault.CloudProviderConfig{}, errors.Wrap(err, "finding default AWS region") 345 } 346 347 v.applyDefaultRegionIfEmpty(param.AWS, clusterRegion) 348 349 if param.AWS.AutoCreate { 350 domain := "jenkins-x-domain" 351 username := param.AWS.IAMUsername 352 if username == "" { 353 username = "vault_" + clusterRegion 354 } 355 356 bucketName := param.AWS.S3Bucket 357 if bucketName == "" { 358 bucketName = "vault-unseal." + param.AWS.S3Region + "." + domain 359 } 360 361 valueUUID, err := uuid.NewUUID() 362 if err != nil { 363 return vault.CloudProviderConfig{}, errors.Wrapf(err, "Generating UUID failed") 364 } 365 366 // Create suffix to apply to resources 367 suffixString := valueUUID.String()[:7] 368 var kmsID, s3Name, tableName, accessID, secretKey *string 369 if param.Boot { 370 accessID, secretKey, kmsID, s3Name, tableName, err = awsvault.CreateVaultResourcesBoot(awsvault.ResourceCreationOpts{ 371 Region: clusterRegion, 372 Domain: domain, 373 Username: username, 374 BucketName: bucketName, 375 TableName: autoCreateTableName, 376 AWSTemplatesDir: param.AWS.TemplatesDir, 377 AccessKeyID: param.AWS.AccessKeyID, 378 SecretAccessKey: param.AWS.SecretAccessKey, 379 UniqueSuffix: suffixString, 380 }) 381 } else { 382 // left for non-boot clusters until deprecation 383 accessID, secretKey, kmsID, s3Name, tableName, err = awsvault.CreateVaultResources(awsvault.ResourceCreationOpts{ 384 Region: clusterRegion, 385 Domain: domain, 386 Username: username, 387 BucketName: bucketName, 388 TableName: autoCreateTableName, 389 }) 390 } 391 392 if err != nil { 393 return vault.CloudProviderConfig{}, errors.Wrap(err, "an error occurred while creating the vaultCRD resources") 394 } 395 if s3Name != nil { 396 param.AWS.S3Bucket = *s3Name 397 } 398 if kmsID != nil { 399 param.AWS.KMSKeyID = *kmsID 400 } 401 if tableName != nil { 402 param.AWS.DynamoDBTable = *tableName 403 } 404 if accessID != nil { 405 param.AWS.AccessKeyID = *accessID 406 } 407 if secretKey != nil { 408 param.AWS.SecretAccessKey = *secretKey 409 } 410 411 } else { 412 if param.AWS.S3Bucket == "" { 413 return vault.CloudProviderConfig{}, fmt.Errorf("missing S3 bucket flag") 414 } 415 if param.AWS.KMSKeyID == "" { 416 return vault.CloudProviderConfig{}, fmt.Errorf("missing AWS KMS key id flag") 417 } 418 if param.AWS.AccessKeyID == "" { 419 return vault.CloudProviderConfig{}, fmt.Errorf("missing AWS access key id flag") 420 } 421 if param.AWS.SecretAccessKey == "" { 422 return vault.CloudProviderConfig{}, fmt.Errorf("missing AWS secret access key flag") 423 } 424 } 425 426 awsServiceAccountSecretName, err := awsvault.StoreAWSCredentialsIntoSecret(param.KubeClient, param.AWS.AccessKeyID, param.AWS.SecretAccessKey, vaultCRD.Name, param.Namespace) 427 if err != nil { 428 return vault.CloudProviderConfig{}, errors.Wrap(err, "storing the service account credentials into a secret") 429 } 430 431 vaultConfig := vault.AWSConfig{ 432 AutoCreate: param.AWS.AutoCreate, 433 DynamoDBTable: param.AWS.DynamoDBTable, 434 DynamoDBRegion: param.AWS.DynamoDBRegion, 435 AccessKeyID: param.AWS.AccessKeyID, 436 SecretAccessKey: param.AWS.SecretAccessKey, 437 ProvidedIAMUsername: param.AWS.IAMUsername, 438 AWSUnsealConfig: v1alpha1.AWSUnsealConfig{ 439 KMSKeyID: param.AWS.KMSKeyID, 440 KMSRegion: param.AWS.KMSRegion, 441 S3Bucket: param.AWS.S3Bucket, 442 S3Prefix: param.AWS.S3Prefix, 443 S3Region: param.AWS.S3Region, 444 }, 445 } 446 447 return vault.PrepareAWSVaultCRD(awsServiceAccountSecretName, &vaultConfig) 448 } 449 450 func (v *defaultVaultCreator) extractCloudProviderConfig(vaultCRD *v1alpha1.Vault) (vault.CloudProviderConfig, error) { 451 var cloudProviderConfig = vault.CloudProviderConfig{} 452 453 storageConfig := vaultCRD.Spec.Config["storage"] 454 if storageConfig == nil { 455 return cloudProviderConfig, errors.Errorf("unable to find storage config in Vault CRD %s", vaultCRD.Name) 456 } 457 storage, ok := storageConfig.(map[string]interface{}) 458 if !ok { 459 return cloudProviderConfig, errors.Errorf("unexpected storage config in Vault CRD %s: %v", vaultCRD.Name, storageConfig) 460 } 461 462 sealConfig := vaultCRD.Spec.Config["seal"] 463 if sealConfig == nil { 464 return cloudProviderConfig, errors.Errorf("unable to find seal config in Vault CRD %s", vaultCRD.Name) 465 } 466 seal, ok := sealConfig.(map[string]interface{}) 467 if !ok { 468 return cloudProviderConfig, errors.Errorf("unexpected seal config in Vault CRD %s: %v", vaultCRD.Name, sealConfig) 469 } 470 471 cloudProviderConfig = vault.CloudProviderConfig{ 472 Storage: storage, 473 Seal: seal, 474 UnsealConfig: vaultCRD.Spec.UnsealConfig, 475 CredentialsConfig: vaultCRD.Spec.CredentialsConfig, 476 } 477 478 return cloudProviderConfig, nil 479 } 480 481 // applyDefaultRegionIfEmpty applies the default region to all AWS resources 482 func (v *defaultVaultCreator) applyDefaultRegionIfEmpty(awsParam *AWSParam, defaultRegion string) { 483 if awsParam.DynamoDBRegion == "" { 484 log.Logger().Infof("DynamoDBRegion not specified, defaulting to %s", util.ColorInfo(defaultRegion)) 485 if awsParam.DynamoDBRegion == "" { 486 awsParam.DynamoDBRegion = defaultRegion 487 } 488 } 489 490 if awsParam.KMSRegion == "" { 491 log.Logger().Infof("KMSRegion not specified, defaulting to %s", util.ColorInfo(defaultRegion)) 492 if awsParam.KMSRegion == "" { 493 awsParam.KMSRegion = defaultRegion 494 } 495 } 496 497 if awsParam.S3Region == "" { 498 log.Logger().Infof("S3Region not specified, defaulting to %s", util.ColorInfo(defaultRegion)) 499 if awsParam.S3Region == "" { 500 awsParam.S3Region = defaultRegion 501 } 502 } 503 } 504 505 func (v *defaultVaultCreator) vaultAzureConfig(_ *v1alpha1.Vault, param VaultCreationParam) (vault.CloudProviderConfig, error) { 506 507 vaultConfig := vault.AzureConfig{ 508 AzureUnsealConfig: v1alpha1.AzureUnsealConfig{ 509 KeyVaultName: param.Azure.VaultName, 510 }, 511 StorageAccountName: param.Azure.StorageAccountName, 512 StorageAccountKey: param.Azure.StorageAccountKey, 513 ContainerName: param.Azure.ContainerName, 514 TenantID: param.Azure.TenantID, 515 VaultName: param.Azure.VaultName, 516 KeyName: param.Azure.KeyName, 517 } 518 519 return vault.PrepareAzureVaultCRD(&vaultConfig) 520 }