diff --git a/cmd/mciasgrpcctl/main.go b/cmd/mciasgrpcctl/main.go index c44d495..c9aa3aa 100644 --- a/cmd/mciasgrpcctl/main.go +++ b/cmd/mciasgrpcctl/main.go @@ -506,7 +506,7 @@ func (c *controller) pgCredsSet(args []string) { Id: *id, Creds: &mciasv1.PGCreds{ Host: *host, - Port: int32(*port), + Port: int32(*port), //nolint:gosec // G115: port validated as [1,65535] by flag parsing Database: *dbName, Username: *username, Password: *password, diff --git a/cmd/mciassrv/main.go b/cmd/mciassrv/main.go index d268e8f..1d92eaa 100644 --- a/cmd/mciassrv/main.go +++ b/cmd/mciassrv/main.go @@ -132,11 +132,8 @@ func run(configPath string, logger *slog.Logger) error { } grpcSrvImpl := grpcserver.New(database, cfg, privKey, pubKey, masterKey, logger) - grpcSrv = grpcSrvImpl.GRPCServer() - // Apply TLS to the gRPC server by wrapping options. - // We reconstruct the server with TLS credentials since GRPCServer() - // returns an already-built server; instead, build with creds directly. - // Re-create with TLS option. + // Build server directly with TLS credentials. GRPCServerWithCreds builds + // the server with transport credentials at construction time per gRPC idiom. grpcSrv = rebuildGRPCServerWithTLS(grpcSrvImpl, grpcTLSCreds) grpcListener, err = net.Listen("tcp", cfg.Server.GRPCAddr) diff --git a/internal/grpcserver/accountservice.go b/internal/grpcserver/accountservice.go index f791b7b..b5cfe41 100644 --- a/internal/grpcserver/accountservice.go +++ b/internal/grpcserver/accountservice.go @@ -4,16 +4,17 @@ package grpcserver import ( "context" + "errors" "fmt" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" + mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" "git.wntrmute.dev/kyle/mcias/internal/auth" "git.wntrmute.dev/kyle/mcias/internal/db" "git.wntrmute.dev/kyle/mcias/internal/model" - mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" ) type accountServiceServer struct { @@ -101,7 +102,7 @@ func (a *accountServiceServer) GetAccount(ctx context.Context, req *mciasv1.GetA } acct, err := a.s.db.GetAccountByUUID(req.Id) if err != nil { - if err == db.ErrNotFound { + if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "account not found") } return nil, status.Error(codes.Internal, "internal error") @@ -119,7 +120,7 @@ func (a *accountServiceServer) UpdateAccount(ctx context.Context, req *mciasv1.U } acct, err := a.s.db.GetAccountByUUID(req.Id) if err != nil { - if err == db.ErrNotFound { + if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "account not found") } return nil, status.Error(codes.Internal, "internal error") @@ -149,7 +150,7 @@ func (a *accountServiceServer) DeleteAccount(ctx context.Context, req *mciasv1.D } acct, err := a.s.db.GetAccountByUUID(req.Id) if err != nil { - if err == db.ErrNotFound { + if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "account not found") } return nil, status.Error(codes.Internal, "internal error") @@ -174,7 +175,7 @@ func (a *accountServiceServer) GetRoles(ctx context.Context, req *mciasv1.GetRol } acct, err := a.s.db.GetAccountByUUID(req.Id) if err != nil { - if err == db.ErrNotFound { + if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "account not found") } return nil, status.Error(codes.Internal, "internal error") @@ -199,7 +200,7 @@ func (a *accountServiceServer) SetRoles(ctx context.Context, req *mciasv1.SetRol } acct, err := a.s.db.GetAccountByUUID(req.Id) if err != nil { - if err == db.ErrNotFound { + if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "account not found") } return nil, status.Error(codes.Internal, "internal error") diff --git a/internal/grpcserver/auth.go b/internal/grpcserver/auth.go index 55ce4aa..3b08554 100644 --- a/internal/grpcserver/auth.go +++ b/internal/grpcserver/auth.go @@ -12,11 +12,11 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" + mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" "git.wntrmute.dev/kyle/mcias/internal/auth" "git.wntrmute.dev/kyle/mcias/internal/crypto" "git.wntrmute.dev/kyle/mcias/internal/model" "git.wntrmute.dev/kyle/mcias/internal/token" - mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" ) type authServiceServer struct { @@ -97,8 +97,8 @@ func (a *authServiceServer) Login(ctx context.Context, req *mciasv1.LoginRequest return nil, status.Error(codes.Internal, "internal error") } - a.s.db.WriteAuditEvent(model.EventLoginOK, &acct.ID, nil, ip, "") //nolint:errcheck - a.s.db.WriteAuditEvent(model.EventTokenIssued, &acct.ID, nil, ip, //nolint:errcheck + a.s.db.WriteAuditEvent(model.EventLoginOK, &acct.ID, nil, ip, "") //nolint:errcheck + a.s.db.WriteAuditEvent(model.EventTokenIssued, &acct.ID, nil, ip, //nolint:errcheck fmt.Sprintf(`{"jti":%q}`, claims.JTI)) return &mciasv1.LoginResponse{ diff --git a/internal/grpcserver/credentialservice.go b/internal/grpcserver/credentialservice.go index cc34c76..afe10b7 100644 --- a/internal/grpcserver/credentialservice.go +++ b/internal/grpcserver/credentialservice.go @@ -4,14 +4,15 @@ package grpcserver import ( "context" + "errors" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" "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 { @@ -31,7 +32,7 @@ func (c *credentialServiceServer) GetPGCreds(ctx context.Context, req *mciasv1.G } acct, err := c.s.db.GetAccountByUUID(req.Id) if err != nil { - if err == db.ErrNotFound { + if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "account not found") } return nil, status.Error(codes.Internal, "internal error") @@ -39,7 +40,7 @@ func (c *credentialServiceServer) GetPGCreds(ctx context.Context, req *mciasv1.G cred, err := c.s.db.ReadPGCredentials(acct.ID) if err != nil { - if err == db.ErrNotFound { + if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "no credentials stored") } return nil, status.Error(codes.Internal, "internal error") @@ -58,8 +59,8 @@ func (c *credentialServiceServer) GetPGCreds(ctx context.Context, req *mciasv1.G Host: cred.PGHost, Database: cred.PGDatabase, Username: cred.PGUsername, - Password: string(password), // security: returned only on explicit admin request - Port: int32(cred.PGPort), + Password: string(password), // security: returned only on explicit admin request + Port: int32(cred.PGPort), //nolint:gosec // G115: PGPort is validated as [1,65535] on write }, }, nil } @@ -87,7 +88,7 @@ func (c *credentialServiceServer) SetPGCreds(ctx context.Context, req *mciasv1.S acct, err := c.s.db.GetAccountByUUID(req.Id) if err != nil { - if err == db.ErrNotFound { + if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "account not found") } return nil, status.Error(codes.Internal, "internal error") diff --git a/internal/grpcserver/grpcserver.go b/internal/grpcserver/grpcserver.go index bcffeb3..de95fe0 100644 --- a/internal/grpcserver/grpcserver.go +++ b/internal/grpcserver/grpcserver.go @@ -31,10 +31,10 @@ import ( "google.golang.org/grpc/peer" "google.golang.org/grpc/status" + mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" "git.wntrmute.dev/kyle/mcias/internal/config" "git.wntrmute.dev/kyle/mcias/internal/db" "git.wntrmute.dev/kyle/mcias/internal/token" - mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" ) // contextKey is the unexported context key type for this package. @@ -55,10 +55,10 @@ func claimsFromContext(ctx context.Context) *token.Claims { type Server struct { db *db.DB cfg *config.Config + logger *slog.Logger privKey ed25519.PrivateKey pubKey ed25519.PublicKey masterKey []byte - logger *slog.Logger } // New creates a Server with the given dependencies (same as the REST Server). @@ -76,10 +76,10 @@ func New(database *db.DB, cfg *config.Config, priv ed25519.PrivateKey, pub ed255 // publicMethods is the set of fully-qualified method names that bypass auth. // These match the gRPC full method path: /./. var publicMethods = map[string]bool{ - "/mcias.v1.AdminService/Health": true, - "/mcias.v1.AdminService/GetPublicKey": true, - "/mcias.v1.TokenService/ValidateToken": true, - "/mcias.v1.AuthService/Login": true, + "/mcias.v1.AdminService/Health": true, + "/mcias.v1.AdminService/GetPublicKey": true, + "/mcias.v1.TokenService/ValidateToken": true, + "/mcias.v1.AuthService/Login": true, } // GRPCServer builds and returns a configured *grpc.Server with all services @@ -217,17 +217,17 @@ func (s *Server) requireAdmin(ctx context.Context) error { // grpcRateLimiter is a per-IP token bucket for gRPC, sharing the same // algorithm as the REST RateLimit middleware. type grpcRateLimiter struct { - mu sync.Mutex ips map[string]*grpcRateLimitEntry rps float64 burst float64 ttl time.Duration + mu sync.Mutex } type grpcRateLimitEntry struct { - mu sync.Mutex lastSeen time.Time tokens float64 + mu sync.Mutex } func newGRPCRateLimiter(rps float64, burst int) *grpcRateLimiter { diff --git a/internal/grpcserver/grpcserver_test.go b/internal/grpcserver/grpcserver_test.go index 2faba91..bb0b286 100644 --- a/internal/grpcserver/grpcserver_test.go +++ b/internal/grpcserver/grpcserver_test.go @@ -22,12 +22,12 @@ import ( "google.golang.org/grpc/status" "google.golang.org/grpc/test/bufconn" + mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" "git.wntrmute.dev/kyle/mcias/internal/auth" "git.wntrmute.dev/kyle/mcias/internal/config" "git.wntrmute.dev/kyle/mcias/internal/db" "git.wntrmute.dev/kyle/mcias/internal/model" "git.wntrmute.dev/kyle/mcias/internal/token" - mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" ) const ( @@ -38,11 +38,11 @@ const ( // testEnv holds all resources for a single test's gRPC server. type testEnv struct { db *db.DB + cfg *config.Config + conn *grpc.ClientConn priv ed25519.PrivateKey pub ed25519.PublicKey masterKey []byte - cfg *config.Config - conn *grpc.ClientConn } // newTestEnv spins up an in-process gRPC server using bufconn and returns @@ -76,9 +76,7 @@ func newTestEnv(t *testing.T) *testEnv { lis := bufconn.Listen(bufConnSize) go func() { - if err := grpcSrv.Serve(lis); err != nil { - // Serve returns when the listener is closed; ignore that error. - } + _ = grpcSrv.Serve(lis) // returns on listener close; error ignored }() conn, err := grpc.NewClient( diff --git a/internal/grpcserver/tokenservice.go b/internal/grpcserver/tokenservice.go index 23c20b2..421d07d 100644 --- a/internal/grpcserver/tokenservice.go +++ b/internal/grpcserver/tokenservice.go @@ -3,16 +3,17 @@ package grpcserver import ( "context" + "errors" "fmt" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/timestamppb" + mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" "git.wntrmute.dev/kyle/mcias/internal/db" "git.wntrmute.dev/kyle/mcias/internal/model" "git.wntrmute.dev/kyle/mcias/internal/token" - mciasv1 "git.wntrmute.dev/kyle/mcias/gen/mcias/v1" ) type tokenServiceServer struct { @@ -110,7 +111,7 @@ func (ts *tokenServiceServer) RevokeToken(ctx context.Context, req *mciasv1.Revo } if err := ts.s.db.RevokeToken(req.Jti, "admin revocation"); err != nil { - if err == db.ErrNotFound { + if errors.Is(err, db.ErrNotFound) { return nil, status.Error(codes.NotFound, "token not found or already revoked") } return nil, status.Error(codes.Internal, "internal error")