github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/kube/vault/vault.go (about) 1 package vault 2 3 import ( 4 "bytes" 5 "fmt" 6 7 "github.com/olli-ai/jx/v2/pkg/util/json" 8 "k8s.io/apimachinery/pkg/types" 9 10 "github.com/banzaicloud/bank-vaults/operator/pkg/apis/vault/v1alpha1" 11 "github.com/banzaicloud/bank-vaults/operator/pkg/client/clientset/versioned" 12 vaultapi "github.com/hashicorp/vault/api" 13 "github.com/jenkins-x/jx-logging/pkg/log" 14 "github.com/olli-ai/jx/v2/pkg/kube" 15 "github.com/olli-ai/jx/v2/pkg/kube/cluster" 16 "github.com/olli-ai/jx/v2/pkg/kube/naming" 17 "github.com/olli-ai/jx/v2/pkg/kube/serviceaccount" 18 "github.com/olli-ai/jx/v2/pkg/kube/services" 19 "github.com/olli-ai/jx/v2/pkg/util" 20 "github.com/olli-ai/jx/v2/pkg/vault" 21 "github.com/pkg/errors" 22 v1 "k8s.io/api/core/v1" 23 apierrors "k8s.io/apimachinery/pkg/api/errors" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/client-go/kubernetes" 26 ) 27 28 const ( 29 BankVaultsImage = "banzaicloud/bank-vaults" 30 VaultOperatorImage = "banzaicloud/vault-operator" 31 VaultImage = "vault" 32 33 gcpServiceAccountEnv = "GOOGLE_APPLICATION_CREDENTIALS" 34 gcpServiceAccountPath = "/etc/gcp/service-account.json" 35 36 awsServiceAccountEnv = "AWS_SHARED_CREDENTIALS_FILE" 37 awsServiceAccountPath = "/etc/aws/credentials" 38 39 vaultAuthName = "auth" 40 vaultAuthType = "kubernetes" 41 vaultAuthTTL = "1h" 42 43 vaultRoleName = "vault-auth" 44 45 vaultSecretEngines = "secrets" 46 defaultNumVaults = 1 47 defaultInternalVaultURL = "http://%s:" + vault.DefaultVaultPort 48 ) 49 50 // GCPConfig keeps the configuration for Google Cloud 51 type GCPConfig struct { 52 ProjectId string 53 KmsKeyring string 54 KmsKey string 55 KmsLocation string 56 GcsBucket string 57 } 58 59 // GCSConfig Google Cloud Storage config for Vault backend 60 type GCSConfig struct { 61 Bucket string `json:"bucket"` 62 HaEnabled string `json:"ha_enabled"` 63 } 64 65 // AWSConfig keeps the vault configuration for AWS 66 type AWSConfig struct { 67 v1alpha1.AWSUnsealConfig 68 AutoCreate bool 69 DynamoDBTable string 70 DynamoDBRegion string 71 AccessKeyID string 72 SecretAccessKey string 73 ProvidedIAMUsername string 74 } 75 76 // AzureConfig keeps the vault configuration for Azure 77 type AzureConfig struct { 78 v1alpha1.AzureUnsealConfig 79 StorageAccountName string 80 StorageAccountKey string 81 ContainerName string 82 TenantID string 83 VaultName string 84 KeyName string 85 } 86 87 // DynamoDBConfig AWS DynamoDB config for Vault backend 88 type DynamoDBConfig struct { 89 HaEnabled string `json:"ha_enabled"` 90 Region string `json:"region"` 91 Table string `json:"table"` 92 AccessKeyID string `json:"access_key"` 93 SecretAccessKey string `json:"secret_key"` 94 } 95 96 // AzureStorageConfig Azure Storage config for Vault backend 97 type AzureStorageConfig struct { 98 AccountName string `json:"accountName"` 99 AccountKey string `json:"accountKey"` 100 ContainerName string `json:"container"` 101 } 102 103 // VaultAuths list of vault authentications 104 type VaultAuths []VaultAuth 105 106 // VaultAuth vault auth configuration 107 type VaultAuth struct { 108 Roles []VaultRole `json:"roles"` 109 Type string `json:"type"` 110 } 111 112 // VaultRole role configuration for VaultAuth 113 type VaultRole struct { 114 BoundServiceAccountNames string `json:"bound_service_account_names"` 115 BoundServiceAccountNamespaces string `json:"bound_service_account_namespaces"` 116 Name string `json:"name"` 117 Policies string `json:"policies"` 118 TTL string `json:"ttl"` 119 } 120 121 // VaultPolicies list of vault policies 122 type VaultPolicies []VaultPolicy 123 124 // VaultPolicy vault policy 125 type VaultPolicy struct { 126 Name string `json:"name"` 127 Rules string `json:"rules"` 128 } 129 130 // Tcp address for vault server 131 type Tcp struct { 132 Address string `json:"address"` 133 TlsDisable bool `json:"tls_disable"` 134 } 135 136 // Listener vault server listener 137 type Listener struct { 138 Tcp Tcp `json:"tcp"` 139 } 140 141 // Telemetry address for telemetry server 142 type Telemetry struct { 143 StatsdAddress string `json:"statsd_address"` 144 } 145 146 // Storage configuration for Vault storage 147 type Storage struct { 148 GCS *GCSConfig `json:"gcs,omitempty"` 149 DynamoDB *DynamoDBConfig `json:"dynamodb,omitempty"` 150 AzureStorage *AzureStorageConfig `json:"azure,omitempty"` 151 } 152 153 // SecretEngine configuration for secret engine 154 type SecretEngine struct { 155 vaultapi.MountInput 156 Path string `json:"path"` 157 } 158 159 // Seal configuration for Vault auto-unseal 160 type Seal struct { 161 GcpCkms *GCPSealConfig `json:"gcpckms,omitempty"` 162 AWSKms *AWSSealConfig `json:"awskms,omitempty"` 163 AzureKeyVault *AzureSealConfig `json:"azurekeyvault,omitempty"` 164 } 165 166 // GCPSealConfig Google Cloud KMS config for vault auto-unseal 167 type GCPSealConfig struct { 168 Credentials string `json:"credentials,omitempty"` 169 Project string `json:"project,omitempty"` 170 Region string `json:"region,omitempty"` 171 KeyRing string `json:"key_ring,omitempty"` 172 CryptoKey string `json:"crypto_key,omitempty"` 173 } 174 175 // AWSSealConfig AWS KMS config for vault auto-unseal 176 type AWSSealConfig struct { 177 Region string `json:"region,omitempty"` 178 AccessKey string `json:"access_key,omitempty"` 179 SecretKey string `json:"secret_key,omitempty"` 180 KmsKeyID string `json:"kms_key_id,omitempty"` 181 Endpoint string `json:"endpoint,omitempty"` 182 } 183 184 // AzureSealConfig Azure Key Vault config for vault auto-unseal 185 type AzureSealConfig struct { 186 TenantID string `json:"tenant_id,omitempty"` 187 VaultName string `json:"vault_name,omitempty"` 188 KeyName string `json:"key_name,omitempty"` 189 } 190 191 // CloudProviderConfig is a wrapper around the cloud provider specific elements of the Vault CRD configuration 192 type CloudProviderConfig struct { 193 Storage map[string]interface{} 194 Seal map[string]interface{} 195 UnsealConfig v1alpha1.UnsealConfig 196 CredentialsConfig v1alpha1.CredentialsConfig 197 } 198 199 // SystemVaultName returns the name of the system vault based on the cluster name 200 func SystemVaultName(kuber kube.Kuber) (string, error) { 201 clusterName, err := cluster.ShortName(kuber) 202 if err != nil { 203 return "", err 204 } 205 return SystemVaultNameForCluster(clusterName), nil 206 } 207 208 // SystemVaultNameForCluster returns the system vault name from a given cluster name 209 func SystemVaultNameForCluster(clusterName string) string { 210 shortClusterName := naming.ToValidNameTruncated(clusterName, 16) 211 fullName := fmt.Sprintf("%s-%s", vault.SystemVaultNamePrefix, shortClusterName) 212 return naming.ToValidNameTruncated(fullName, 22) 213 } 214 215 // PrepareGKEVaultCRD creates a new vault backed by GCP KMS and storage 216 func PrepareGKEVaultCRD(gcpServiceAccountSecretName string, gcpConfig *GCPConfig) (CloudProviderConfig, error) { 217 storage := Storage{ 218 GCS: &GCSConfig{ 219 Bucket: gcpConfig.GcsBucket, 220 HaEnabled: "true", 221 }, 222 } 223 storageConfig, err := util.ToObjectMap(storage) 224 if err != nil { 225 return CloudProviderConfig{}, errors.Wrap(err, "error creating storage config") 226 } 227 228 seal := Seal{ 229 GcpCkms: &GCPSealConfig{ 230 Credentials: gcpServiceAccountPath, 231 Project: gcpConfig.ProjectId, 232 Region: gcpConfig.KmsLocation, 233 KeyRing: gcpConfig.KmsKeyring, 234 CryptoKey: gcpConfig.KmsKey, 235 }, 236 } 237 sealConfig, err := util.ToObjectMap(seal) 238 if err != nil { 239 return CloudProviderConfig{}, errors.Wrap(err, "error creating seal config") 240 } 241 242 unsealConfig := v1alpha1.UnsealConfig{ 243 Google: &v1alpha1.GoogleUnsealConfig{ 244 KMSKeyRing: gcpConfig.KmsKeyring, 245 KMSCryptoKey: gcpConfig.KmsKey, 246 KMSLocation: gcpConfig.KmsLocation, 247 KMSProject: gcpConfig.ProjectId, 248 StorageBucket: gcpConfig.GcsBucket, 249 }, 250 } 251 credentialsConfig := v1alpha1.CredentialsConfig{ 252 Env: gcpServiceAccountEnv, 253 Path: gcpServiceAccountPath, 254 SecretName: gcpServiceAccountSecretName, 255 } 256 return CloudProviderConfig{storageConfig, sealConfig, unsealConfig, credentialsConfig}, nil 257 } 258 259 // PrepareAWSVaultCRD creates a new vault backed by AWS KMS and DynamoDB storage 260 func PrepareAWSVaultCRD(awsServiceAccountSecretName string, awsConfig *AWSConfig) (CloudProviderConfig, error) { 261 storage := Storage{ 262 DynamoDB: &DynamoDBConfig{ 263 HaEnabled: "true", 264 Region: awsConfig.DynamoDBRegion, 265 Table: awsConfig.DynamoDBTable, 266 AccessKeyID: awsConfig.AccessKeyID, 267 SecretAccessKey: awsConfig.SecretAccessKey, 268 }, 269 } 270 storageConfig, err := util.ToObjectMap(storage) 271 if err != nil { 272 return CloudProviderConfig{}, errors.Wrap(err, "error creating storage config") 273 } 274 275 seal := Seal{ 276 AWSKms: &AWSSealConfig{ 277 Region: awsConfig.KMSRegion, 278 AccessKey: awsConfig.AccessKeyID, 279 SecretKey: awsConfig.SecretAccessKey, 280 KmsKeyID: awsConfig.KMSKeyID, 281 }, 282 } 283 sealConfig, err := util.ToObjectMap(seal) 284 if err != nil { 285 return CloudProviderConfig{}, errors.Wrap(err, "error creating seal config") 286 } 287 288 unsealConfig := v1alpha1.UnsealConfig{ 289 AWS: &awsConfig.AWSUnsealConfig, 290 } 291 credentialsConfig := v1alpha1.CredentialsConfig{ 292 Env: awsServiceAccountEnv, 293 Path: awsServiceAccountPath, 294 SecretName: awsServiceAccountSecretName, 295 } 296 return CloudProviderConfig{storageConfig, sealConfig, unsealConfig, credentialsConfig}, nil 297 } 298 299 // PrepareAzureVaultCRD creates a new vault backed by Azure Key Vault and Azure Storage 300 func PrepareAzureVaultCRD(azureConfig *AzureConfig) (CloudProviderConfig, error) { 301 storage := Storage{ 302 AzureStorage: &AzureStorageConfig{ 303 AccountName: azureConfig.StorageAccountName, 304 AccountKey: azureConfig.StorageAccountKey, 305 ContainerName: azureConfig.ContainerName, 306 }, 307 } 308 storageConfig, err := util.ToObjectMap(storage) 309 if err != nil { 310 return CloudProviderConfig{}, errors.Wrap(err, "error creating storage config") 311 } 312 313 seal := Seal{ 314 AzureKeyVault: &AzureSealConfig{ 315 TenantID: azureConfig.TenantID, 316 VaultName: azureConfig.VaultName, 317 KeyName: azureConfig.KeyName, 318 }, 319 } 320 sealConfig, err := util.ToObjectMap(seal) 321 if err != nil { 322 return CloudProviderConfig{}, errors.Wrap(err, "error creating seal config") 323 } 324 325 unsealConfig := v1alpha1.UnsealConfig{ 326 Azure: &azureConfig.AzureUnsealConfig, 327 } 328 credentialsConfig := v1alpha1.CredentialsConfig{} 329 return CloudProviderConfig{storageConfig, sealConfig, unsealConfig, credentialsConfig}, nil 330 } 331 332 // NewVaultCRD creates and initializes a new Vault instance. 333 func NewVaultCRD(kubeClient kubernetes.Interface, name string, ns string, images map[string]string, 334 authServiceAccount string, authServiceAccountNamespace string, secretsPathPrefix string) (*v1alpha1.Vault, error) { 335 336 err := createVaultServiceAccount(kubeClient, ns, name) 337 if err != nil { 338 return nil, errors.Wrapf(err, "creating the vault service account '%s'", name) 339 } 340 341 err = ensureVaultRoleBinding(kubeClient, ns, vaultRoleName, name, name) 342 if err != nil { 343 return nil, errors.Wrapf(err, "ensuring vault cluster role binding '%s' is created", name) 344 } 345 346 if secretsPathPrefix == "" { 347 secretsPathPrefix = vault.DefaultSecretsPathPrefix 348 } 349 pathRule := &vault.PathRule{ 350 Path: []vault.PathPolicy{{ 351 Prefix: secretsPathPrefix, 352 Capabilities: vault.DefaultSecretsCapabiltities, 353 }}, 354 } 355 vaultRule, err := pathRule.String() 356 if err != nil { 357 return nil, errors.Wrap(err, "encoding the policies for secret path") 358 } 359 360 vault := &v1alpha1.Vault{ 361 TypeMeta: metav1.TypeMeta{ 362 Kind: "Vault", 363 APIVersion: "vault.banzaicloud.com/v1alpha1", 364 }, 365 ObjectMeta: metav1.ObjectMeta{ 366 Name: name, 367 Namespace: ns, 368 }, 369 Spec: v1alpha1.VaultSpec{ 370 Size: defaultNumVaults, 371 Image: images[VaultImage], 372 BankVaultsImage: images[BankVaultsImage], 373 ServiceType: string(v1.ServiceTypeClusterIP), 374 ServiceAccount: name, 375 Config: map[string]interface{}{ 376 "api_addr": fmt.Sprintf("http://%s.%s:%s", name, ns, vault.DefaultVaultPort), 377 "disable_clustering": true, 378 "listener": Listener{ 379 Tcp: Tcp{ 380 Address: fmt.Sprintf("0.0.0.0:%s", vault.DefaultVaultPort), 381 TlsDisable: true, 382 }, 383 }, 384 "telemetry": Telemetry{ 385 StatsdAddress: "localhost:9125", 386 }, 387 "ui": true, 388 }, 389 ExternalConfig: map[string]interface{}{ 390 vaultAuthName: []VaultAuth{ 391 { 392 Roles: []VaultRole{ 393 { 394 395 BoundServiceAccountNames: authServiceAccount, 396 BoundServiceAccountNamespaces: authServiceAccountNamespace, 397 Name: authServiceAccount, 398 Policies: vault.PathRulesName, 399 TTL: vaultAuthTTL, 400 }, 401 }, 402 Type: vaultAuthType, 403 }, 404 }, 405 vault.PoliciesName: []VaultPolicy{ 406 { 407 Name: vault.PathRulesName, 408 Rules: vaultRule, 409 }, 410 }, 411 vaultSecretEngines: []SecretEngine{ 412 { 413 Path: vault.DefaultSecretsPath, 414 MountInput: vaultapi.MountInput{ 415 Type: "kv", 416 Description: "KV secret engine", 417 Local: false, 418 SealWrap: false, 419 Options: map[string]string{ 420 "version": "2", 421 }, 422 Config: vaultapi.MountConfigInput{ 423 ForceNoCache: true, 424 }, 425 }, 426 }, 427 }, 428 }, 429 }, 430 } 431 432 return vault, err 433 } 434 435 func createVaultServiceAccount(client kubernetes.Interface, namespace string, name string) error { 436 _, err := serviceaccount.CreateServiceAccount(client, namespace, name) 437 if err != nil { 438 return errors.Wrap(err, "creating vault service account") 439 } 440 return nil 441 } 442 443 func ensureVaultRoleBinding(client kubernetes.Interface, namespace string, roleName string, 444 roleBindingName string, serviceAccount string) error { 445 apiGroups := []string{"authentication.k8s.io"} 446 resources := []string{"tokenreviews"} 447 verbs := []string{"*"} 448 found := kube.IsClusterRole(client, roleName) 449 if found { 450 err := kube.DeleteClusterRole(client, roleName) 451 if err != nil { 452 return errors.Wrapf(err, "deleting the existing cluster role '%s'", roleName) 453 } 454 } 455 err := kube.CreateClusterRole(client, namespace, roleName, apiGroups, resources, verbs) 456 if err != nil { 457 return errors.Wrapf(err, "creating the cluster role '%s' for vault", roleName) 458 } 459 460 found = kube.IsClusterRoleBinding(client, roleBindingName) 461 if found { 462 err := kube.DeleteClusterRoleBinding(client, roleBindingName) 463 if err != nil { 464 return errors.Wrapf(err, "deleting the existing cluster role binding '%s'", roleBindingName) 465 } 466 } 467 468 err = kube.CreateClusterRoleBinding(client, namespace, roleBindingName, serviceAccount, roleName) 469 if err != nil { 470 return errors.Wrapf(err, "creating the cluster role binding '%s' for vault", roleBindingName) 471 } 472 return nil 473 } 474 475 // FindVault checks if a vault is available 476 func FindVault(vaultOperatorClient versioned.Interface, name string, ns string) bool { 477 _, err := GetVault(vaultOperatorClient, name, ns) 478 if err != nil { 479 log.Logger().Debugf("vault %s not found in namespace %s, err is %s", name, ns, err) 480 return false 481 } 482 return true 483 } 484 485 // GetVault gets a specific vault 486 func GetVault(vaultOperatorClient versioned.Interface, name string, ns string) (*v1alpha1.Vault, error) { 487 return vaultOperatorClient.VaultV1alpha1().Vaults(ns).Get(name, metav1.GetOptions{}) 488 } 489 490 // GetVaults returns all vaults available in a given namespaces 491 func GetVaults(client kubernetes.Interface, vaultOperatorClient versioned.Interface, ns string, useIngressURL bool) ([]*vault.Vault, error) { 492 vaultList, err := vaultOperatorClient.VaultV1alpha1().Vaults(ns).List(metav1.ListOptions{}) 493 if err != nil { 494 return nil, errors.Wrapf(err, "listing vaults in namespace '%s'", ns) 495 } 496 497 vaults := []*vault.Vault{} 498 for _, v := range vaultList.Items { 499 vaultName := v.Name 500 vaultAuthSaName := GetAuthSaName(v) 501 502 // default to using internal kubernetes service dns name for vault endpoint 503 vaultURL := fmt.Sprintf(defaultInternalVaultURL, vaultName) 504 if useIngressURL { 505 vaultURL, err = services.FindIngressURL(client, ns, vaultName) 506 if err != nil || vaultURL == "" { 507 log.Logger().Debugf("Cannot finding the vault ingress url for vault %s", vaultName) 508 // skip this vault since cannot be used without the ingress 509 continue 510 } 511 } 512 513 vault := vault.Vault{ 514 Name: vaultName, 515 Namespace: ns, 516 URL: vaultURL, 517 ServiceAccountName: vaultAuthSaName, 518 } 519 vaults = append(vaults, &vault) 520 } 521 return vaults, nil 522 } 523 524 // DeleteVault delete a Vault resource 525 func DeleteVault(vaultOperatorClient versioned.Interface, name string, ns string) error { 526 return vaultOperatorClient.VaultV1alpha1().Vaults(ns).Delete(name, &metav1.DeleteOptions{}) 527 } 528 529 // GetAuthSaName gets the Auth Service Account name for the vault 530 func GetAuthSaName(vault v1alpha1.Vault) string { 531 // This is nasty, but the ExternalConfig member of VaultSpec is just defined as a map[string]interface{} :-( 532 authArray := vault.Spec.ExternalConfig["auth"] 533 authObject := authArray.([]interface{})[0] 534 roleArray := authObject.(map[string]interface{})["roles"] 535 roleObject := roleArray.([]interface{})[0] 536 name := roleObject.(map[string]interface{})["name"] 537 538 return name.(string) 539 } 540 541 // CreateOrUpdateVault creates the specified Vault CRD if it does not exist or updates it otherwise. 542 func CreateOrUpdateVault(vault *v1alpha1.Vault, vaultOperatorClient versioned.Interface, ns string) error { 543 vaultExists := false 544 existingVault, err := GetVault(vaultOperatorClient, vault.Name, ns) 545 if err == nil { 546 vaultExists = true 547 } else { 548 statusError, ok := err.(*apierrors.StatusError) 549 if ok && statusError.ErrStatus.Code == 404 { 550 vaultExists = false 551 } else { 552 return errors.Wrapf(err, "unable to check for existence of vault '%s' in namespace '%s'", vault.Name, ns) 553 } 554 } 555 556 if vaultExists { 557 vaultName := existingVault.ObjectMeta.Name 558 patch, err := json.CreatePatch(existingVault, vault) 559 if err != nil { 560 return errors.Wrapf(err, "unable to create path for update of of vault '%s' in namespace '%s'", vault.Name, ns) 561 } 562 if bytes.Equal(patch, []byte("[]")) { 563 return nil 564 } 565 _, err = vaultOperatorClient.VaultV1alpha1().Vaults(ns).Patch(vaultName, types.JSONPatchType, patch) 566 } else { 567 _, err = vaultOperatorClient.VaultV1alpha1().Vaults(ns).Create(vault) 568 } 569 570 op := "create" 571 if vaultExists { 572 op = "update" 573 } 574 if err != nil { 575 return errors.Wrapf(err, "unable to %s vault '%s' in namespace '%s'", op, vault.Name, ns) 576 } 577 log.Logger().Infof("Vault '%s' in namespace '%s' %sd ", util.ColorInfo(vault.Name), util.ColorInfo(ns), op) 578 579 return nil 580 }