Skip to content

Latest commit

 

History

History
122 lines (95 loc) · 4.79 KB

contributing-vault-apis.md

File metadata and controls

122 lines (95 loc) · 4.79 KB

Contributing a new Vault API

All vault APIs can be manipulated using the Vault logical client and essentially with three operations: read, write (corresponding to create and update) and delete.

A framework has been created to make it simple to add new types (essentially new secret engine configuration and role types and new authentication engine configuration and role types).

Here are the steps:

  1. Create the CRD type and miutating a validating webhooks:

    operator-sdk create api --group redhatcop --version v1alpha1 --kind MyVaultType --resource --controller
    operator-sdk create webhook --group redhatcop --version v1alpha1 --kind MyVaultType --defaulting --programmatic-validation
  2. Define the My Vault type fields. You should define the vault specific field in an inline type. You should have a one to one mapping between the CRD fields and the Vault api field, except for field with special treatment. Add the path and authentication fields. Add any special treatment field as un-exported and un-marshalled fields. Here is an example:

  type MyVaultTypeSpec struct {
  // Authentication is the kube aoth configuraiton to be used to execute this request
  // +kubebuilder:validation:Required
  Authentication KubeAuthConfiguration `json:"authentication,omitempty"`

  // Path at which to create the role.
  // The final path will be {[spec.authentication.namespace]}/{spec.path}/roles/{metadata.name}.
  // The authentication role must have the following capabilities = [ "create", "read", "update", "delete"] on that path.
  // +kubebuilder:validation:Required
  Path Path `json:"path,omitempty"`

  MyVaultSpecificFields `json:",inline"`
  }

  type MyVaultSpecificFields struct {
    NormalField string `json:"normalField,omitempty"`
    specialField string `json:"-"`
  }
  1. Implement the VaultObejct interface

    var _ vaultutils.VaultObject = &MyVaultType{}

then add the required methods. If you have special treatment field use the PrepareInternalValues method to initialze those fields.

  1. Implements the apis.ConditionsAware interface

    var _ apis.ConditionsAware = &MyVaultType{}
  2. Add needed validation and defaulting to the webhook. Notice that all the resources will need to prevent the path from being changed in the valitating webhooks:

func (r *MyVaultType) Default() {
  authenginemountlog.Info("default", "name", r.Name)
  //add your defaults here
}

func (r *MyVaultType) ValidateUpdate(old runtime.Object) error {
  authenginemountlog.Info("validate update", "name", r.Name)

  // the path cannot be updated
  if r.Spec.Path != old.(*MyVaultType).Spec.Path {
    return errors.New("spec.path cannot be updated")
  }
}
  1. Implement the controller, under normal circumstances this should be straightfornad, just use this code:
  type MyVaultTypeReconciler struct {
    util.ReconcilerBase
    Log            logr.Logger
    ControllerName string
  }

  func (r *MyVaultTypeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    _ = log.FromContext(ctx)
    instance := &redhatcopv1alpha1.MyVaultType{}
    err := r.GetClient().Get(ctx, req.NamespacedName, instance)
    if err != nil {
      if apierrors.IsNotFound(err) {
        return reconcile.Result{}, nil
      }
      return reconcile.Result{}, err
    }

    ctx = context.WithValue(ctx, "kubeClient", r.GetClient())
      ctx = context.WithValue(ctx, "restConfig", r.GetRestConfig())      
    vaultClient, err := instance.Spec.Authentication.GetVaultClient(ctx, instance.Namespace)
    if err != nil {
      r.Log.Error(err, "unable to create vault client", "instance", instance)
      return r.ManageError(ctx, instance, err)
    }
    ctx = context.WithValue(ctx, "vaultClient", vaultClient)
    vaultResource := vaultresourcecontroller.NewVaultResource(&r.ReconcilerBase, instance)

    return vaultResource.Reconcile(ctx, instance)
  }

  func (r *MyVaultTypeReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
      For(&redhatcopv1alpha1.MyVaultType{}).
      Complete(r)
  }
  1. On the main.go update the controller reconciler to use the new operator util structur.
	if err = (&controllers.MyVaultTypeReconciler{
    ReconcilerBase: util.NewReconcilerBase(mgr.GetClient(), mgr.GetScheme(), mgr.GetConfig(), mgr.GetEventRecorderFor("MyVaultType"), mgr.GetAPIReader()),
    Log:            ctrl.Log.WithName("controllers").WithName("MyVaultType"),
    ControllerName: "MyVaultType",
  }).SetupWithManager(mgr); err != nil {
    setupLog.Error(err, "unable to create controller", "controller", "MyVaultType")
    os.Exit(1)
  }