package secretsengine import ( "context" "fmt" "time" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" ) const staticRolePath = "static-role" // staticRoleEntry defines the data required // for a static role, using a set principal type staticRoleEntry struct { Principal string `json:"principal"` Password string `json:"password"` LastVaultRotation time.Time `json:"last_vault_rotation"` } // toResponseData returns response data for a role func (r *staticRoleEntry) toResponseData() map[string]interface{} { respData := map[string]interface{}{ "principal": r.Principal, "last_vault_rotation": r.LastVaultRotation, } return respData } // pathStaticRole extends the Vault API with a `/static-role` // endpoint for the backend. func pathStaticRole(b *krbBackend) []*framework.Path { return []*framework.Path{ { Pattern: staticRolePath + "/" + framework.GenericNameRegex("name"), Fields: map[string]*framework.FieldSchema{ "name": { Type: framework.TypeLowerCaseString, Description: "Name of the role", Required: true, }, "principal": { Type: framework.TypeString, Description: "The principal credentials should be generated for.", Required: true, }, "last_vault_rotation": { Type: framework.TypeDurationSecond, Description: "Last time the credentials were rotated.", }, }, Operations: map[logical.Operation]framework.OperationHandler{ logical.ReadOperation: &framework.PathOperation{ Callback: b.pathRolesRead, }, logical.CreateOperation: &framework.PathOperation{ Callback: b.pathRolesWrite, }, logical.UpdateOperation: &framework.PathOperation{ Callback: b.pathRolesWrite, }, logical.DeleteOperation: &framework.PathOperation{ Callback: b.pathRolesDelete, }, }, HelpSynopsis: pathRoleHelpSynopsis, HelpDescription: pathRoleHelpDescription, }, { Pattern: staticRolePath + "/?$", Operations: map[logical.Operation]framework.OperationHandler{ logical.ListOperation: &framework.PathOperation{ Callback: b.pathRolesList, }, }, HelpSynopsis: pathRoleListHelpSynopsis, HelpDescription: pathRoleListHelpDescription, }, } } // pathRolesList makes a request to Vault storage to retrieve a list of roles for the backend func (b *krbBackend) pathRolesList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { entries, err := req.Storage.List(ctx, staticRolePath) if err != nil { return nil, err } return logical.ListResponse(entries), nil } // pathRolesRead makes a request to Vault storage to read a role and return response data func (b *krbBackend) pathRolesRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { entry, err := b.getRole(ctx, req.Storage, d.Get("name").(string)) if err != nil { return nil, err } if entry == nil { return nil, nil } return &logical.Response{ Data: entry.toResponseData(), }, nil } // pathRolesWrite makes a request to Vault storage to update a role based on the attributes passed to the role configuration func (b *krbBackend) pathRolesWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { rawName, ok := d.GetOk("name") if !ok { return logical.ErrorResponse("missing role name"), nil } name := rawName.(string) roleEntry, err := b.getRole(ctx, req.Storage, name) if err != nil { return nil, err } if roleEntry == nil { roleEntry = &staticRoleEntry{} } createOperation := (req.Operation == logical.CreateOperation) if principal, ok := d.GetOk("principal"); ok { roleEntry.Principal = principal.(string) } else if !ok && createOperation { return nil, fmt.Errorf("missing principal in role") } roleEntry.LastVaultRotation = time.Unix(0, 0) if err := setRole(ctx, req.Storage, name, roleEntry); err != nil { return nil, err } b.backgroundRotation(name, req.Storage) return nil, nil } // pathRolesDelete makes a request to Vault storage to delete a role func (b *krbBackend) pathRolesDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { err := req.Storage.Delete(ctx, staticRolePath+"/"+d.Get("name").(string)) if err != nil { return nil, fmt.Errorf("error deleting krb role: %w", err) } return nil, nil } // setRole adds the role to the Vault storage API func setRole(ctx context.Context, s logical.Storage, name string, roleEntry *staticRoleEntry) error { entry, err := logical.StorageEntryJSON(staticRolePath+"/"+name, roleEntry) if err != nil { return err } if entry == nil { return fmt.Errorf("failed to create storage entry for role") } if err := s.Put(ctx, entry); err != nil { return err } return nil } // getRole gets the role from the Vault storage API func (b *krbBackend) getRole(ctx context.Context, s logical.Storage, name string) (*staticRoleEntry, error) { if name == "" { return nil, fmt.Errorf("missing role name") } entry, err := s.Get(ctx, staticRolePath+"/"+name) if err != nil { return nil, err } if entry == nil { return nil, nil } var role staticRoleEntry if err := entry.DecodeJSON(&role); err != nil { return nil, err } return &role, nil } const ( pathRoleHelpSynopsis = `Manages the Vault roles for credentials for individual Kerberos principals.` pathRoleHelpDescription = ` This path allows you to read and write roles used to generate credentials for individual Kerberos principals. You can configure a role to manage a principal's password by setting the principal field. ` pathRoleListHelpSynopsis = `List the existing roles in HashiCups backend` pathRoleListHelpDescription = `Roles will be listed by the role name.` )