Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion proto/milpacs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import "google/protobuf/empty.proto";
// These annotations are used when generating the OpenAPI file.
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info: {
version: "1.7.4";
version: "2.0.0";
};
external_docs: {
url: "https://github.com/7cav/api";
Expand Down
95 changes: 24 additions & 71 deletions servers/grpc/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,93 +20,46 @@ package grpc

import (
"context"
"crypto/tls"
"crypto/x509"
"net/http"
"crypto/subtle"
"strings"
"time"

"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)

const authServerURL = "https://auth.7cav.us/auth/realms/7cav/check?apiKey="
func NewAuthInterceptor(secret string) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
// You can use your global logger here if accessible, or fmt
return nil, status.Errorf(codes.Unauthenticated, "missing metadata")
}

func ValidateToken(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
Info.Println("Checking metadata")
authHeaders := md.Get("authorization")
if len(authHeaders) < 1 {
return nil, status.Errorf(codes.Unauthenticated, "missing authorization token")
}

md, ok := metadata.FromIncomingContext(ctx)
if !ok {
Warn.Println("Unauthorized: No metadata found")
return nil, status.Errorf(codes.Unauthenticated, "missing metadata")
}
authHeader := strings.TrimSpace(authHeaders[0])

authHeaders := md.Get("authorization")
if len(authHeaders) < 1 {
Warn.Println("Unauthorized: Missing authorization header")
return nil, status.Errorf(codes.Unauthenticated, "missing authorization token")
}
// Pass the baked-in secret to the check
if !isValidToken(authHeader, secret) {
// logic to log warning if needed
return nil, status.Errorf(codes.Unauthenticated, "invalid token")
}

authHeader := strings.TrimSpace(authHeaders[0])
if !isValidToken(authHeader) {
Warn.Printf("Unauthorized attempt on method %s", info.FullMethod)
return nil, status.Errorf(codes.Unauthenticated, "invalid token")
return handler(ctx, req)
}

return handler(ctx, req)
}

func isValidToken(authHeader string) bool {
func isValidToken(authHeader, secret string) bool {
token := strings.TrimPrefix(authHeader, "Bearer ")
if token == "" {
Warn.Println("Empty token provided")
return false
}
if !isPrintableASCII(token) {
Warn.Println("Token contains non-printable ASCII")
return false
}

config, err := loadTLSConfig()
if err != nil {
Warn.Printf("Failed to load TLS config: %v", err)
return false
}

client := &http.Client{
Transport: &http.Transport{TLSClientConfig: config},
Timeout: 5 * time.Second,
}
res, err := client.Get(authServerURL + token)
if err != nil {
Warn.Printf("Auth server unreachable: %v", err)
return false
}
defer func() {
if err := res.Body.Close(); err != nil {
Warn.Printf("Error closing response body: %v", err)
}
}()

return res.StatusCode == http.StatusOK
}

func loadTLSConfig() (*tls.Config, error) {
rootCAs, err := x509.SystemCertPool()
if err != nil {
Warn.Printf("Could not load system CA pool: %v", err)
return nil, err
}
return &tls.Config{RootCAs: rootCAs}, nil
}

func isPrintableASCII(s string) bool {
for _, r := range s {
if r < 32 || r > 126 {
return false
}
}
return true
}

// Compare the token against the secret passed from startup
return subtle.ConstantTimeCompare([]byte(token), []byte(secret)) == 1
}
15 changes: 13 additions & 2 deletions servers/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import (
"gorm.io/gorm"
)

const version = "1.7.6"
const version = "2.0.0"

type MicroServer struct {
addr string
Expand All @@ -61,6 +61,16 @@ var (
Error = log.New(os.Stdout, "ERROR: ", log.LstdFlags)
)

func setupAuth() string {
secret := viper.GetString("API_SECRET")
if secret == "" {
// It is critical to fail fast if this is missing
Error.Println("CRITICAL: API_SECRET is not set in environment/config")
os.Exit(1)
}
return secret
}

func setupRedis() *cache.RedisCache {
redisHost := viper.GetString("REDIS_HOST")
if redisHost == "" {
Expand Down Expand Up @@ -135,6 +145,7 @@ func (server *MicroServer) Start() {
Error.Fatalf("Failed to listen on %s: %w", server.addr, err)
}

apiSecret := setupAuth()
ds := setupDatasource()
server.cache = setupRedis()
go cache.CacheManager(server.cache, ds)
Expand All @@ -144,7 +155,7 @@ func (server *MicroServer) Start() {
// If this needed to change in the future, then we will need to refactor this method
opts := []grpc.ServerOption{
// Intercept request to check the token.
grpc.UnaryInterceptor(grpcServices.ValidateToken),
grpc.UnaryInterceptor(grpcServices.NewAuthInterceptor(apiSecret)),
//grpc.Creds(creds),
}

Expand Down