diff options
Diffstat (limited to 'path_config.go')
-rw-r--r-- | path_config.go | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/path_config.go b/path_config.go new file mode 100644 index 0000000..a89488b --- /dev/null +++ b/path_config.go @@ -0,0 +1,253 @@ +package secretsengine + +import ( + "context" + "errors" + "fmt" + "time" + + "git.tardisproject.uk/tcmal/vault-plugin-kerberos-secrets/config" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" +) + +const ( + configStoragePath = "config" + defaultCtxTimeout = 1 * time.Minute +) + +// ConfigPaths extends the Vault API with a `/config` endpoint for the backend. +func pathConfig(b *krbBackend) []*framework.Path { + return []*framework.Path{&framework.Path{ + Pattern: "config", + Fields: configSchema(), + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.pathConfigRead, + }, + logical.CreateOperation: &framework.PathOperation{ + Callback: b.pathConfigWrite, + }, + logical.UpdateOperation: &framework.PathOperation{ + Callback: b.pathConfigWrite, + }, + logical.DeleteOperation: &framework.PathOperation{ + Callback: b.pathConfigDelete, + }, + }, + ExistenceCheck: b.pathConfigExistenceCheck, + HelpSynopsis: pathConfigHelpSynopsis, + HelpDescription: pathConfigHelpDescription, + }, + } +} + +func configSchema() map[string]*framework.FieldSchema { + return map[string]*framework.FieldSchema{ + "realm": { + Type: framework.TypeString, + Description: "The realm to authenticate against", + Required: true, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Realm", + Sensitive: false, + }, + }, + "username": { + Type: framework.TypeString, + Description: "The username to access kadmin with", + Required: true, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Username", + Sensitive: false, + }, + }, + "password": { + Type: framework.TypeString, + Description: "The user's password to access kadmin with", + Required: true, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Password", + Sensitive: true, + }, + }, + "kdc": { + Type: framework.TypeCommaStringSlice, + Description: "Available KDCs for the realm", + Required: true, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "KDCs", + Sensitive: false, + }, + }, + "admin_server": { + Type: framework.TypeCommaStringSlice, + Description: "Available admin servers for the realm", + Required: true, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Admin Servers", + Sensitive: false, + }, + }, + "kpasswd_server": { + Type: framework.TypeCommaStringSlice, + Description: "KPasswd servers for the realm", + Required: true, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "KPasswd Servers", + Sensitive: false, + }, + }, + } + +} + +// pathConfigExistenceCheck verifies if the configuration exists. +func (b *krbBackend) pathConfigExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) { + out, err := req.Storage.Get(ctx, req.Path) + if err != nil { + return false, fmt.Errorf("existence check failed: %w", err) + } + + return out != nil, nil +} + +// pathConfigRead reads the configuration and outputs non-sensitive information. +func (b *krbBackend) pathConfigRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + config, err := getConfig(ctx, req.Storage) + if err != nil { + return nil, err + } + + if config == nil { + return nil, fmt.Errorf("config not yet created") + } + + return &logical.Response{ + Data: map[string]interface{}{ + "realm": config.Realm, + "kdc": config.KDC, + "admin_server": config.AdminServer, + "kpasswd_server": config.KPasswdServer, + "username": config.Username, + }, + }, nil +} + +// pathConfigWrite updates the configuration for the backend +func (b *krbBackend) pathConfigWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + c, err := getConfig(ctx, req.Storage) + if err != nil { + return nil, err + } + + createOperation := (req.Operation == logical.CreateOperation) + + if c == nil { + if !createOperation { + return nil, errors.New("config not found during update operation") + } + c = new(config.Config) + } + + if realm, ok := data.GetOk("realm"); ok { + c.Realm = realm.(string) + } else if !ok && createOperation { + return nil, fmt.Errorf("missing realm in configuration") + } + + // TODO: Also validate these aren't empty + if kdc, ok := data.GetOk("kdc"); ok { + c.KDC = kdc.([]string) + } else if !ok && createOperation { + return nil, fmt.Errorf("missing KDCs in configuration") + } + if len(c.KDC) == 0 { + return nil, fmt.Errorf("no KDCs specified") + } + + if admin_server, ok := data.GetOk("admin_server"); ok { + c.AdminServer = admin_server.([]string) + } else if !ok && createOperation { + return nil, fmt.Errorf("missing admin servers in configuration") + } + if len(c.AdminServer) == 0 { + return nil, fmt.Errorf("no admin servers specified") + } + + if kpasswd_server, ok := data.GetOk("kpasswd_server"); ok { + c.KPasswdServer = kpasswd_server.([]string) + } else if !ok && createOperation { + return nil, fmt.Errorf("missing kpasswd servers in configuration") + } + if len(c.KPasswdServer) == 0 { + return nil, fmt.Errorf("no kpasswd servers specified") + } + + if username, ok := data.GetOk("username"); ok { + c.Username = username.(string) + } else if !ok && createOperation { + return nil, fmt.Errorf("missing username in configuration") + } + + if password, ok := data.GetOk("password"); ok { + c.Password = password.(string) + } else if !ok && createOperation { + return nil, fmt.Errorf("missing password in configuration") + } + + entry, err := logical.StorageEntryJSON(configStoragePath, c) + if err != nil { + return nil, err + } + + if err := req.Storage.Put(ctx, entry); err != nil { + return nil, err + } + + b.reset() + + return nil, nil +} + +// pathConfigDelete removes the configuration for the backend +func (b *krbBackend) pathConfigDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + err := req.Storage.Delete(ctx, configStoragePath) + + if err == nil { + b.reset() + } + + return nil, err +} + +func getConfig(ctx context.Context, s logical.Storage) (*config.Config, error) { + entry, err := s.Get(ctx, configStoragePath) + if err != nil { + return nil, err + } + + if entry == nil { + return nil, nil + } + + c := new(config.Config) + if err := entry.DecodeJSON(&c); err != nil { + return nil, fmt.Errorf("error reading root configuration: %w", err) + } + + // return the config, we are done + return c, nil +} + +// pathConfigHelpSynopsis summarizes the help text for the configuration +const pathConfigHelpSynopsis = `Configure the Kerberos backend.` + +// pathConfigHelpDescription describes the help text for the configuration +const pathConfigHelpDescription = ` +The Kerberos secret backend requires credentials and +connection details for the Kerberos servers. + +The user provided must be able to create & modify principals +in order for this backend to work correctly. +` |