Support Policy Attachment for Object Store Users (#7981)

* Implement Policy Attachment support for Object Store Users

- Added policy_names field to iam.proto and regenerated protos.
- Updated S3 API and IAM integration to support direct policy evaluation for users.
- Enhanced Admin UI to allow attaching policies to users via modals.
- Renamed 'policies' to 'policy_names' to clarify that it stores identifiers.
- Fixed syntax error in user_management.go.

* Fix policy dropdown not populating

The API returns {policies: [...]} but JavaScript was treating response as direct array.
Updated loadPolicies() to correctly access data.policies property.

* Add null safety checks for policy dropdowns

Added checks to prevent "undefined" errors when:
- Policy select elements don't exist
- Policy dropdowns haven't loaded yet
- User is being edited before policies are loaded

* Fix policy dropdown by using correct JSON field name

JSON response has lowercase 'name' field but JavaScript was accessing 'Name'.
Changed policy.Name to policy.name to match the IAMPolicy JSON structure.

* Fix policy names not being saved on user update

Changed condition from len(req.PolicyNames) > 0 to req.PolicyNames != nil
to ensure policy names are always updated when present in the request,
even if it's an empty array (to allow clearing policies).

* Add debug logging for policy names update flow

Added console.log in frontend and glog in backend to trace
policy_names data through the update process.

* Temporarily disable auto-reload for debugging

Commented out window.location.reload() so console logs are visible
when updating a user.

* Add detailed debug logging and alert for policy selection

Added console.log for each step and an alert to show policy_names value
to help diagnose why it's not being included in the request.

* Regenerate templ files for object_store_users

Ran templ generate to ensure _templ.go files are up to date with
the latest .templ changes including debug logging.

* Remove debug logging and restore normal functionality

Cleaned up temporary debug code (console.log and alert statements)
and re-enabled automatic page reload after user update.

* Add step-by-step alert debugging for policy update

Added 5 alert checkpoints to trace policy data through the update flow:
1. Check if policiesSelect element exists
2. Show selected policy values
3. Show userData.policy_names
4. Show full request body
5. Confirm server response

Temporarily disabled auto-reload to see alerts.

* Add version check alert on page load

Added alert on DOMContentLoaded to verify new JavaScript is being executed
and not cached by the browser.

* Compile templates using make

Ran make to compile all template files and install the weed binary.

* Add button click detection and make handleUpdateUser global

- Added inline alert on button click to verify click is detected
- Made handleUpdateUser a window-level function to ensure it's accessible
- Added alert at start of handleUpdateUser function

* Fix handleUpdateUser scope issue - remove duplicate definition

Removed duplicate function definition that was inside DOMContentLoaded.
Now handleUpdateUser is defined only once in global scope (line 383)
making it accessible when button onclick fires.

* Remove all duplicate handleUpdateUser definitions

Now handleUpdateUser is defined only once at the very top of the script
block (line 352), before DOMContentLoaded, ensuring it's available when
the button onclick fires.

* Add function existence check and error catching

Added alerts to check if handleUpdateUser is defined and wrapped
the function call in try-catch to capture any JavaScript errors.
Also added console.log statements to verify function definition.

* Simplify handleUpdateUser to non-async for testing

Removed async/await and added early return to test if function
can be called at all. This will help identify if async is causing
the issue.

* Add cache-control headers to prevent browser caching

Added no-cache headers to ShowObjectStoreUsers handler to prevent
aggressive browser caching of inline JavaScript in the HTML page.

* Fix syntax error - make handleUpdateUser async

Changed function back to async to fix 'await is only valid in async functions' error.
The cache-control headers are working - browser is now loading new code.

* Update version check to v3 to verify cache busting

Changed version alert to 'v3 - WITH EARLY RETURN' to confirm
the new code with early return statement is being loaded.

* Remove all debug code - clean implementation

Removed all alerts, console.logs, and test code.
Implemented clean policy update functionality with proper error handling.

* Add ETag header for cache-busting and update walkthrough

* Fix policy pre-selection in Edit User modal

- Updated admin.js editUser function to pre-select policies
- Root cause: duplicate editUser in admin.js overwrote inline version
- Added policy pre-selection logic to match inline template
- Verified working in browser: policies now pre-select correctly

* Fix policy persistence in handleUpdateUser

- Added policy_names field to userData payload in handleUpdateUser
- Policies were being lost because handleUpdateUser only sent email and actions
- Now collects selected policies from editPolicies dropdown
- Verified working: policies persist correctly across updates

* Fix XSS vulnerability in access keys display

- Escape HTML in access key display using escapeHtml utility
- Replace inline onclick handlers with data attributes
- Add event delegation for delete access key buttons
- Prevents script injection via malicious access key values

* Fix additional XSS vulnerabilities in user details display

- Escape HTML in actions badges (line 626)
- Escape HTML in policy_names badges (line 636)
- Prevents script injection via malicious action or policy names

* Fix XSS vulnerability in loadPolicies function

- Replace innerHTML string concatenation with DOM API
- Use createElement and textContent for safe policy name insertion
- Prevents script injection via malicious policy names
- Apply same pattern to both create and edit select elements

* Remove debug logging from UpdateObjectStoreUser

- Removed glog.V(0) debug statements
- Clean up temporary debugging code before production

* Remove duplicate handleUpdateUser function

- Removed inline handleUpdateUser that duplicated admin.js logic
- Removed debug console.log statement
- admin.js version is now the single source of truth
- Eliminates maintenance burden of keeping two versions in sync

* Refine user management and address code review feedback

- Preserve PolicyNames in UpdateUserPolicies
- Allow clearing actions in UpdateObjectStoreUser by checking for nil
- Remove version comment from object_store_users.templ
- Refactor loadPolicies for DRYness using cloneNode while keeping DOM API security

* IAM Authorization for Static Access Keys

* verified XSS Fixes in Templates

* fix div
This commit is contained in:
Chris Lu
2026-01-06 21:53:28 -08:00
committed by GitHub
parent d4ecfaeda7
commit e67973dc53
29 changed files with 456 additions and 337 deletions

View File

@@ -37,6 +37,7 @@ type ObjectStoreUser struct {
AccessKey string `json:"access_key"` AccessKey string `json:"access_key"`
SecretKey string `json:"secret_key"` SecretKey string `json:"secret_key"`
Permissions []string `json:"permissions"` Permissions []string `json:"permissions"`
PolicyNames []string `json:"policy_names"`
} }
type ObjectStoreUsersData struct { type ObjectStoreUsersData struct {
@@ -52,11 +53,13 @@ type CreateUserRequest struct {
Email string `json:"email"` Email string `json:"email"`
Actions []string `json:"actions"` Actions []string `json:"actions"`
GenerateKey bool `json:"generate_key"` GenerateKey bool `json:"generate_key"`
PolicyNames []string `json:"policy_names"`
} }
type UpdateUserRequest struct { type UpdateUserRequest struct {
Email string `json:"email"` Email string `json:"email"`
Actions []string `json:"actions"` Actions []string `json:"actions"`
PolicyNames []string `json:"policy_names"`
} }
type UpdateUserPoliciesRequest struct { type UpdateUserPoliciesRequest struct {
@@ -70,10 +73,11 @@ type AccessKeyInfo struct {
} }
type UserDetails struct { type UserDetails struct {
Username string `json:"username"` Username string `json:"username"`
Email string `json:"email"` Email string `json:"email"`
Actions []string `json:"actions"` Actions []string `json:"actions"`
AccessKeys []AccessKeyInfo `json:"access_keys"` PolicyNames []string `json:"policy_names"`
AccessKeys []AccessKeyInfo `json:"access_keys"`
} }
type FilerNode struct { type FilerNode struct {

View File

@@ -21,8 +21,9 @@ func (s *AdminServer) CreateObjectStoreUser(req CreateUserRequest) (*ObjectStore
// Create new identity // Create new identity
newIdentity := &iam_pb.Identity{ newIdentity := &iam_pb.Identity{
Name: req.Username, Name: req.Username,
Actions: req.Actions, Actions: req.Actions,
PolicyNames: req.PolicyNames,
} }
// Add account if email is provided // Add account if email is provided
@@ -63,6 +64,7 @@ func (s *AdminServer) CreateObjectStoreUser(req CreateUserRequest) (*ObjectStore
AccessKey: accessKey, AccessKey: accessKey,
SecretKey: secretKey, SecretKey: secretKey,
Permissions: req.Actions, Permissions: req.Actions,
PolicyNames: req.PolicyNames,
} }
return user, nil return user, nil
@@ -91,12 +93,17 @@ func (s *AdminServer) UpdateObjectStoreUser(username string, req UpdateUserReque
Account: identity.Account, Account: identity.Account,
Credentials: identity.Credentials, Credentials: identity.Credentials,
Actions: identity.Actions, Actions: identity.Actions,
PolicyNames: identity.PolicyNames,
} }
// Update actions if provided // Update actions if provided
if len(req.Actions) > 0 { if req.Actions != nil {
updatedIdentity.Actions = req.Actions updatedIdentity.Actions = req.Actions
} }
// Always update policy names when present in request (even if empty to allow clearing)
if req.PolicyNames != nil {
updatedIdentity.PolicyNames = req.PolicyNames
}
// Update email if provided // Update email if provided
if req.Email != "" { if req.Email != "" {
@@ -120,6 +127,7 @@ func (s *AdminServer) UpdateObjectStoreUser(username string, req UpdateUserReque
Username: username, Username: username,
Email: req.Email, Email: req.Email,
Permissions: updatedIdentity.Actions, Permissions: updatedIdentity.Actions,
PolicyNames: updatedIdentity.PolicyNames,
} }
// Get first access key for display // Get first access key for display
@@ -169,8 +177,9 @@ func (s *AdminServer) GetObjectStoreUserDetails(username string) (*UserDetails,
} }
details := &UserDetails{ details := &UserDetails{
Username: username, Username: username,
Actions: identity.Actions, Actions: identity.Actions,
PolicyNames: identity.PolicyNames,
} }
// Set email from account if available // Set email from account if available
@@ -295,6 +304,7 @@ func (s *AdminServer) UpdateUserPolicies(username string, actions []string) erro
Account: identity.Account, Account: identity.Account,
Credentials: identity.Credentials, Credentials: identity.Credentials,
Actions: actions, Actions: actions,
PolicyNames: identity.PolicyNames,
} }
// Update user using credential manager // Update user using credential manager

View File

@@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"fmt"
"net/http" "net/http"
"time" "time"
@@ -29,7 +30,12 @@ func (h *UserHandlers) ShowObjectStoreUsers(c *gin.Context) {
usersData := h.getObjectStoreUsersData(c) usersData := h.getObjectStoreUsersData(c)
// Render HTML template // Render HTML template
// Add cache-control headers to prevent browser caching of inline JavaScript
c.Header("Content-Type", "text/html") c.Header("Content-Type", "text/html")
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
c.Header("ETag", fmt.Sprintf("\"%d\"", time.Now().Unix()))
usersComponent := app.ObjectStoreUsers(usersData) usersComponent := app.ObjectStoreUsers(usersData)
layoutComponent := layout.Layout(c, usersComponent) layoutComponent := layout.Layout(c, usersComponent)
err := layoutComponent.Render(c.Request.Context(), c.Writer) err := layoutComponent.Render(c.Request.Context(), c.Writer)

View File

@@ -4,7 +4,7 @@
let bucketToDelete = ''; let bucketToDelete = '';
// Initialize dashboard when DOM is loaded // Initialize dashboard when DOM is loaded
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function () {
initializeDashboard(); initializeDashboard();
initializeEventHandlers(); initializeEventHandlers();
setupFormValidation(); setupFormValidation();
@@ -36,17 +36,17 @@ function initializeDashboard() {
// HTMX event listeners // HTMX event listeners
function setupHTMXListeners() { function setupHTMXListeners() {
// Show loading indicator on requests // Show loading indicator on requests
document.body.addEventListener('htmx:beforeRequest', function(evt) { document.body.addEventListener('htmx:beforeRequest', function (evt) {
showLoadingIndicator(); showLoadingIndicator();
}); });
// Hide loading indicator on completion // Hide loading indicator on completion
document.body.addEventListener('htmx:afterRequest', function(evt) { document.body.addEventListener('htmx:afterRequest', function (evt) {
hideLoadingIndicator(); hideLoadingIndicator();
}); });
// Handle errors // Handle errors
document.body.addEventListener('htmx:responseError', function(evt) { document.body.addEventListener('htmx:responseError', function (evt) {
handleHTMXError(evt); handleHTMXError(evt);
}); });
} }
@@ -62,7 +62,7 @@ function initializeTooltips() {
// Set up auto-refresh for dashboard data // Set up auto-refresh for dashboard data
function setupAutoRefresh() { function setupAutoRefresh() {
// Refresh dashboard data every 30 seconds // Refresh dashboard data every 30 seconds
setInterval(function() { setInterval(function () {
if (window.location.pathname === '/dashboard') { if (window.location.pathname === '/dashboard') {
htmx.trigger('#dashboard-content', 'refresh'); htmx.trigger('#dashboard-content', 'refresh');
} }
@@ -74,7 +74,7 @@ function setActiveNavigation() {
const currentPath = window.location.pathname; const currentPath = window.location.pathname;
const navLinks = document.querySelectorAll('.sidebar .nav-link'); const navLinks = document.querySelectorAll('.sidebar .nav-link');
navLinks.forEach(function(link) { navLinks.forEach(function (link) {
const href = link.getAttribute('href'); const href = link.getAttribute('href');
let isActive = false; let isActive = false;
@@ -146,24 +146,24 @@ function setupSubmenuBehavior() {
// Prevent submenu from collapsing when clicking on submenu items // Prevent submenu from collapsing when clicking on submenu items
const clusterSubmenuLinks = document.querySelectorAll('#clusterSubmenu .nav-link'); const clusterSubmenuLinks = document.querySelectorAll('#clusterSubmenu .nav-link');
clusterSubmenuLinks.forEach(function(link) { clusterSubmenuLinks.forEach(function (link) {
link.addEventListener('click', function(e) { link.addEventListener('click', function (e) {
// Don't prevent the navigation, just stop the collapse behavior // Don't prevent the navigation, just stop the collapse behavior
e.stopPropagation(); e.stopPropagation();
}); });
}); });
const objectStoreSubmenuLinks = document.querySelectorAll('#objectStoreSubmenu .nav-link'); const objectStoreSubmenuLinks = document.querySelectorAll('#objectStoreSubmenu .nav-link');
objectStoreSubmenuLinks.forEach(function(link) { objectStoreSubmenuLinks.forEach(function (link) {
link.addEventListener('click', function(e) { link.addEventListener('click', function (e) {
// Don't prevent the navigation, just stop the collapse behavior // Don't prevent the navigation, just stop the collapse behavior
e.stopPropagation(); e.stopPropagation();
}); });
}); });
const maintenanceSubmenuLinks = document.querySelectorAll('#maintenanceSubmenu .nav-link'); const maintenanceSubmenuLinks = document.querySelectorAll('#maintenanceSubmenu .nav-link');
maintenanceSubmenuLinks.forEach(function(link) { maintenanceSubmenuLinks.forEach(function (link) {
link.addEventListener('click', function(e) { link.addEventListener('click', function (e) {
// Don't prevent the navigation, just stop the collapse behavior // Don't prevent the navigation, just stop the collapse behavior
e.stopPropagation(); e.stopPropagation();
}); });
@@ -172,7 +172,7 @@ function setupSubmenuBehavior() {
// Handle the main cluster toggle // Handle the main cluster toggle
const clusterToggle = document.querySelector('[data-bs-target="#clusterSubmenu"]'); const clusterToggle = document.querySelector('[data-bs-target="#clusterSubmenu"]');
if (clusterToggle) { if (clusterToggle) {
clusterToggle.addEventListener('click', function(e) { clusterToggle.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
const submenu = document.getElementById('clusterSubmenu'); const submenu = document.getElementById('clusterSubmenu');
@@ -195,7 +195,7 @@ function setupSubmenuBehavior() {
// Handle the main object store toggle // Handle the main object store toggle
const objectStoreToggle = document.querySelector('[data-bs-target="#objectStoreSubmenu"]'); const objectStoreToggle = document.querySelector('[data-bs-target="#objectStoreSubmenu"]');
if (objectStoreToggle) { if (objectStoreToggle) {
objectStoreToggle.addEventListener('click', function(e) { objectStoreToggle.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
const submenu = document.getElementById('objectStoreSubmenu'); const submenu = document.getElementById('objectStoreSubmenu');
@@ -218,7 +218,7 @@ function setupSubmenuBehavior() {
// Handle the main maintenance toggle // Handle the main maintenance toggle
const maintenanceToggle = document.querySelector('[data-bs-target="#maintenanceSubmenu"]'); const maintenanceToggle = document.querySelector('[data-bs-target="#maintenanceSubmenu"]');
if (maintenanceToggle) { if (maintenanceToggle) {
maintenanceToggle.addEventListener('click', function(e) { maintenanceToggle.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
const submenu = document.getElementById('maintenanceSubmenu'); const submenu = document.getElementById('maintenanceSubmenu');
@@ -306,7 +306,7 @@ function showErrorMessage(message) {
bsToast.show(); bsToast.show();
// Remove toast element after it's hidden // Remove toast element after it's hidden
toast.addEventListener('hidden.bs.toast', function() { toast.addEventListener('hidden.bs.toast', function () {
toast.remove(); toast.remove();
}); });
} }
@@ -343,7 +343,7 @@ function showSuccessMessage(message) {
const bsToast = new bootstrap.Toast(toast); const bsToast = new bootstrap.Toast(toast);
bsToast.show(); bsToast.show();
toast.addEventListener('hidden.bs.toast', function() { toast.addEventListener('hidden.bs.toast', function () {
toast.remove(); toast.remove();
}); });
} }
@@ -380,7 +380,7 @@ function confirmAction(message, callback) {
} }
// Global error handler // Global error handler
window.addEventListener('error', function(e) { window.addEventListener('error', function (e) {
console.error('Global error:', e.error); console.error('Global error:', e.error);
showErrorMessage('An unexpected error occurred.'); showErrorMessage('An unexpected error occurred.');
}); });
@@ -403,7 +403,7 @@ function initializeEventHandlers() {
} }
// Delete bucket buttons // Delete bucket buttons
document.addEventListener('click', function(e) { document.addEventListener('click', function (e) {
if (e.target.closest('.delete-bucket-btn')) { if (e.target.closest('.delete-bucket-btn')) {
const button = e.target.closest('.delete-bucket-btn'); const button = e.target.closest('.delete-bucket-btn');
const bucketName = button.getAttribute('data-bucket-name'); const bucketName = button.getAttribute('data-bucket-name');
@@ -429,7 +429,7 @@ function initializeEventHandlers() {
// Enable quota checkbox for create bucket form // Enable quota checkbox for create bucket form
const enableQuotaCheckbox = document.getElementById('enableQuota'); const enableQuotaCheckbox = document.getElementById('enableQuota');
if (enableQuotaCheckbox) { if (enableQuotaCheckbox) {
enableQuotaCheckbox.addEventListener('change', function() { enableQuotaCheckbox.addEventListener('change', function () {
const quotaSettings = document.getElementById('quotaSettings'); const quotaSettings = document.getElementById('quotaSettings');
if (this.checked) { if (this.checked) {
quotaSettings.style.display = 'block'; quotaSettings.style.display = 'block';
@@ -442,7 +442,7 @@ function initializeEventHandlers() {
// Enable quota checkbox for quota modal // Enable quota checkbox for quota modal
const quotaEnabledCheckbox = document.getElementById('quotaEnabled'); const quotaEnabledCheckbox = document.getElementById('quotaEnabled');
if (quotaEnabledCheckbox) { if (quotaEnabledCheckbox) {
quotaEnabledCheckbox.addEventListener('change', function() { quotaEnabledCheckbox.addEventListener('change', function () {
const quotaSizeSettings = document.getElementById('quotaSizeSettings'); const quotaSizeSettings = document.getElementById('quotaSizeSettings');
if (this.checked) { if (this.checked) {
quotaSizeSettings.style.display = 'block'; quotaSizeSettings.style.display = 'block';
@@ -735,7 +735,7 @@ function exportVolumes() {
for (let i = 0; i < cells.length - 1; i++) { for (let i = 0; i < cells.length - 1; i++) {
rowData.push(`"${cells[i].textContent.trim().replace(/"/g, '""')}"`); rowData.push(`"${cells[i].textContent.trim().replace(/"/g, '""')}"`);
} }
csv += rowData.join(',') + '\n'; csv += rowData.join(',') + '\n';
}); });
downloadCSV(csv, 'seaweedfs-volumes.csv'); downloadCSV(csv, 'seaweedfs-volumes.csv');
@@ -889,7 +889,7 @@ function confirmDeleteCollection(button) {
modal.show(); modal.show();
// Set up confirm button // Set up confirm button
document.getElementById('confirmDeleteCollection').onclick = function() { document.getElementById('confirmDeleteCollection').onclick = function () {
deleteCollection(collectionName); deleteCollection(collectionName);
}; };
} }
@@ -1200,7 +1200,7 @@ async function submitUploadFile() {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
// Handle progress // Handle progress
xhr.upload.addEventListener('progress', function(e) { xhr.upload.addEventListener('progress', function (e) {
if (e.lengthComputable) { if (e.lengthComputable) {
const percentComplete = Math.round((e.loaded / e.total) * 100); const percentComplete = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = percentComplete + '%'; progressBar.style.width = percentComplete + '%';
@@ -1211,7 +1211,7 @@ async function submitUploadFile() {
}); });
// Handle completion // Handle completion
xhr.addEventListener('load', function() { xhr.addEventListener('load', function () {
if (xhr.status === 200) { if (xhr.status === 200) {
try { try {
const response = JSON.parse(xhr.responseText); const response = JSON.parse(xhr.responseText);
@@ -1258,13 +1258,13 @@ async function submitUploadFile() {
}); });
// Handle errors // Handle errors
xhr.addEventListener('error', function() { xhr.addEventListener('error', function () {
showErrorMessage('Failed to upload files. Please check your connection and try again.'); showErrorMessage('Failed to upload files. Please check your connection and try again.');
progressContainer.style.display = 'none'; progressContainer.style.display = 'none';
}); });
// Handle abort // Handle abort
xhr.addEventListener('abort', function() { xhr.addEventListener('abort', function () {
showErrorMessage('File upload was cancelled.'); showErrorMessage('File upload was cancelled.');
progressContainer.style.display = 'none'; progressContainer.style.display = 'none';
}); });
@@ -1404,7 +1404,7 @@ function setupFileManagerEventHandlers() {
// Handle Enter key in folder name input // Handle Enter key in folder name input
const folderNameInput = document.getElementById('folderName'); const folderNameInput = document.getElementById('folderName');
if (folderNameInput) { if (folderNameInput) {
folderNameInput.addEventListener('keypress', function(e) { folderNameInput.addEventListener('keypress', function (e) {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault(); e.preventDefault();
submitCreateFolder(); submitCreateFolder();
@@ -1415,7 +1415,7 @@ function setupFileManagerEventHandlers() {
// Handle file selection change to show preview // Handle file selection change to show preview
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
if (fileInput) { if (fileInput) {
fileInput.addEventListener('change', function(e) { fileInput.addEventListener('change', function (e) {
updateFileListPreview(); updateFileListPreview();
}); });
} }
@@ -1423,7 +1423,7 @@ function setupFileManagerEventHandlers() {
// Setup checkbox event listeners for file selection // Setup checkbox event listeners for file selection
const checkboxes = document.querySelectorAll('.file-checkbox'); const checkboxes = document.querySelectorAll('.file-checkbox');
checkboxes.forEach(checkbox => { checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() { checkbox.addEventListener('change', function () {
updateDeleteSelectedButton(); updateDeleteSelectedButton();
updateSelectAllCheckbox(); updateSelectAllCheckbox();
}); });
@@ -1435,14 +1435,14 @@ function setupFileManagerEventHandlers() {
// Clear form when modals are hidden // Clear form when modals are hidden
const createFolderModal = document.getElementById('createFolderModal'); const createFolderModal = document.getElementById('createFolderModal');
if (createFolderModal) { if (createFolderModal) {
createFolderModal.addEventListener('hidden.bs.modal', function() { createFolderModal.addEventListener('hidden.bs.modal', function () {
document.getElementById('folderName').value = ''; document.getElementById('folderName').value = '';
}); });
} }
const uploadFileModal = document.getElementById('uploadFileModal'); const uploadFileModal = document.getElementById('uploadFileModal');
if (uploadFileModal) { if (uploadFileModal) {
uploadFileModal.addEventListener('hidden.bs.modal', function() { uploadFileModal.addEventListener('hidden.bs.modal', function () {
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
const progressContainer = document.getElementById('uploadProgress'); const progressContainer = document.getElementById('uploadProgress');
const fileListPreview = document.getElementById('fileListPreview'); const fileListPreview = document.getElementById('fileListPreview');
@@ -2060,7 +2060,7 @@ function escapeHtml(text) {
'"': '&quot;', '"': '&quot;',
"'": '&#039;' "'": '&#039;'
}; };
return text.replace(/[&<>"']/g, function(m) { return map[m]; }); return text.replace(/[&<>"']/g, function (m) { return map[m]; });
} }
// ============================================================================ // ============================================================================
@@ -2131,7 +2131,7 @@ async function editUser(username) {
// Populate edit form // Populate edit form
document.getElementById('editUsername').value = username; document.getElementById('editUsername').value = username;
document.getElementById('editEmail').value = user.email || ''; document.getElementById('editEmail').value = user.email || '';
// Set selected actions // Set selected actions
const actionsSelect = document.getElementById('editActions'); const actionsSelect = document.getElementById('editActions');
@@ -2139,6 +2139,14 @@ async function editUser(username) {
option.selected = user.actions && user.actions.includes(option.value); option.selected = user.actions && user.actions.includes(option.value);
}); });
// Set selected policies
const policiesSelect = document.getElementById('editPolicies');
if (policiesSelect) {
Array.from(policiesSelect.options).forEach(option => {
option.selected = user.policy_names && user.policy_names.includes(option.value);
});
}
// Show modal // Show modal
const modal = new bootstrap.Modal(document.getElementById('editUserModal')); const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
modal.show(); modal.show();
@@ -2159,9 +2167,14 @@ async function handleUpdateUser() {
const actionsSelect = document.getElementById('editActions'); const actionsSelect = document.getElementById('editActions');
const selectedActions = Array.from(actionsSelect.selectedOptions).map(option => option.value); const selectedActions = Array.from(actionsSelect.selectedOptions).map(option => option.value);
// Get selected policies
const policiesSelect = document.getElementById('editPolicies');
const selectedPolicies = policiesSelect ? Array.from(policiesSelect.selectedOptions).map(option => option.value) : [];
const userData = { const userData = {
email: formData.get('email'), email: formData.get('email'),
actions: selectedActions actions: selectedActions,
policy_names: selectedPolicies
}; };
try { try {
@@ -2260,16 +2273,16 @@ function createUserDetailsContent(user) {
<h6 class="text-muted">Permissions</h6> <h6 class="text-muted">Permissions</h6>
<div class="mb-3"> <div class="mb-3">
${user.actions && user.actions.length > 0 ? ${user.actions && user.actions.length > 0 ?
user.actions.map(action => `<span class="badge bg-info me-1">${action}</span>`).join('') : user.actions.map(action => `<span class="badge bg-info me-1">${action}</span>`).join('') :
'<span class="text-muted">No permissions assigned</span>' '<span class="text-muted">No permissions assigned</span>'
} }
</div> </div>
<h6 class="text-muted">Access Keys</h6> <h6 class="text-muted">Access Keys</h6>
${user.access_keys && user.access_keys.length > 0 ? ${user.access_keys && user.access_keys.length > 0 ?
createAccessKeysTable(user.access_keys) : createAccessKeysTable(user.access_keys) :
'<p class="text-muted">No access keys</p>' '<p class="text-muted">No access keys</p>'
} }
</div> </div>
</div> </div>
`; `;
@@ -2523,11 +2536,10 @@ function showModal(title, content) {
modal.show(); modal.show();
// Remove modal from DOM when hidden // Remove modal from DOM when hidden
document.getElementById(modalId).addEventListener('hidden.bs.modal', function() { document.getElementById(modalId).addEventListener('hidden.bs.modal', function () {
this.remove(); this.remove();
}); });
} }

View File

@@ -223,6 +223,13 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
</select> </select>
<small class="form-text text-muted">Hold Ctrl/Cmd to select multiple permissions</small> <small class="form-text text-muted">Hold Ctrl/Cmd to select multiple permissions</small>
</div> </div>
<div class="mb-3">
<label for="policies" class="form-label">Attached Policies</label>
<select multiple class="form-control" id="policies" name="policies" size="5">
<!-- Options loaded dynamically -->
</select>
<small class="form-text text-muted">Hold Ctrl/Cmd to select multiple policies</small>
</div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="generateKey" name="generateKey" checked> <input type="checkbox" class="form-check-input" id="generateKey" name="generateKey" checked>
<label class="form-check-label" for="generateKey"> <label class="form-check-label" for="generateKey">
@@ -275,6 +282,12 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
</optgroup> </optgroup>
</select> </select>
</div> </div>
<div class="mb-3">
<label for="editPolicies" class="form-label">Attached Policies</label>
<select multiple class="form-control" id="editPolicies" name="policies" size="5">
<!-- Options loaded dynamically -->
</select>
</div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@@ -336,6 +349,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
<!-- JavaScript for user management --> <!-- JavaScript for user management -->
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Event delegation for user action buttons // Event delegation for user action buttons
document.addEventListener('click', function(e) { document.addEventListener('click', function(e) {
const button = e.target.closest('[data-action]'); const button = e.target.closest('[data-action]');
@@ -359,8 +373,67 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
break; break;
} }
}); });
// Event delegation for delete access key buttons
document.addEventListener('click', function(e) {
const deleteBtn = e.target.closest('.delete-access-key-btn');
if (deleteBtn) {
const username = deleteBtn.getAttribute('data-username');
const accessKey = deleteBtn.getAttribute('data-access-key');
deleteAccessKey(username, accessKey);
}
});
// Load policies for dropdowns
loadPolicies();
}); });
// Load policies
async function loadPolicies() {
try {
const response = await fetch('/api/object-store/policies');
if (response.ok) {
const data = await response.json();
const policies = data.policies || [];
const createSelect = document.getElementById('policies');
const editSelect = document.getElementById('editPolicies');
// Check if elements exist
if (!createSelect || !editSelect) {
console.warn('Policy select elements not found');
return;
}
// Clear existing options
createSelect.innerHTML = '';
editSelect.innerHTML = '';
if (policies && policies.length > 0) {
policies.forEach(policy => {
const option = document.createElement('option');
option.value = policy.name;
option.textContent = policy.name;
createSelect.appendChild(option);
editSelect.appendChild(option.cloneNode(true));
});
} else {
const emptyOption = document.createElement('option');
emptyOption.disabled = true;
emptyOption.textContent = 'No policies available';
createSelect.appendChild(emptyOption);
editSelect.appendChild(emptyOption.cloneNode(true));
}
} else {
const error = await response.json().catch(() => ({}));
showErrorMessage('Failed to load policies: ' + (error.error || 'Unknown error'));
}
} catch (error) {
console.error('Error loading policies:', error);
showErrorMessage('Failed to load policies: ' + error.message);
}
}
// Show user details modal // Show user details modal
async function showUserDetails(username) { async function showUserDetails(username) {
try { try {
@@ -396,6 +469,14 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
option.selected = user.actions && user.actions.includes(option.value); option.selected = user.actions && user.actions.includes(option.value);
}); });
// Set selected policies
const policiesSelect = document.getElementById('editPolicies');
if (policiesSelect) {
Array.from(policiesSelect.options).forEach(option => {
option.selected = user.policy_names && user.policy_names.includes(option.value);
});
}
// Show modal // Show modal
const modal = new bootstrap.Modal(document.getElementById('editUserModal')); const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
modal.show(); modal.show();
@@ -458,6 +539,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
username: formData.get('username'), username: formData.get('username'),
email: formData.get('email'), email: formData.get('email'),
actions: Array.from(document.getElementById('actions').selectedOptions).map(option => option.value), actions: Array.from(document.getElementById('actions').selectedOptions).map(option => option.value),
policy_names: Array.from(document.getElementById('policies').selectedOptions).map(option => option.value),
generate_key: document.getElementById('generateKey').checked generate_key: document.getElementById('generateKey').checked
}; };
@@ -494,41 +576,6 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
} }
} }
// Handle update user form submission
async function handleUpdateUser() {
const username = document.getElementById('editUsername').value;
const formData = new FormData(document.getElementById('editUserForm'));
const userData = {
email: formData.get('email'),
actions: Array.from(document.getElementById('editActions').selectedOptions).map(option => option.value)
};
try {
const response = await fetch(`/api/users/${username}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
if (response.ok) {
showSuccessMessage('User updated successfully');
// Close modal and refresh page
const modal = bootstrap.Modal.getInstance(document.getElementById('editUserModal'));
modal.hide();
setTimeout(() => window.location.reload(), 1000);
} else {
const error = await response.json();
showErrorMessage('Failed to update user: ' + (error.error || 'Unknown error'));
}
} catch (error) {
console.error('Error updating user:', error);
showErrorMessage('Failed to update user: ' + error.message);
}
}
// Create user details content // Create user details content
function createUserDetailsContent(user) { function createUserDetailsContent(user) {
@@ -545,17 +592,27 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
detailsHtml += '<div class="mb-3">'; detailsHtml += '<div class="mb-3">';
if (user.actions && user.actions.length > 0) { if (user.actions && user.actions.length > 0) {
detailsHtml += user.actions.map(function(action) { detailsHtml += user.actions.map(function(action) {
return '<span class="badge bg-info me-1">' + action + '</span>'; return '<span class="badge bg-info me-1">' + escapeHtml(action) + '</span>';
}).join(''); }).join('');
} else { } else {
detailsHtml += '<span class="text-muted">No permissions assigned</span>'; detailsHtml += '<span class="text-muted">No permissions assigned</span>';
} }
detailsHtml += '</div>'; detailsHtml += '</div>';
detailsHtml += '<h6 class="text-muted">Access Keys</h6>'; detailsHtml += '<h6 class="text-muted">Attached Policy Names</h6>';
detailsHtml += '<div class="mb-3">';
if (user.policy_names && user.policy_names.length > 0) {
detailsHtml += user.policy_names.map(function(policy) {
return '<span class="badge bg-secondary me-1">' + escapeHtml(policy) + '</span>';
}).join('');
} else {
detailsHtml += '<span class="text-muted">No policies attached</span>';
}
detailsHtml += '</div>';
detailsHtml += '<h6 class="text-muted">Access Keys</h6>';
if (user.access_keys && user.access_keys.length > 0) { if (user.access_keys && user.access_keys.length > 0) {
detailsHtml += '<div class="mb-2">'; detailsHtml += '<div class="mb-2">';
user.access_keys.forEach(function(key) { user.access_keys.forEach(function(key) {
detailsHtml += '<div><code class="text-muted">' + key.access_key + '</code></div>'; detailsHtml += '<div><code class="text-muted">' + escapeHtml(key.access_key) + '</code></div>';
}); });
detailsHtml += '</div>'; detailsHtml += '</div>';
} else { } else {
@@ -579,10 +636,10 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
user.access_keys.forEach(function(key) { user.access_keys.forEach(function(key) {
keysHtml += '<tr>'; keysHtml += '<tr>';
keysHtml += '<td><code>' + key.access_key + '</code></td>'; keysHtml += '<td><code>' + escapeHtml(key.access_key) + '</code></td>';
keysHtml += '<td><span class="badge bg-success">Active</span></td>'; keysHtml += '<td><span class="badge bg-success">Active</span></td>';
keysHtml += '<td>'; keysHtml += '<td>';
keysHtml += '<button class="btn btn-outline-danger btn-sm" onclick="deleteAccessKey(\'' + user.username + '\', \'' + key.access_key + '\')">'; keysHtml += '<button class="btn btn-outline-danger btn-sm delete-access-key-btn" data-username="' + escapeHtml(user.username) + '" data-access-key="' + escapeHtml(key.access_key) + '">';
keysHtml += '<i class="fas fa-trash"></i> Delete'; keysHtml += '<i class="fas fa-trash"></i> Delete';
keysHtml += '</button>'; keysHtml += '</button>';
keysHtml += '</td>'; keysHtml += '</td>';

File diff suppressed because one or more lines are too long

View File

@@ -77,6 +77,9 @@ type ActionRequest struct {
// RequestContext contains additional request information // RequestContext contains additional request information
RequestContext map[string]interface{} `json:"requestContext,omitempty"` RequestContext map[string]interface{} `json:"requestContext,omitempty"`
// PolicyNames to evaluate (overrides role-based policies if present)
PolicyNames []string `json:"policyNames,omitempty"`
} }
// NewIAMManager creates a new IAM manager // NewIAMManager creates a new IAM manager
@@ -281,14 +284,32 @@ func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest
return false, fmt.Errorf("IAM manager not initialized") return false, fmt.Errorf("IAM manager not initialized")
} }
// Validate session token first (skip for OIDC tokens which are already validated) // Validate session token if present (skip for OIDC tokens which are already validated,
if !isOIDCToken(request.SessionToken) { // and skip for empty tokens which represent static access keys)
if request.SessionToken != "" && !isOIDCToken(request.SessionToken) {
_, err := m.stsService.ValidateSessionToken(ctx, request.SessionToken) _, err := m.stsService.ValidateSessionToken(ctx, request.SessionToken)
if err != nil { if err != nil {
return false, fmt.Errorf("invalid session: %w", err) return false, fmt.Errorf("invalid session: %w", err)
} }
} }
// Create evaluation context
evalCtx := &policy.EvaluationContext{
Principal: request.Principal,
Action: request.Action,
Resource: request.Resource,
RequestContext: request.RequestContext,
}
// If explicit policy names are provided (e.g. from user identity), evaluate them directly
if len(request.PolicyNames) > 0 {
result, err := m.policyEngine.Evaluate(ctx, "", evalCtx, request.PolicyNames)
if err != nil {
return false, fmt.Errorf("policy evaluation failed: %w", err)
}
return result.Effect == policy.EffectAllow, nil
}
// Extract role name from principal ARN // Extract role name from principal ARN
roleName := utils.ExtractRoleNameFromPrincipal(request.Principal) roleName := utils.ExtractRoleNameFromPrincipal(request.Principal)
if roleName == "" { if roleName == "" {
@@ -301,14 +322,6 @@ func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest
return false, fmt.Errorf("role not found: %s", roleName) return false, fmt.Errorf("role not found: %s", roleName)
} }
// Create evaluation context
evalCtx := &policy.EvaluationContext{
Principal: request.Principal,
Action: request.Action,
Resource: request.Resource,
RequestContext: request.RequestContext,
}
// Evaluate policies attached to the role // Evaluate policies attached to the role
result, err := m.policyEngine.Evaluate(ctx, "", evalCtx, roleDef.AttachedPolicies) result, err := m.policyEngine.Evaluate(ctx, "", evalCtx, roleDef.AttachedPolicies)
if err != nil { if err != nil {

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: filer.proto // source: filer.proto
package filer_pb package filer_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3 // - protoc v6.33.1
// source: filer.proto // source: filer.proto
package filer_pb package filer_pb

View File

@@ -27,6 +27,7 @@ message Identity {
Account account = 4; Account account = 4;
bool disabled = 5; // User status: false = enabled (default), true = disabled bool disabled = 5; // User status: false = enabled (default), true = disabled
repeated string service_account_ids = 6; // IDs of service accounts owned by this user repeated string service_account_ids = 6; // IDs of service accounts owned by this user
repeated string policy_names = 7;
} }
message Credential { message Credential {

View File

@@ -89,6 +89,7 @@ type Identity struct {
Account *Account `protobuf:"bytes,4,opt,name=account,proto3" json:"account,omitempty"` Account *Account `protobuf:"bytes,4,opt,name=account,proto3" json:"account,omitempty"`
Disabled bool `protobuf:"varint,5,opt,name=disabled,proto3" json:"disabled,omitempty"` // User status: false = enabled (default), true = disabled Disabled bool `protobuf:"varint,5,opt,name=disabled,proto3" json:"disabled,omitempty"` // User status: false = enabled (default), true = disabled
ServiceAccountIds []string `protobuf:"bytes,6,rep,name=service_account_ids,json=serviceAccountIds,proto3" json:"service_account_ids,omitempty"` // IDs of service accounts owned by this user ServiceAccountIds []string `protobuf:"bytes,6,rep,name=service_account_ids,json=serviceAccountIds,proto3" json:"service_account_ids,omitempty"` // IDs of service accounts owned by this user
PolicyNames []string `protobuf:"bytes,7,rep,name=policy_names,json=policyNames,proto3" json:"policy_names,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@@ -165,6 +166,13 @@ func (x *Identity) GetServiceAccountIds() []string {
return nil return nil
} }
func (x *Identity) GetPolicyNames() []string {
if x != nil {
return x.PolicyNames
}
return nil
}
type Credential struct { type Credential struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
AccessKey string `protobuf:"bytes,1,opt,name=access_key,json=accessKey,proto3" json:"access_key,omitempty"` AccessKey string `protobuf:"bytes,1,opt,name=access_key,json=accessKey,proto3" json:"access_key,omitempty"`
@@ -405,14 +413,15 @@ const file_iam_proto_rawDesc = "" +
"identities\x18\x01 \x03(\v2\x10.iam_pb.IdentityR\n" + "identities\x18\x01 \x03(\v2\x10.iam_pb.IdentityR\n" +
"identities\x12+\n" + "identities\x12+\n" +
"\baccounts\x18\x02 \x03(\v2\x0f.iam_pb.AccountR\baccounts\x12A\n" + "\baccounts\x18\x02 \x03(\v2\x0f.iam_pb.AccountR\baccounts\x12A\n" +
"\x10service_accounts\x18\x03 \x03(\v2\x16.iam_pb.ServiceAccountR\x0fserviceAccounts\"\xe5\x01\n" + "\x10service_accounts\x18\x03 \x03(\v2\x16.iam_pb.ServiceAccountR\x0fserviceAccounts\"\x88\x02\n" +
"\bIdentity\x12\x12\n" + "\bIdentity\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x124\n" + "\x04name\x18\x01 \x01(\tR\x04name\x124\n" +
"\vcredentials\x18\x02 \x03(\v2\x12.iam_pb.CredentialR\vcredentials\x12\x18\n" + "\vcredentials\x18\x02 \x03(\v2\x12.iam_pb.CredentialR\vcredentials\x12\x18\n" +
"\aactions\x18\x03 \x03(\tR\aactions\x12)\n" + "\aactions\x18\x03 \x03(\tR\aactions\x12)\n" +
"\aaccount\x18\x04 \x01(\v2\x0f.iam_pb.AccountR\aaccount\x12\x1a\n" + "\aaccount\x18\x04 \x01(\v2\x0f.iam_pb.AccountR\aaccount\x12\x1a\n" +
"\bdisabled\x18\x05 \x01(\bR\bdisabled\x12.\n" + "\bdisabled\x18\x05 \x01(\bR\bdisabled\x12.\n" +
"\x13service_account_ids\x18\x06 \x03(\tR\x11serviceAccountIds\"b\n" + "\x13service_account_ids\x18\x06 \x03(\tR\x11serviceAccountIds\x12!\n" +
"\fpolicy_names\x18\a \x03(\tR\vpolicyNames\"b\n" +
"\n" + "\n" +
"Credential\x12\x1d\n" + "Credential\x12\x1d\n" +
"\n" + "\n" +

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: master.proto // source: master.proto
package master_pb package master_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3 // - protoc v6.33.1
// source: master.proto // source: master.proto
package master_pb package master_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: mount.proto // source: mount.proto
package mount_pb package mount_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3 // - protoc v6.33.1
// source: mount.proto // source: mount.proto
package mount_pb package mount_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: mq_agent.proto // source: mq_agent.proto
package mq_agent_pb package mq_agent_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3 // - protoc v6.33.1
// source: mq_agent.proto // source: mq_agent.proto
package mq_agent_pb package mq_agent_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: mq_broker.proto // source: mq_broker.proto
package mq_pb package mq_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3 // - protoc v6.33.1
// source: mq_broker.proto // source: mq_broker.proto
package mq_pb package mq_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: remote.proto // source: remote.proto
package remote_pb package remote_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: s3.proto // source: s3.proto
package s3_pb package s3_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3 // - protoc v6.33.1
// source: s3.proto // source: s3.proto
package s3_pb package s3_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: mq_schema.proto // source: mq_schema.proto
package schema_pb package schema_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: volume_server.proto // source: volume_server.proto
package volume_server_pb package volume_server_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3 // - protoc v6.33.1
// source: volume_server.proto // source: volume_server.proto
package volume_server_pb package volume_server_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.36.6 // protoc-gen-go v1.36.6
// protoc v5.29.3 // protoc v6.33.1
// source: worker.proto // source: worker.proto
package worker_pb package worker_pb

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.5.1 // - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3 // - protoc v6.33.1
// source: worker.proto // source: worker.proto
package worker_pb package worker_pb

View File

@@ -66,8 +66,9 @@ type Identity struct {
Account *Account Account *Account
Credentials []*Credential Credentials []*Credential
Actions []Action Actions []Action
PrincipalArn string // ARN for IAM authorization (e.g., "arn:aws:iam::account-id:user/username") PolicyNames []string // Attached IAM policy names
Disabled bool // User status: false = enabled (default), true = disabled PrincipalArn string // ARN for IAM authorization (e.g., "arn:aws:iam::account-id:user/username")
Disabled bool // User status: false = enabled (default), true = disabled
} }
// Account represents a system user, a system user can // Account represents a system user, a system user can
@@ -310,6 +311,7 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
Actions: nil, Actions: nil,
PrincipalArn: generatePrincipalArn(ident.Name), PrincipalArn: generatePrincipalArn(ident.Name),
Disabled: ident.Disabled, // false (default) = enabled, true = disabled Disabled: ident.Disabled, // false (default) = enabled, true = disabled
PolicyNames: ident.PolicyNames,
} }
switch { switch {
case ident.Name == AccountAnonymous.Id: case ident.Name == AccountAnonymous.Id:
@@ -939,9 +941,10 @@ func (iam *IdentityAccessManagement) authenticateJWTWithIAM(r *http.Request) (*I
// Convert IAMIdentity to existing Identity structure // Convert IAMIdentity to existing Identity structure
identity := &Identity{ identity := &Identity{
Name: iamIdentity.Name, Name: iamIdentity.Name,
Account: iamIdentity.Account, Account: iamIdentity.Account,
Actions: []Action{}, // Empty - authorization handled by policy engine Actions: []Action{}, // Empty - authorization handled by policy engine
PolicyNames: iamIdentity.PolicyNames,
} }
// Store session info in request headers for later authorization // Store session info in request headers for later authorization
@@ -997,8 +1000,9 @@ func (iam *IdentityAccessManagement) authorizeWithIAM(r *http.Request, identity
// Create IAMIdentity for authorization // Create IAMIdentity for authorization
iamIdentity := &IAMIdentity{ iamIdentity := &IAMIdentity{
Name: identity.Name, Name: identity.Name,
Account: identity.Account, Account: identity.Account,
PolicyNames: identity.PolicyNames,
} }
// Determine authorization path and configure identity // Determine authorization path and configure identity

View File

@@ -193,6 +193,7 @@ func (s3iam *S3IAMIntegration) AuthorizeAction(ctx context.Context, identity *IA
Resource: resourceArn, Resource: resourceArn,
SessionToken: identity.SessionToken, SessionToken: identity.SessionToken,
RequestContext: requestContext, RequestContext: requestContext,
PolicyNames: identity.PolicyNames,
} }
// Check if action is allowed using our policy engine // Check if action is allowed using our policy engine
@@ -214,6 +215,7 @@ type IAMIdentity struct {
Principal string Principal string
SessionToken string SessionToken string
Account *Account Account *Account
PolicyNames []string
} }
// IsAdmin checks if the identity has admin privileges // IsAdmin checks if the identity has admin privileges
@@ -490,7 +492,8 @@ func (enhanced *EnhancedS3ApiServer) AuthenticateJWTRequest(r *http.Request) (*I
Name: iamIdentity.Name, Name: iamIdentity.Name,
Account: iamIdentity.Account, Account: iamIdentity.Account,
// Note: Actions will be determined by policy evaluation // Note: Actions will be determined by policy evaluation
Actions: []Action{}, // Empty - authorization handled by policy engine Actions: []Action{}, // Empty - authorization handled by policy engine
PolicyNames: iamIdentity.PolicyNames,
} }
// Store session token for later authorization // Store session token for later authorization