| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package dbplugin |
| |
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "time" |
| |
| "google.golang.org/grpc" |
| "google.golang.org/grpc/codes" |
| "google.golang.org/grpc/status" |
| |
| "github.com/golang/protobuf/ptypes" |
| "github.com/hashicorp/vault/sdk/helper/pluginutil" |
| ) |
| |
| var ( |
| ErrPluginShutdown = errors.New("plugin shutdown") |
| ErrPluginStaticUnsupported = errors.New("database plugin does not support Static Accounts") |
| ) |
| |
| // ---- gRPC Server domain ---- |
| |
| type gRPCServer struct { |
| UnimplementedDatabaseServer |
| |
| impl Database |
| } |
| |
| func (s *gRPCServer) Type(context.Context, *Empty) (*TypeResponse, error) { |
| t, err := s.impl.Type() |
| if err != nil { |
| return nil, err |
| } |
| |
| return &TypeResponse{ |
| Type: t, |
| }, nil |
| } |
| |
| func (s *gRPCServer) CreateUser(ctx context.Context, req *CreateUserRequest) (*CreateUserResponse, error) { |
| e, err := ptypes.Timestamp(req.Expiration) |
| if err != nil { |
| return nil, err |
| } |
| |
| u, p, err := s.impl.CreateUser(ctx, *req.Statements, *req.UsernameConfig, e) |
| |
| return &CreateUserResponse{ |
| Username: u, |
| Password: p, |
| }, err |
| } |
| |
| func (s *gRPCServer) RenewUser(ctx context.Context, req *RenewUserRequest) (*Empty, error) { |
| e, err := ptypes.Timestamp(req.Expiration) |
| if err != nil { |
| return nil, err |
| } |
| err = s.impl.RenewUser(ctx, *req.Statements, req.Username, e) |
| return &Empty{}, err |
| } |
| |
| func (s *gRPCServer) RevokeUser(ctx context.Context, req *RevokeUserRequest) (*Empty, error) { |
| err := s.impl.RevokeUser(ctx, *req.Statements, req.Username) |
| return &Empty{}, err |
| } |
| |
| func (s *gRPCServer) RotateRootCredentials(ctx context.Context, req *RotateRootCredentialsRequest) (*RotateRootCredentialsResponse, error) { |
| resp, err := s.impl.RotateRootCredentials(ctx, req.Statements) |
| if err != nil { |
| return nil, err |
| } |
| |
| respConfig, err := json.Marshal(resp) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &RotateRootCredentialsResponse{ |
| Config: respConfig, |
| }, err |
| } |
| |
| func (s *gRPCServer) Initialize(ctx context.Context, req *InitializeRequest) (*Empty, error) { |
| _, err := s.Init(ctx, &InitRequest{ |
| Config: req.Config, |
| VerifyConnection: req.VerifyConnection, |
| }) |
| return &Empty{}, err |
| } |
| |
| func (s *gRPCServer) Init(ctx context.Context, req *InitRequest) (*InitResponse, error) { |
| config := map[string]interface{}{} |
| err := json.Unmarshal(req.Config, &config) |
| if err != nil { |
| return nil, err |
| } |
| |
| resp, err := s.impl.Init(ctx, config, req.VerifyConnection) |
| if err != nil { |
| return nil, err |
| } |
| |
| respConfig, err := json.Marshal(resp) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &InitResponse{ |
| Config: respConfig, |
| }, err |
| } |
| |
| func (s *gRPCServer) Close(_ context.Context, _ *Empty) (*Empty, error) { |
| s.impl.Close() |
| return &Empty{}, nil |
| } |
| |
| func (s *gRPCServer) GenerateCredentials(ctx context.Context, _ *Empty) (*GenerateCredentialsResponse, error) { |
| p, err := s.impl.GenerateCredentials(ctx) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &GenerateCredentialsResponse{ |
| Password: p, |
| }, nil |
| } |
| |
| func (s *gRPCServer) SetCredentials(ctx context.Context, req *SetCredentialsRequest) (*SetCredentialsResponse, error) { |
| username, password, err := s.impl.SetCredentials(ctx, *req.Statements, *req.StaticUserConfig) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &SetCredentialsResponse{ |
| Username: username, |
| Password: password, |
| }, err |
| } |
| |
| // ---- gRPC client domain ---- |
| |
| type gRPCClient struct { |
| client DatabaseClient |
| clientConn *grpc.ClientConn |
| |
| doneCtx context.Context |
| } |
| |
| func (c *gRPCClient) Type() (string, error) { |
| resp, err := c.client.Type(c.doneCtx, &Empty{}) |
| if err != nil { |
| return "", err |
| } |
| |
| return resp.Type, err |
| } |
| |
| func (c *gRPCClient) CreateUser(ctx context.Context, statements Statements, usernameConfig UsernameConfig, expiration time.Time) (username string, password string, err error) { |
| t, err := ptypes.TimestampProto(expiration) |
| if err != nil { |
| return "", "", err |
| } |
| |
| ctx, cancel := context.WithCancel(ctx) |
| quitCh := pluginutil.CtxCancelIfCanceled(cancel, c.doneCtx) |
| defer close(quitCh) |
| defer cancel() |
| |
| resp, err := c.client.CreateUser(ctx, &CreateUserRequest{ |
| Statements: &statements, |
| UsernameConfig: &usernameConfig, |
| Expiration: t, |
| }) |
| if err != nil { |
| if c.doneCtx.Err() != nil { |
| return "", "", ErrPluginShutdown |
| } |
| |
| return "", "", err |
| } |
| |
| return resp.Username, resp.Password, err |
| } |
| |
| func (c *gRPCClient) RenewUser(ctx context.Context, statements Statements, username string, expiration time.Time) error { |
| t, err := ptypes.TimestampProto(expiration) |
| if err != nil { |
| return err |
| } |
| |
| ctx, cancel := context.WithCancel(ctx) |
| quitCh := pluginutil.CtxCancelIfCanceled(cancel, c.doneCtx) |
| defer close(quitCh) |
| defer cancel() |
| |
| _, err = c.client.RenewUser(ctx, &RenewUserRequest{ |
| Statements: &statements, |
| Username: username, |
| Expiration: t, |
| }) |
| if err != nil { |
| if c.doneCtx.Err() != nil { |
| return ErrPluginShutdown |
| } |
| |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (c *gRPCClient) RevokeUser(ctx context.Context, statements Statements, username string) error { |
| ctx, cancel := context.WithCancel(ctx) |
| quitCh := pluginutil.CtxCancelIfCanceled(cancel, c.doneCtx) |
| defer close(quitCh) |
| defer cancel() |
| |
| _, err := c.client.RevokeUser(ctx, &RevokeUserRequest{ |
| Statements: &statements, |
| Username: username, |
| }) |
| if err != nil { |
| if c.doneCtx.Err() != nil { |
| return ErrPluginShutdown |
| } |
| |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (c *gRPCClient) RotateRootCredentials(ctx context.Context, statements []string) (conf map[string]interface{}, err error) { |
| ctx, cancel := context.WithCancel(ctx) |
| quitCh := pluginutil.CtxCancelIfCanceled(cancel, c.doneCtx) |
| defer close(quitCh) |
| defer cancel() |
| |
| resp, err := c.client.RotateRootCredentials(ctx, &RotateRootCredentialsRequest{ |
| Statements: statements, |
| }) |
| if err != nil { |
| if c.doneCtx.Err() != nil { |
| return nil, ErrPluginShutdown |
| } |
| |
| return nil, err |
| } |
| |
| if err := json.Unmarshal(resp.Config, &conf); err != nil { |
| return nil, err |
| } |
| |
| return conf, nil |
| } |
| |
| func (c *gRPCClient) Initialize(ctx context.Context, conf map[string]interface{}, verifyConnection bool) error { |
| _, err := c.Init(ctx, conf, verifyConnection) |
| return err |
| } |
| |
| func (c *gRPCClient) Init(ctx context.Context, conf map[string]interface{}, verifyConnection bool) (map[string]interface{}, error) { |
| configRaw, err := json.Marshal(conf) |
| if err != nil { |
| return nil, err |
| } |
| |
| ctx, cancel := context.WithCancel(ctx) |
| quitCh := pluginutil.CtxCancelIfCanceled(cancel, c.doneCtx) |
| defer close(quitCh) |
| defer cancel() |
| |
| resp, err := c.client.Init(ctx, &InitRequest{ |
| Config: configRaw, |
| VerifyConnection: verifyConnection, |
| }) |
| if err != nil { |
| // Fall back to old call if not implemented |
| grpcStatus, ok := status.FromError(err) |
| if ok && grpcStatus.Code() == codes.Unimplemented { |
| _, err = c.client.Initialize(ctx, &InitializeRequest{ |
| Config: configRaw, |
| VerifyConnection: verifyConnection, |
| }) |
| if err == nil { |
| return conf, nil |
| } |
| } |
| |
| if c.doneCtx.Err() != nil { |
| return nil, ErrPluginShutdown |
| } |
| return nil, err |
| } |
| |
| if err := json.Unmarshal(resp.Config, &conf); err != nil { |
| return nil, err |
| } |
| return conf, nil |
| } |
| |
| func (c *gRPCClient) Close() error { |
| _, err := c.client.Close(c.doneCtx, &Empty{}) |
| return err |
| } |
| |
| func (c *gRPCClient) GenerateCredentials(ctx context.Context) (string, error) { |
| ctx, cancel := context.WithCancel(ctx) |
| quitCh := pluginutil.CtxCancelIfCanceled(cancel, c.doneCtx) |
| defer close(quitCh) |
| defer cancel() |
| |
| resp, err := c.client.GenerateCredentials(ctx, &Empty{}) |
| if err != nil { |
| grpcStatus, ok := status.FromError(err) |
| if ok && grpcStatus.Code() == codes.Unimplemented { |
| return "", ErrPluginStaticUnsupported |
| } |
| |
| if c.doneCtx.Err() != nil { |
| return "", ErrPluginShutdown |
| } |
| return "", err |
| } |
| |
| return resp.Password, nil |
| } |
| |
| func (c *gRPCClient) SetCredentials(ctx context.Context, statements Statements, staticUser StaticUserConfig) (username, password string, err error) { |
| ctx, cancel := context.WithCancel(ctx) |
| quitCh := pluginutil.CtxCancelIfCanceled(cancel, c.doneCtx) |
| defer close(quitCh) |
| defer cancel() |
| |
| resp, err := c.client.SetCredentials(ctx, &SetCredentialsRequest{ |
| StaticUserConfig: &staticUser, |
| Statements: &statements, |
| }) |
| if err != nil { |
| // Fall back to old call if not implemented |
| grpcStatus, ok := status.FromError(err) |
| if ok && grpcStatus.Code() == codes.Unimplemented { |
| return "", "", ErrPluginStaticUnsupported |
| } |
| |
| if c.doneCtx.Err() != nil { |
| return "", "", ErrPluginShutdown |
| } |
| return "", "", err |
| } |
| |
| return resp.Username, resp.Password, err |
| } |