fix: critical security vulnerabilities
- Fix JWT secret derivation vulnerability - replace deriveJWTSecret with cryptographically secure GenerateSecureToken - Secure setup interface - remove JWT secret display and API response exposure - Addresses system-wide compromise risk from admin credential exposure
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Fimeg/RedFlag/aggregator-server/internal/config"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/lib/pq"
|
||||
_ "github.com/lib/pq"
|
||||
@@ -374,8 +373,12 @@ func (h *SetupHandler) ConfigureServer(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Generate JWT secret for display (not logged for security)
|
||||
jwtSecret := deriveJWTSecret(req.AdminUser, req.AdminPass)
|
||||
// Generate secure JWT secret (not derived from credentials for security)
|
||||
jwtSecret, err := config.GenerateSecureToken()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate JWT secret"})
|
||||
return
|
||||
}
|
||||
|
||||
// Step 1: Update PostgreSQL password from bootstrap to user password
|
||||
fmt.Println("Updating PostgreSQL password from bootstrap to user-provided password...")
|
||||
@@ -398,7 +401,6 @@ func (h *SetupHandler) ConfigureServer(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Configuration generated successfully!",
|
||||
"jwtSecret": jwtSecret,
|
||||
"envContent": newEnvContent,
|
||||
"restartMessage": "Please replace the bootstrap environment variables with the newly generated ones, then run: docker-compose down && docker-compose up -d",
|
||||
"manualRestartRequired": true,
|
||||
@@ -407,8 +409,3 @@ func (h *SetupHandler) ConfigureServer(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// deriveJWTSecret generates a JWT secret from admin credentials
|
||||
func deriveJWTSecret(username, password string) string {
|
||||
hash := sha256.Sum256([]byte(username + password + "redflag-jwt-2024"))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -123,12 +122,6 @@ func getEnv(key, defaultValue string) string {
|
||||
}
|
||||
|
||||
|
||||
func deriveJWTSecret(username, password string) string {
|
||||
// Derive JWT secret from admin credentials
|
||||
// This ensures JWT secret changes if admin password changes
|
||||
hash := sha256.Sum256([]byte(username + password + "redflag-jwt-2024"))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// GenerateSecureToken generates a cryptographically secure random token
|
||||
func GenerateSecureToken() (string, error) {
|
||||
|
||||
@@ -7,6 +7,7 @@ interface SetupCompletionCheckerProps {
|
||||
}
|
||||
|
||||
export const SetupCompletionChecker: React.FC<SetupCompletionCheckerProps> = ({ children }) => {
|
||||
const [wasInSetupMode, setWasInSetupMode] = useState(false);
|
||||
const [isSetupMode, setIsSetupMode] = useState<boolean | null>(null);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -16,13 +17,28 @@ export const SetupCompletionChecker: React.FC<SetupCompletionCheckerProps> = ({
|
||||
try {
|
||||
const data = await setupApi.checkHealth();
|
||||
|
||||
if (data.status === 'waiting for configuration') {
|
||||
setIsSetupMode(true);
|
||||
} else {
|
||||
setIsSetupMode(false);
|
||||
const currentSetupMode = data.status === 'waiting for configuration';
|
||||
|
||||
// Track if we were previously in setup mode
|
||||
if (currentSetupMode) {
|
||||
setWasInSetupMode(true);
|
||||
}
|
||||
|
||||
// If we were in setup mode and now we're not, redirect to login
|
||||
if (wasInSetupMode && !currentSetupMode && location.pathname === '/setup') {
|
||||
console.log('Setup completed - redirecting to login');
|
||||
navigate('/login', { replace: true });
|
||||
return; // Prevent further state updates
|
||||
}
|
||||
|
||||
setIsSetupMode(currentSetupMode);
|
||||
} catch (error) {
|
||||
// If we can't reach the health endpoint, assume normal mode
|
||||
if (wasInSetupMode && location.pathname === '/setup') {
|
||||
console.log('Setup completed (endpoint reachable) - redirecting to login');
|
||||
navigate('/login', { replace: true });
|
||||
return;
|
||||
}
|
||||
setIsSetupMode(false);
|
||||
}
|
||||
};
|
||||
@@ -33,15 +49,7 @@ export const SetupCompletionChecker: React.FC<SetupCompletionCheckerProps> = ({
|
||||
const interval = setInterval(checkSetupStatus, 3000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// If we're on the setup page and server is now healthy, redirect to login
|
||||
useEffect(() => {
|
||||
if (isSetupMode === false && location.pathname === '/setup') {
|
||||
console.log('Setup completed - redirecting to login');
|
||||
navigate('/login', { replace: true });
|
||||
}
|
||||
}, [isSetupMode, location.pathname, navigate]);
|
||||
}, [wasInSetupMode, location.pathname, navigate]);
|
||||
|
||||
// Always render children - this component only handles redirects
|
||||
return <>{children}</>;
|
||||
|
||||
@@ -21,8 +21,7 @@ const Setup: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [jwtSecret, setJwtSecret] = useState<string | null>(null);
|
||||
const [envContent, setEnvContent] = useState<string | null>(null);
|
||||
const [envContent, setEnvContent] = useState<string | null>(null);
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showDbPassword, setShowDbPassword] = useState(false);
|
||||
@@ -112,8 +111,7 @@ const Setup: React.FC = () => {
|
||||
try {
|
||||
const result = await setupApi.configure(formData);
|
||||
|
||||
// Store JWT secret, env content and show success screen
|
||||
setJwtSecret(result.jwtSecret || null);
|
||||
// Store env content and show success screen
|
||||
setEnvContent(result.envContent || null);
|
||||
setShowSuccess(true);
|
||||
toast.success(result.message || 'Configuration saved successfully!');
|
||||
@@ -128,8 +126,8 @@ const Setup: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Success screen with credentials display
|
||||
if (showSuccess && jwtSecret) {
|
||||
// Success screen with configuration display
|
||||
if (showSuccess && envContent) {
|
||||
return (
|
||||
<div className="px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
@@ -200,27 +198,6 @@ const Setup: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* JWT Secret Section (Server Configuration) */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">Server JWT Secret</h3>
|
||||
<div className="bg-gray-50 border border-gray-200 rounded-md p-4">
|
||||
<code className="text-sm text-gray-800 break-all font-mono">{jwtSecret}</code>
|
||||
</div>
|
||||
<div className="mt-3 p-3 bg-gray-50 border border-gray-200 rounded-md">
|
||||
<p className="text-sm text-gray-700">
|
||||
<strong>For your information:</strong> This JWT secret is used internally by the server for session management and agent authentication. It's automatically included in the configuration file above.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(jwtSecret);
|
||||
toast.success('JWT secret copied to clipboard!');
|
||||
}}
|
||||
className="mt-3 w-full flex justify-center py-2 px-4 border border-transparent rounded-md text-sm font-medium text-white bg-gray-600 hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500"
|
||||
>
|
||||
Copy JWT Secret (Optional)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Next Steps */}
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
|
||||
Reference in New Issue
Block a user