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  }