// credentialServiceServer implements mciasv1.CredentialServiceServer. // All RPCs require admin role. package grpcserver import ( "context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "git.wntrmute.dev/kyle/mcias/internal/crypto" "git.wntrmute.dev/kyle/mcias/internal/db" "git.wntrmute.dev/kyle/mcias/internal/model" mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" ) type credentialServiceServer struct { mciasv1.UnimplementedCredentialServiceServer s *Server } // GetPGCreds decrypts and returns Postgres credentials. Admin only. // Security: the password field is decrypted and returned; this constitutes // a sensitive operation. The audit log records the access. func (c *credentialServiceServer) GetPGCreds(ctx context.Context, req *mciasv1.GetPGCredsRequest) (*mciasv1.GetPGCredsResponse, error) { if err := c.s.requireAdmin(ctx); err != nil { return nil, err } if req.Id == "" { return nil, status.Error(codes.InvalidArgument, "id is required") } acct, err := c.s.db.GetAccountByUUID(req.Id) if err != nil { if err == db.ErrNotFound { return nil, status.Error(codes.NotFound, "account not found") } return nil, status.Error(codes.Internal, "internal error") } cred, err := c.s.db.ReadPGCredentials(acct.ID) if err != nil { if err == db.ErrNotFound { return nil, status.Error(codes.NotFound, "no credentials stored") } return nil, status.Error(codes.Internal, "internal error") } // Decrypt the password for admin retrieval. password, err := crypto.OpenAESGCM(c.s.masterKey, cred.PGPasswordNonce, cred.PGPasswordEnc) if err != nil { return nil, status.Error(codes.Internal, "internal error") } c.s.db.WriteAuditEvent(model.EventPGCredAccessed, nil, &acct.ID, peerIP(ctx), "") //nolint:errcheck return &mciasv1.GetPGCredsResponse{ Creds: &mciasv1.PGCreds{ Host: cred.PGHost, Database: cred.PGDatabase, Username: cred.PGUsername, Password: string(password), // security: returned only on explicit admin request Port: int32(cred.PGPort), }, }, nil } // SetPGCreds stores Postgres credentials for an account. Admin only. func (c *credentialServiceServer) SetPGCreds(ctx context.Context, req *mciasv1.SetPGCredsRequest) (*mciasv1.SetPGCredsResponse, error) { if err := c.s.requireAdmin(ctx); err != nil { return nil, err } if req.Id == "" { return nil, status.Error(codes.InvalidArgument, "id is required") } if req.Creds == nil { return nil, status.Error(codes.InvalidArgument, "creds is required") } cr := req.Creds if cr.Host == "" || cr.Database == "" || cr.Username == "" || cr.Password == "" { return nil, status.Error(codes.InvalidArgument, "host, database, username, and password are required") } port := int(cr.Port) if port == 0 { port = 5432 } acct, err := c.s.db.GetAccountByUUID(req.Id) if err != nil { if err == db.ErrNotFound { return nil, status.Error(codes.NotFound, "account not found") } return nil, status.Error(codes.Internal, "internal error") } enc, nonce, err := crypto.SealAESGCM(c.s.masterKey, []byte(cr.Password)) if err != nil { return nil, status.Error(codes.Internal, "internal error") } if err := c.s.db.WritePGCredentials(acct.ID, cr.Host, port, cr.Database, cr.Username, enc, nonce); err != nil { return nil, status.Error(codes.Internal, "internal error") } c.s.db.WriteAuditEvent(model.EventPGCredUpdated, nil, &acct.ID, peerIP(ctx), "") //nolint:errcheck return &mciasv1.SetPGCredsResponse{}, nil }