mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
CLI: Add audit logs to cluster management commands
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -8,7 +8,11 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
"github.com/photoprism/photoprism/internal/service/cluster"
|
"github.com/photoprism/photoprism/internal/service/cluster"
|
||||||
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/http/header"
|
"github.com/photoprism/photoprism/pkg/http/header"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,3 +57,23 @@ func obtainClientCredentialsViaRegister(portalURL, joinToken, nodeName string) (
|
|||||||
}
|
}
|
||||||
return id, secret, nil
|
return id, secret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clusterAuditWho builds the leading audit log segments for CLI commands.
|
||||||
|
func clusterAuditWho(ctx *cli.Context, conf *config.Config) []string {
|
||||||
|
actor := clean.Log(conf.NodeName())
|
||||||
|
if actor == "" {
|
||||||
|
actor = clean.Log(conf.SiteUrl())
|
||||||
|
}
|
||||||
|
if actor == "" {
|
||||||
|
actor = "cli"
|
||||||
|
}
|
||||||
|
|
||||||
|
context := "cli"
|
||||||
|
if ctx != nil && ctx.Command != nil {
|
||||||
|
if full := strings.TrimSpace(ctx.Command.FullName()); full != "" {
|
||||||
|
context = "cli " + full
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{actor, context}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
||||||
"github.com/photoprism/photoprism/pkg/txt/report"
|
"github.com/photoprism/photoprism/pkg/txt/report"
|
||||||
)
|
)
|
||||||
@@ -74,6 +76,13 @@ func clusterNodesListAction(ctx *cli.Context) error {
|
|||||||
opts := reg.NodeOpts{IncludeAdvertiseUrl: true, IncludeDatabase: true}
|
opts := reg.NodeOpts{IncludeAdvertiseUrl: true, IncludeDatabase: true}
|
||||||
out := reg.BuildClusterNodes(page, opts)
|
out := reg.BuildClusterNodes(page, opts)
|
||||||
|
|
||||||
|
who := clusterAuditWho(ctx, conf)
|
||||||
|
event.AuditInfo(append(who,
|
||||||
|
string(acl.ResourceCluster),
|
||||||
|
"list nodes count %d",
|
||||||
|
event.Succeeded,
|
||||||
|
), len(out))
|
||||||
|
|
||||||
if ctx.Bool("json") {
|
if ctx.Bool("json") {
|
||||||
b, _ := json.Marshal(out)
|
b, _ := json.Marshal(out)
|
||||||
fmt.Println(string(b))
|
fmt.Println(string(b))
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ import (
|
|||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
)
|
)
|
||||||
@@ -112,6 +114,29 @@ func clusterNodesModAction(ctx *cli.Context) error {
|
|||||||
return cli.Exit(err, 1)
|
return cli.Exit(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodeID := n.UUID
|
||||||
|
if nodeID == "" {
|
||||||
|
nodeID = n.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSummary := strings.Join(changes, ", ")
|
||||||
|
|
||||||
|
who := clusterAuditWho(ctx, conf)
|
||||||
|
segments := []string{
|
||||||
|
string(acl.ResourceCluster),
|
||||||
|
"update node %s",
|
||||||
|
}
|
||||||
|
args := []interface{}{clean.Log(nodeID)}
|
||||||
|
|
||||||
|
if changeSummary != "" {
|
||||||
|
segments = append(segments, "%s")
|
||||||
|
args = append(args, clean.Log(changeSummary))
|
||||||
|
}
|
||||||
|
|
||||||
|
segments = append(segments, event.Updated)
|
||||||
|
|
||||||
|
event.AuditInfo(append(who, segments...), args...)
|
||||||
|
|
||||||
log.Infof("node %s has been updated", clean.LogQuote(n.Name))
|
log.Infof("node %s has been updated", clean.LogQuote(n.Name))
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import (
|
|||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/service/cluster/provisioner"
|
"github.com/photoprism/photoprism/internal/service/cluster/provisioner"
|
||||||
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
@@ -109,6 +111,13 @@ func clusterNodesRemoveAction(ctx *cli.Context) error {
|
|||||||
return cli.Exit(err, 1)
|
return cli.Exit(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
who := clusterAuditWho(ctx, conf)
|
||||||
|
event.AuditInfo(append(who,
|
||||||
|
string(acl.ResourceCluster),
|
||||||
|
"node %s",
|
||||||
|
event.Deleted,
|
||||||
|
), clean.Log(uuid))
|
||||||
|
|
||||||
loggedDeletion := false
|
loggedDeletion := false
|
||||||
|
|
||||||
if dropDB {
|
if dropDB {
|
||||||
@@ -122,6 +131,11 @@ func clusterNodesRemoveAction(ctx *cli.Context) error {
|
|||||||
return cli.Exit(fmt.Errorf("failed to drop database credentials for node %s: %w", clean.Log(uuid), err), 1)
|
return cli.Exit(fmt.Errorf("failed to drop database credentials for node %s: %w", clean.Log(uuid), err), 1)
|
||||||
}
|
}
|
||||||
log.Infof("node %s database %s and user %s have been dropped", clean.Log(uuid), clean.Log(dbName), clean.Log(dbUser))
|
log.Infof("node %s database %s and user %s have been dropped", clean.Log(uuid), clean.Log(dbName), clean.Log(dbUser))
|
||||||
|
event.AuditInfo(append(who,
|
||||||
|
string(acl.ResourceCluster),
|
||||||
|
"drop database %s user %s",
|
||||||
|
event.Succeeded,
|
||||||
|
), clean.Log(dbName), clean.Log(dbUser))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/service/cluster"
|
"github.com/photoprism/photoprism/internal/service/cluster"
|
||||||
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
||||||
"github.com/photoprism/photoprism/internal/service/cluster/theme"
|
"github.com/photoprism/photoprism/internal/service/cluster/theme"
|
||||||
@@ -170,6 +173,35 @@ func clusterNodesRotateAction(ctx *cli.Context) error {
|
|||||||
return cli.Exit(err, 1)
|
return cli.Exit(err, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodeID := resp.Node.UUID
|
||||||
|
if nodeID == "" {
|
||||||
|
nodeID = resp.Node.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
rotatedParts := make([]string, 0, 2)
|
||||||
|
if rotateDatabase {
|
||||||
|
rotatedParts = append(rotatedParts, "database")
|
||||||
|
}
|
||||||
|
if rotateSecret {
|
||||||
|
rotatedParts = append(rotatedParts, "secret")
|
||||||
|
}
|
||||||
|
|
||||||
|
detail := strings.Join(rotatedParts, ", ")
|
||||||
|
|
||||||
|
who := clusterAuditWho(ctx, conf)
|
||||||
|
segments := []string{
|
||||||
|
string(acl.ResourceCluster),
|
||||||
|
"rotate node %s",
|
||||||
|
}
|
||||||
|
args := []interface{}{clean.Log(nodeID)}
|
||||||
|
if detail != "" {
|
||||||
|
segments = append(segments, "%s")
|
||||||
|
args = append(args, clean.Log(detail))
|
||||||
|
}
|
||||||
|
segments = append(segments, event.Succeeded)
|
||||||
|
|
||||||
|
event.AuditInfo(append(who, segments...), args...)
|
||||||
|
|
||||||
if ctx.Bool("json") {
|
if ctx.Bool("json") {
|
||||||
jb, _ := json.Marshal(resp)
|
jb, _ := json.Marshal(resp)
|
||||||
fmt.Println(string(jb))
|
fmt.Println(string(jb))
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import (
|
|||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
reg "github.com/photoprism/photoprism/internal/service/cluster/registry"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/txt/report"
|
"github.com/photoprism/photoprism/pkg/txt/report"
|
||||||
@@ -57,6 +59,13 @@ func clusterNodesShowAction(ctx *cli.Context) error {
|
|||||||
opts := reg.NodeOpts{IncludeAdvertiseUrl: true, IncludeDatabase: true}
|
opts := reg.NodeOpts{IncludeAdvertiseUrl: true, IncludeDatabase: true}
|
||||||
dto := reg.BuildClusterNode(*n, opts)
|
dto := reg.BuildClusterNode(*n, opts)
|
||||||
|
|
||||||
|
who := clusterAuditWho(ctx, conf)
|
||||||
|
event.AuditInfo(append(who,
|
||||||
|
string(acl.ResourceCluster),
|
||||||
|
"show node %s",
|
||||||
|
event.Succeeded,
|
||||||
|
), clean.Log(dto.UUID))
|
||||||
|
|
||||||
if ctx.Bool("json") {
|
if ctx.Bool("json") {
|
||||||
b, _ := json.Marshal(dto)
|
b, _ := json.Marshal(dto)
|
||||||
fmt.Println(string(b))
|
fmt.Println(string(b))
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ import (
|
|||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
"github.com/photoprism/photoprism/internal/service/cluster"
|
"github.com/photoprism/photoprism/internal/service/cluster"
|
||||||
clusternode "github.com/photoprism/photoprism/internal/service/cluster/node"
|
clusternode "github.com/photoprism/photoprism/internal/service/cluster/node"
|
||||||
"github.com/photoprism/photoprism/internal/service/cluster/theme"
|
"github.com/photoprism/photoprism/internal/service/cluster/theme"
|
||||||
@@ -293,6 +295,18 @@ func clusterRegisterAction(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodeID := resp.Node.UUID
|
||||||
|
if nodeID == "" {
|
||||||
|
nodeID = resp.Node.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
who := clusterAuditWho(ctx, conf)
|
||||||
|
event.AuditInfo(append(who,
|
||||||
|
string(acl.ResourceCluster),
|
||||||
|
"register node %s",
|
||||||
|
event.Succeeded,
|
||||||
|
), clean.Log(nodeID))
|
||||||
|
|
||||||
// Optional persistence
|
// Optional persistence
|
||||||
if ctx.Bool("write-config") {
|
if ctx.Bool("write-config") {
|
||||||
if err := persistRegisterResponse(conf, &resp); err != nil {
|
if err := persistRegisterResponse(conf, &resp); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user