Add RBAC support with roles, permissions, and session management. Implement middleware for authentication and CSRF protection. Enhance audit logging with additional fields. Update HTTP handlers and routes for new features.

This commit is contained in:
2025-12-13 17:44:09 +00:00
parent d69e01bbaf
commit 8100f87686
44 changed files with 3262 additions and 76 deletions

View File

@@ -0,0 +1,159 @@
package objectstore
import (
"context"
"database/sql"
"errors"
"fmt"
"time"
"github.com/example/storage-appliance/internal/audit"
"github.com/example/storage-appliance/internal/infra/minio"
"github.com/example/storage-appliance/internal/infra/crypto"
)
var (
ErrForbidden = errors.New("forbidden")
)
type Settings struct {
ID string
Name string
AccessKey string
SecretKey string
DataPath string
Port int
TLS bool
CreatedAt time.Time
}
type ObjectService struct {
DB *sql.DB
Minio *minio.Adapter
Audit audit.AuditLogger
// encryption key for secret storage
Key []byte
}
func NewObjectService(db *sql.DB, m *minio.Adapter, a audit.AuditLogger, key []byte) *ObjectService {
return &ObjectService{DB: db, Minio: m, Audit: a, Key: key}
}
func (s *ObjectService) SetSettings(ctx context.Context, user, role string, stMap map[string]any) error {
if role != "admin" {
return ErrForbidden
}
// convert map to Settings struct for local use
st := Settings{}
if v, ok := stMap["access_key"].(string); ok { st.AccessKey = v }
if v, ok := stMap["secret_key"].(string); ok { st.SecretKey = v }
if v, ok := stMap["data_path"].(string); ok { st.DataPath = v }
if v, ok := stMap["name"].(string); ok { st.Name = v }
if v, ok := stMap["port"].(int); ok { st.Port = v }
if v, ok := stMap["tls"].(bool); ok { st.TLS = v }
// encrypt access key and secret key
if len(s.Key) != 32 {
return errors.New("encryption key must be 32 bytes")
}
encAccess, err := crypto.Encrypt(s.Key, st.AccessKey)
if err != nil {
return err
}
encSecret, err := crypto.Encrypt(s.Key, st.SecretKey)
if err != nil {
return err
}
// upsert into DB (single row)
if _, err := s.DB.ExecContext(ctx, `INSERT OR REPLACE INTO object_storage (id, name, access_key, secret_key, data_path, port, tls) VALUES ('minio', ?, ?, ?, ?, ?, ?)` , st.Name, encAccess, encSecret, st.DataPath, st.Port, boolToInt(st.TLS)); err != nil {
return err
}
if s.Audit != nil {
s.Audit.Record(ctx, audit.Event{UserID: user, Action: "object.settings.update", ResourceType: "object_storage", ResourceID: "minio", Success: true})
}
if s.Minio != nil {
// write env file
settings := minio.Settings{AccessKey: st.AccessKey, SecretKey: st.SecretKey, DataPath: st.DataPath, Port: st.Port, TLS: st.TLS}
if err := s.Minio.WriteEnv(ctx, settings); err != nil {
return err
}
if err := s.Minio.Reload(ctx); err != nil {
return err
}
}
return nil
}
func (s *ObjectService) GetSettings(ctx context.Context) (map[string]any, error) {
var st Settings
row := s.DB.QueryRowContext(ctx, `SELECT name, access_key, secret_key, data_path, port, tls, created_at FROM object_storage WHERE id = 'minio'`)
var encAccess, encSecret string
var tlsInt int
if err := row.Scan(&st.Name, &encAccess, &encSecret, &st.DataPath, &st.Port, &tlsInt, &st.CreatedAt); err != nil {
return nil, err
}
st.TLS = tlsInt == 1
if len(s.Key) == 32 {
if A, err := crypto.Decrypt(s.Key, encAccess); err == nil { st.AccessKey = A }
if S, err := crypto.Decrypt(s.Key, encSecret); err == nil { st.SecretKey = S }
}
res := map[string]any{"name": st.Name, "access_key": st.AccessKey, "secret_key": st.SecretKey, "data_path": st.DataPath, "port": st.Port, "tls": st.TLS, "created_at": st.CreatedAt}
return res, nil
}
func boolToInt(b bool) int { if b { return 1 }; return 0 }
// ListBuckets via minio adapter or fallback to DB
func (s *ObjectService) ListBuckets(ctx context.Context) ([]string, error) {
if s.Minio != nil {
// ensure mc alias is configured
stMap, err := s.GetSettings(ctx)
if err != nil { return nil, err }
alias := "appliance"
mSet := minio.Settings{}
if v, ok := stMap["access_key"].(string); ok { mSet.AccessKey = v }
if v, ok := stMap["secret_key"].(string); ok { mSet.SecretKey = v }
if v, ok := stMap["data_path"].(string); ok { mSet.DataPath = v }
if v, ok := stMap["port"].(int); ok { mSet.Port = v }
if v, ok := stMap["tls"].(bool); ok { mSet.TLS = v }
s.Minio.ConfigureMC(ctx, alias, mSet)
return s.Minio.ListBuckets(ctx, alias)
}
// fallback to DB persisted buckets
rows, err := s.DB.QueryContext(ctx, `SELECT name FROM buckets`)
if err != nil { return nil, err }
defer rows.Close()
var res []string
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil { return nil, err }
res = append(res, name)
}
return res, nil
}
func (s *ObjectService) CreateBucket(ctx context.Context, user, role, name string) (string, error) {
if role != "admin" && role != "operator" { return "", ErrForbidden }
// attempt via minio adapter
if s.Minio != nil {
stMap, err := s.GetSettings(ctx)
if err != nil { return "", err }
alias := "appliance"
mSet := minio.Settings{}
if v, ok := stMap["access_key"].(string); ok { mSet.AccessKey = v }
if v, ok := stMap["secret_key"].(string); ok { mSet.SecretKey = v }
if v, ok := stMap["data_path"].(string); ok { mSet.DataPath = v }
if v, ok := stMap["port"].(int); ok { mSet.Port = v }
if v, ok := stMap["tls"].(bool); ok { mSet.TLS = v }
if err := s.Minio.ConfigureMC(ctx, alias, mSet); err != nil { return "", err }
if err := s.Minio.CreateBucket(ctx, alias, name); err != nil { return "", err }
// persist
id := fmt.Sprintf("bucket-%d", time.Now().UnixNano())
if _, err := s.DB.ExecContext(ctx, `INSERT INTO buckets (id, name) VALUES (?, ?)`, id, name); err != nil {
return "", err
}
if s.Audit != nil { s.Audit.Record(ctx, audit.Event{UserID: user, Action: "object.bucket.create", ResourceType: "bucket", ResourceID: name, Success: true}) }
return id, nil
}
return "", errors.New("no minio adapter configured")
}