mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-01-09 11:21:11 +08:00
Support Policy Attachment for Object Store Users (#7981)
Some checks failed
go: build dev binaries / cleanup (push) Has been cancelled
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Has been cancelled
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Has been cancelled
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been cancelled
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been cancelled
docker: build dev containers / build-dev-containers (push) Has been cancelled
End to End / FUSE Mount (push) Has been cancelled
EC Integration Tests / EC Integration Tests (push) Has been cancelled
FUSE Integration Tests / FUSE Integration Testing (push) Has been cancelled
go: build binary / Build (push) Has been cancelled
Java Client Integration Tests / Java Integration Tests (11) (push) Has been cancelled
Java Client Integration Tests / Java Integration Tests (17) (push) Has been cancelled
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Has been cancelled
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Has been cancelled
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Has been cancelled
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Has been cancelled
S3 IAM Integration Tests / IAM Unit Tests (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Has been cancelled
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
Some checks failed
go: build dev binaries / cleanup (push) Has been cancelled
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Has been cancelled
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Has been cancelled
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been cancelled
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been cancelled
docker: build dev containers / build-dev-containers (push) Has been cancelled
End to End / FUSE Mount (push) Has been cancelled
EC Integration Tests / EC Integration Tests (push) Has been cancelled
FUSE Integration Tests / FUSE Integration Testing (push) Has been cancelled
go: build binary / Build (push) Has been cancelled
Java Client Integration Tests / Java Integration Tests (11) (push) Has been cancelled
Java Client Integration Tests / Java Integration Tests (17) (push) Has been cancelled
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Has been cancelled
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Has been cancelled
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Has been cancelled
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Has been cancelled
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Has been cancelled
S3 IAM Integration Tests / IAM Unit Tests (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Has been cancelled
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Has been cancelled
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
* 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:
@@ -37,6 +37,7 @@ type ObjectStoreUser struct {
|
||||
AccessKey string `json:"access_key"`
|
||||
SecretKey string `json:"secret_key"`
|
||||
Permissions []string `json:"permissions"`
|
||||
PolicyNames []string `json:"policy_names"`
|
||||
}
|
||||
|
||||
type ObjectStoreUsersData struct {
|
||||
@@ -52,11 +53,13 @@ type CreateUserRequest struct {
|
||||
Email string `json:"email"`
|
||||
Actions []string `json:"actions"`
|
||||
GenerateKey bool `json:"generate_key"`
|
||||
PolicyNames []string `json:"policy_names"`
|
||||
}
|
||||
|
||||
type UpdateUserRequest struct {
|
||||
Email string `json:"email"`
|
||||
Actions []string `json:"actions"`
|
||||
Email string `json:"email"`
|
||||
Actions []string `json:"actions"`
|
||||
PolicyNames []string `json:"policy_names"`
|
||||
}
|
||||
|
||||
type UpdateUserPoliciesRequest struct {
|
||||
@@ -70,10 +73,11 @@ type AccessKeyInfo struct {
|
||||
}
|
||||
|
||||
type UserDetails struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Actions []string `json:"actions"`
|
||||
AccessKeys []AccessKeyInfo `json:"access_keys"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Actions []string `json:"actions"`
|
||||
PolicyNames []string `json:"policy_names"`
|
||||
AccessKeys []AccessKeyInfo `json:"access_keys"`
|
||||
}
|
||||
|
||||
type FilerNode struct {
|
||||
|
||||
@@ -21,8 +21,9 @@ func (s *AdminServer) CreateObjectStoreUser(req CreateUserRequest) (*ObjectStore
|
||||
|
||||
// Create new identity
|
||||
newIdentity := &iam_pb.Identity{
|
||||
Name: req.Username,
|
||||
Actions: req.Actions,
|
||||
Name: req.Username,
|
||||
Actions: req.Actions,
|
||||
PolicyNames: req.PolicyNames,
|
||||
}
|
||||
|
||||
// Add account if email is provided
|
||||
@@ -63,6 +64,7 @@ func (s *AdminServer) CreateObjectStoreUser(req CreateUserRequest) (*ObjectStore
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
Permissions: req.Actions,
|
||||
PolicyNames: req.PolicyNames,
|
||||
}
|
||||
|
||||
return user, nil
|
||||
@@ -91,12 +93,17 @@ func (s *AdminServer) UpdateObjectStoreUser(username string, req UpdateUserReque
|
||||
Account: identity.Account,
|
||||
Credentials: identity.Credentials,
|
||||
Actions: identity.Actions,
|
||||
PolicyNames: identity.PolicyNames,
|
||||
}
|
||||
|
||||
// Update actions if provided
|
||||
if len(req.Actions) > 0 {
|
||||
if req.Actions != nil {
|
||||
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
|
||||
if req.Email != "" {
|
||||
@@ -120,6 +127,7 @@ func (s *AdminServer) UpdateObjectStoreUser(username string, req UpdateUserReque
|
||||
Username: username,
|
||||
Email: req.Email,
|
||||
Permissions: updatedIdentity.Actions,
|
||||
PolicyNames: updatedIdentity.PolicyNames,
|
||||
}
|
||||
|
||||
// Get first access key for display
|
||||
@@ -169,8 +177,9 @@ func (s *AdminServer) GetObjectStoreUserDetails(username string) (*UserDetails,
|
||||
}
|
||||
|
||||
details := &UserDetails{
|
||||
Username: username,
|
||||
Actions: identity.Actions,
|
||||
Username: username,
|
||||
Actions: identity.Actions,
|
||||
PolicyNames: identity.PolicyNames,
|
||||
}
|
||||
|
||||
// Set email from account if available
|
||||
@@ -295,6 +304,7 @@ func (s *AdminServer) UpdateUserPolicies(username string, actions []string) erro
|
||||
Account: identity.Account,
|
||||
Credentials: identity.Credentials,
|
||||
Actions: actions,
|
||||
PolicyNames: identity.PolicyNames,
|
||||
}
|
||||
|
||||
// Update user using credential manager
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -29,7 +30,12 @@ func (h *UserHandlers) ShowObjectStoreUsers(c *gin.Context) {
|
||||
usersData := h.getObjectStoreUsersData(c)
|
||||
|
||||
// Render HTML template
|
||||
// Add cache-control headers to prevent browser caching of inline JavaScript
|
||||
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)
|
||||
layoutComponent := layout.Layout(c, usersComponent)
|
||||
err := layoutComponent.Render(c.Request.Context(), c.Writer)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -223,6 +223,13 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
|
||||
</select>
|
||||
<small class="form-text text-muted">Hold Ctrl/Cmd to select multiple permissions</small>
|
||||
</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">
|
||||
<input type="checkbox" class="form-check-input" id="generateKey" name="generateKey" checked>
|
||||
<label class="form-check-label" for="generateKey">
|
||||
@@ -275,6 +282,12 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
|
||||
</optgroup>
|
||||
</select>
|
||||
</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>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -336,6 +349,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
|
||||
<!-- JavaScript for user management -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Event delegation for user action buttons
|
||||
document.addEventListener('click', function(e) {
|
||||
const button = e.target.closest('[data-action]');
|
||||
@@ -359,8 +373,67 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
|
||||
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
|
||||
async function showUserDetails(username) {
|
||||
try {
|
||||
@@ -395,6 +468,14 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
|
||||
Array.from(actionsSelect.options).forEach(option => {
|
||||
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
|
||||
const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
|
||||
@@ -458,6 +539,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
|
||||
username: formData.get('username'),
|
||||
email: formData.get('email'),
|
||||
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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
function createUserDetailsContent(user) {
|
||||
@@ -545,17 +592,27 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
|
||||
detailsHtml += '<div class="mb-3">';
|
||||
if (user.actions && user.actions.length > 0) {
|
||||
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('');
|
||||
} else {
|
||||
detailsHtml += '<span class="text-muted">No permissions assigned</span>';
|
||||
}
|
||||
detailsHtml += '</div>';
|
||||
detailsHtml += '<h6 class="text-muted">Access Keys</h6>';
|
||||
detailsHtml += '</div>';
|
||||
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) {
|
||||
detailsHtml += '<div class="mb-2">';
|
||||
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>';
|
||||
} else {
|
||||
@@ -579,10 +636,10 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
|
||||
|
||||
user.access_keys.forEach(function(key) {
|
||||
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>';
|
||||
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 += '</button>';
|
||||
keysHtml += '</td>';
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -77,6 +77,9 @@ type ActionRequest struct {
|
||||
|
||||
// RequestContext contains additional request information
|
||||
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
|
||||
@@ -281,14 +284,32 @@ func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest
|
||||
return false, fmt.Errorf("IAM manager not initialized")
|
||||
}
|
||||
|
||||
// Validate session token first (skip for OIDC tokens which are already validated)
|
||||
if !isOIDCToken(request.SessionToken) {
|
||||
// Validate session token if present (skip for OIDC tokens which are already validated,
|
||||
// and skip for empty tokens which represent static access keys)
|
||||
if request.SessionToken != "" && !isOIDCToken(request.SessionToken) {
|
||||
_, err := m.stsService.ValidateSessionToken(ctx, request.SessionToken)
|
||||
if err != nil {
|
||||
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
|
||||
roleName := utils.ExtractRoleNameFromPrincipal(request.Principal)
|
||||
if roleName == "" {
|
||||
@@ -301,14 +322,6 @@ func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest
|
||||
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
|
||||
result, err := m.policyEngine.Evaluate(ctx, "", evalCtx, roleDef.AttachedPolicies)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: filer.proto
|
||||
|
||||
package filer_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.3
|
||||
// - protoc v6.33.1
|
||||
// source: filer.proto
|
||||
|
||||
package filer_pb
|
||||
|
||||
@@ -27,6 +27,7 @@ message Identity {
|
||||
Account account = 4;
|
||||
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 policy_names = 7;
|
||||
}
|
||||
|
||||
message Credential {
|
||||
|
||||
@@ -89,6 +89,7 @@ type Identity struct {
|
||||
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
|
||||
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
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -165,6 +166,13 @@ func (x *Identity) GetServiceAccountIds() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Identity) GetPolicyNames() []string {
|
||||
if x != nil {
|
||||
return x.PolicyNames
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Credential struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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\x12+\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" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x124\n" +
|
||||
"\vcredentials\x18\x02 \x03(\v2\x12.iam_pb.CredentialR\vcredentials\x12\x18\n" +
|
||||
"\aactions\x18\x03 \x03(\tR\aactions\x12)\n" +
|
||||
"\aaccount\x18\x04 \x01(\v2\x0f.iam_pb.AccountR\aaccount\x12\x1a\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" +
|
||||
"Credential\x12\x1d\n" +
|
||||
"\n" +
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: master.proto
|
||||
|
||||
package master_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.3
|
||||
// - protoc v6.33.1
|
||||
// source: master.proto
|
||||
|
||||
package master_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: mount.proto
|
||||
|
||||
package mount_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.3
|
||||
// - protoc v6.33.1
|
||||
// source: mount.proto
|
||||
|
||||
package mount_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: mq_agent.proto
|
||||
|
||||
package mq_agent_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.3
|
||||
// - protoc v6.33.1
|
||||
// source: mq_agent.proto
|
||||
|
||||
package mq_agent_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: mq_broker.proto
|
||||
|
||||
package mq_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.3
|
||||
// - protoc v6.33.1
|
||||
// source: mq_broker.proto
|
||||
|
||||
package mq_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: remote.proto
|
||||
|
||||
package remote_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: s3.proto
|
||||
|
||||
package s3_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.3
|
||||
// - protoc v6.33.1
|
||||
// source: s3.proto
|
||||
|
||||
package s3_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: mq_schema.proto
|
||||
|
||||
package schema_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: volume_server.proto
|
||||
|
||||
package volume_server_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.3
|
||||
// - protoc v6.33.1
|
||||
// source: volume_server.proto
|
||||
|
||||
package volume_server_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.6
|
||||
// protoc v5.29.3
|
||||
// protoc v6.33.1
|
||||
// source: worker.proto
|
||||
|
||||
package worker_pb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v5.29.3
|
||||
// - protoc v6.33.1
|
||||
// source: worker.proto
|
||||
|
||||
package worker_pb
|
||||
|
||||
@@ -66,8 +66,9 @@ type Identity struct {
|
||||
Account *Account
|
||||
Credentials []*Credential
|
||||
Actions []Action
|
||||
PrincipalArn string // ARN for IAM authorization (e.g., "arn:aws:iam::account-id:user/username")
|
||||
Disabled bool // User status: false = enabled (default), true = disabled
|
||||
PolicyNames []string // Attached IAM policy names
|
||||
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
|
||||
@@ -310,6 +311,7 @@ func (iam *IdentityAccessManagement) loadS3ApiConfiguration(config *iam_pb.S3Api
|
||||
Actions: nil,
|
||||
PrincipalArn: generatePrincipalArn(ident.Name),
|
||||
Disabled: ident.Disabled, // false (default) = enabled, true = disabled
|
||||
PolicyNames: ident.PolicyNames,
|
||||
}
|
||||
switch {
|
||||
case ident.Name == AccountAnonymous.Id:
|
||||
@@ -939,9 +941,10 @@ func (iam *IdentityAccessManagement) authenticateJWTWithIAM(r *http.Request) (*I
|
||||
|
||||
// Convert IAMIdentity to existing Identity structure
|
||||
identity := &Identity{
|
||||
Name: iamIdentity.Name,
|
||||
Account: iamIdentity.Account,
|
||||
Actions: []Action{}, // Empty - authorization handled by policy engine
|
||||
Name: iamIdentity.Name,
|
||||
Account: iamIdentity.Account,
|
||||
Actions: []Action{}, // Empty - authorization handled by policy engine
|
||||
PolicyNames: iamIdentity.PolicyNames,
|
||||
}
|
||||
|
||||
// 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
|
||||
iamIdentity := &IAMIdentity{
|
||||
Name: identity.Name,
|
||||
Account: identity.Account,
|
||||
Name: identity.Name,
|
||||
Account: identity.Account,
|
||||
PolicyNames: identity.PolicyNames,
|
||||
}
|
||||
|
||||
// Determine authorization path and configure identity
|
||||
|
||||
@@ -193,6 +193,7 @@ func (s3iam *S3IAMIntegration) AuthorizeAction(ctx context.Context, identity *IA
|
||||
Resource: resourceArn,
|
||||
SessionToken: identity.SessionToken,
|
||||
RequestContext: requestContext,
|
||||
PolicyNames: identity.PolicyNames,
|
||||
}
|
||||
|
||||
// Check if action is allowed using our policy engine
|
||||
@@ -214,6 +215,7 @@ type IAMIdentity struct {
|
||||
Principal string
|
||||
SessionToken string
|
||||
Account *Account
|
||||
PolicyNames []string
|
||||
}
|
||||
|
||||
// IsAdmin checks if the identity has admin privileges
|
||||
@@ -490,7 +492,8 @@ func (enhanced *EnhancedS3ApiServer) AuthenticateJWTRequest(r *http.Request) (*I
|
||||
Name: iamIdentity.Name,
|
||||
Account: iamIdentity.Account,
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user