mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Cluster: Improve API endpoint and CLI command logs
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -33,7 +33,7 @@ The API package exposes PhotoPrism’s HTTP endpoints via Gin handlers. Each fil
|
||||
|
||||
- Emit security events via `event.Audit*` (`AuditInfo`, `AuditWarn`, `AuditErr`, `AuditDebug`) and always build the slice as **Who → What → Outcome**.
|
||||
- **Who:** `ClientIP(c)` followed by the most specific actor context (`"session %s"`, `"client %s"`, `"user %s"`).
|
||||
- **What:** Resource constant plus action segments (for example, `string(acl.ResourceCluster)`, `"node %s"`). Place extra context such as counts or error placeholders in separate segments before the outcome.
|
||||
- **What:** Resource constant plus action segments (for example, `string(acl.ResourceCluster)`, `"node", "%s"`). Place extra context such as counts or error placeholders in separate segments before the outcome.
|
||||
- **Outcome:** End with a single token such as `status.Succeeded`, `status.Failed`, `status.Denied`, or `status.Error(err)` when the sanitized error message should be the outcome; nothing comes after it.
|
||||
- Prefer existing helpers (`ClientIP`, `clean.Log`, `clean.LogQuote`, `clean.Error`) instead of formatting values manually, and avoid inline `=` expressions.
|
||||
- Example patterns:
|
||||
@@ -42,7 +42,7 @@ The API package exposes PhotoPrism’s HTTP endpoints via Gin handlers. Each fil
|
||||
ClientIP(c),
|
||||
"session %s",
|
||||
string(acl.ResourceCluster),
|
||||
"node %s",
|
||||
"node", "%s",
|
||||
status.Deleted,
|
||||
}, s.RefID, uuid)
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ func ClusterGetNode(router *gin.RouterGroup) {
|
||||
|
||||
// Audit get access.
|
||||
event.AuditInfo(
|
||||
[]string{ClientIP(c), "session %s", string(acl.ResourceCluster), "get node %s", status.Succeeded},
|
||||
[]string{ClientIP(c), "session %s", string(acl.ResourceCluster), "get node", "%s", status.Succeeded},
|
||||
s.RefID,
|
||||
uuid,
|
||||
)
|
||||
@@ -262,7 +262,7 @@ func ClusterUpdateNode(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
event.AuditInfo(
|
||||
[]string{ClientIP(c), "session %s", string(acl.ResourceCluster), "node %s", status.Updated},
|
||||
[]string{ClientIP(c), "session %s", string(acl.ResourceCluster), "node", "%s", status.Updated},
|
||||
s.RefID,
|
||||
uuid,
|
||||
)
|
||||
@@ -321,7 +321,7 @@ func ClusterDeleteNode(router *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
event.AuditWarn(
|
||||
[]string{ClientIP(c), "session %s", string(acl.ResourceCluster), "node %s", status.Deleted},
|
||||
[]string{ClientIP(c), "session %s", string(acl.ResourceCluster), "node", "%s", status.Deleted},
|
||||
s.RefID,
|
||||
uuid,
|
||||
)
|
||||
|
||||
@@ -155,7 +155,7 @@ func ClusterNodesRegister(router *gin.RouterGroup) {
|
||||
// If caller attempts to change UUID by name without proving client secret, block with 409.
|
||||
if RegisterRequireClientSecret {
|
||||
if requestedUUID != "" && n.UUID != "" && requestedUUID != n.UUID && req.ClientID == "" {
|
||||
event.AuditWarn([]string{clientIp, string(acl.ResourceCluster), "node %s", "invalid client secret", status.Denied}, clean.Log(name))
|
||||
event.AuditWarn([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "invalid client secret", status.Denied}, clean.Log(name))
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "client secret required to change node uuid"})
|
||||
return
|
||||
}
|
||||
@@ -187,17 +187,17 @@ func ClusterNodesRegister(router *gin.RouterGroup) {
|
||||
if oldUUID != requestedUUID {
|
||||
n.UUID = requestedUUID
|
||||
// Emit audit event for UUID change.
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node %s", "change uuid old %s new %s", status.Updated}, clean.Log(name), clean.Log(oldUUID), clean.Log(requestedUUID))
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "change uuid old %s new %s", status.Updated}, clean.Log(name), clean.Log(oldUUID), clean.Log(requestedUUID))
|
||||
}
|
||||
} else if n.UUID == "" {
|
||||
// Assign a fresh UUID if missing and none requested.
|
||||
n.UUID = rnd.UUIDv7()
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node %s", "assign uuid %s", status.Created}, clean.Log(name), clean.Log(n.UUID))
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "assign uuid %s", status.Created}, clean.Log(name), clean.Log(n.UUID))
|
||||
}
|
||||
|
||||
// Persist metadata changes so UpdatedAt advances.
|
||||
if putErr := regy.Put(n); putErr != nil {
|
||||
event.AuditErr([]string{clientIp, string(acl.ResourceCluster), "node %s", "persist node", status.Error(putErr)}, clean.Log(name))
|
||||
event.AuditErr([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "persist node", status.Error(putErr)}, clean.Log(name))
|
||||
AbortUnexpectedError(c)
|
||||
return
|
||||
}
|
||||
@@ -206,16 +206,16 @@ func ClusterNodesRegister(router *gin.RouterGroup) {
|
||||
var respSecret *cluster.RegisterSecrets
|
||||
if req.RotateSecret {
|
||||
if n, err = regy.RotateSecret(n.UUID); err != nil {
|
||||
event.AuditErr([]string{clientIp, string(acl.ResourceCluster), "node %s", "rotate secret", status.Error(err)}, clean.Log(name))
|
||||
event.AuditErr([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "rotate secret", status.Error(err)}, clean.Log(name))
|
||||
AbortUnexpectedError(c)
|
||||
return
|
||||
}
|
||||
respSecret = &cluster.RegisterSecrets{ClientSecret: n.ClientSecret, RotatedAt: n.RotatedAt}
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node %s", "rotate secret", status.Succeeded}, clean.Log(name))
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "rotate secret", status.Succeeded}, clean.Log(name))
|
||||
|
||||
// Extra safety: ensure the updated secret is persisted even if subsequent steps fail.
|
||||
if putErr := regy.Put(n); putErr != nil {
|
||||
event.AuditErr([]string{clientIp, string(acl.ResourceCluster), "node %s", "persist rotated secret", status.Error(putErr)}, clean.Log(name))
|
||||
event.AuditErr([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "persist rotated secret", status.Error(putErr)}, clean.Log(name))
|
||||
AbortUnexpectedError(c)
|
||||
return
|
||||
}
|
||||
@@ -230,7 +230,7 @@ func ClusterNodesRegister(router *gin.RouterGroup) {
|
||||
creds, _, credsErr = provisioner.EnsureCredentials(c, conf, n.UUID, name, req.RotateDatabase)
|
||||
|
||||
if credsErr != nil {
|
||||
event.AuditWarn([]string{clientIp, string(acl.ResourceCluster), "node %s", "ensure database", status.Error(credsErr)}, clean.Log(name))
|
||||
event.AuditWarn([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "ensure database", status.Error(credsErr)}, clean.Log(name))
|
||||
c.JSON(http.StatusConflict, gin.H{"error": credsErr.Error()})
|
||||
return
|
||||
}
|
||||
@@ -240,11 +240,11 @@ func ClusterNodesRegister(router *gin.RouterGroup) {
|
||||
n.Database.RotatedAt = creds.RotatedAt
|
||||
n.Database.Driver = provisioner.DatabaseDriver
|
||||
if putErr := regy.Put(n); putErr != nil {
|
||||
event.AuditErr([]string{clientIp, string(acl.ResourceCluster), "node %s", "persist node", status.Error(putErr)}, clean.Log(name))
|
||||
event.AuditErr([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "persist node", status.Error(putErr)}, clean.Log(name))
|
||||
AbortUnexpectedError(c)
|
||||
return
|
||||
}
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node %s", "rotate database", status.Succeeded}, clean.Log(name))
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node", "%s", "rotate database", status.Succeeded}, clean.Log(name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +278,7 @@ func ClusterNodesRegister(router *gin.RouterGroup) {
|
||||
resp.Database.RotatedAt = creds.RotatedAt
|
||||
}
|
||||
c.Header(header.CacheControl, header.CacheControlNoStore)
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node %s", status.Synced}, clean.Log(name))
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node", "%s", status.Synced}, clean.Log(name))
|
||||
c.JSON(http.StatusOK, resp)
|
||||
return
|
||||
}
|
||||
@@ -360,7 +360,7 @@ func ClusterNodesRegister(router *gin.RouterGroup) {
|
||||
// When DB provisioning is skipped, leave Database fields zero-value.
|
||||
|
||||
c.Header(header.CacheControl, header.CacheControlNoStore)
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node %s", status.Joined}, clean.Log(name))
|
||||
event.AuditInfo([]string{clientIp, string(acl.ResourceCluster), "node", "%s", status.Joined}, clean.Log(name))
|
||||
c.JSON(http.StatusCreated, resp)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ func clusterNodesModAction(ctx *cli.Context) error {
|
||||
who := clusterAuditWho(ctx, conf)
|
||||
segments := []string{
|
||||
string(acl.ResourceCluster),
|
||||
"update node %s",
|
||||
"update node", "%s",
|
||||
}
|
||||
args := []interface{}{clean.Log(nodeID)}
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ func clusterNodesRemoveAction(ctx *cli.Context) error {
|
||||
who := clusterAuditWho(ctx, conf)
|
||||
event.AuditInfo(append(who,
|
||||
string(acl.ResourceCluster),
|
||||
"node %s",
|
||||
"node", "%s",
|
||||
status.Deleted,
|
||||
), clean.Log(uuid))
|
||||
|
||||
|
||||
@@ -192,7 +192,7 @@ func clusterNodesRotateAction(ctx *cli.Context) error {
|
||||
who := clusterAuditWho(ctx, conf)
|
||||
segments := []string{
|
||||
string(acl.ResourceCluster),
|
||||
"rotate node %s",
|
||||
"rotate node", "%s",
|
||||
}
|
||||
args := []interface{}{clean.Log(nodeID)}
|
||||
if detail != "" {
|
||||
|
||||
@@ -63,7 +63,7 @@ func clusterNodesShowAction(ctx *cli.Context) error {
|
||||
who := clusterAuditWho(ctx, conf)
|
||||
event.AuditInfo(append(who,
|
||||
string(acl.ResourceCluster),
|
||||
"show node %s",
|
||||
"show node", "%s",
|
||||
status.Succeeded,
|
||||
), clean.Log(dto.UUID))
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ func clusterRegisterAction(ctx *cli.Context) error {
|
||||
who := clusterAuditWho(ctx, conf)
|
||||
event.AuditInfo(append(who,
|
||||
string(acl.ResourceCluster),
|
||||
"register node %s",
|
||||
"register node", "%s",
|
||||
status.Succeeded,
|
||||
), clean.Log(nodeID))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user