diff --git a/proto/milpacs.proto b/proto/milpacs.proto index 13e7fcb..2a6765c 100644 --- a/proto/milpacs.proto +++ b/proto/milpacs.proto @@ -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"; diff --git a/servers/grpc/authentication.go b/servers/grpc/authentication.go index 6f4f64c..69f4b06 100644 --- a/servers/grpc/authentication.go +++ b/servers/grpc/authentication.go @@ -20,11 +20,8 @@ package grpc import ( "context" - "crypto/tls" - "crypto/x509" - "net/http" + "crypto/subtle" "strings" - "time" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -32,81 +29,37 @@ import ( "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 +} \ No newline at end of file diff --git a/servers/server.go b/servers/server.go index 5148e7f..262e5a0 100644 --- a/servers/server.go +++ b/servers/server.go @@ -38,7 +38,7 @@ import ( "gorm.io/gorm" ) -const version = "1.7.6" +const version = "2.0.0" type MicroServer struct { addr string @@ -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 == "" { @@ -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) @@ -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), }