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:
159
internal/service/objectstore/objectstore.go
Normal file
159
internal/service/objectstore/objectstore.go
Normal 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")
|
||||
}
|
||||
Reference in New Issue
Block a user