owncloud: extend skill from read-only audit to full occ management
Layer gated admin writes onto the read-only GPS-audit skill, driven through occ over SSH (same paramiko/vault/filecache plumbing): - user lifecycle (add/delete/enable/disable/reset-password/modify), quota get/set/reset, groups (create/delete/members), read-only share listing (from oc_share; occ has no share create/list), apps, system config, maintenance mode, files scan, ownership transfer, trashbin/versions cleanup. - All writes gated behind --confirm (refuse -> rc 3); hard-fail guards (rc 2) for invalid uid, missing password, and all-users cleanup with no target. - --all-users trashbin/versions purge now ALSO requires --force-all-users in addition to --confirm, so a single stray --confirm cannot trigger an irreversible fleet-wide purge. Built against the verified live occ surface (occ list / occ help), not guessed flags. Fixes from code-review + security-review: Python-side share filtering (no MySQL SQL-injection via '' -doubling), occ reads no longer mask a failed read as healthy (maintenance/quota/config), guarded json.loads, clean --json output, and '--' end-of-options guards so dash-leading uids can't be parsed as occ flags. dry_test.sh added as a non-destructive regression harness (43 checks). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -68,8 +68,10 @@ py "$OWNCLOUD" config-set <name> --value V [--type string|integer|double|boolean
|
|||||||
# files admin
|
# files admin
|
||||||
py "$OWNCLOUD" files-scan (--user jdoe | --all) [--path sub/dir] --confirm
|
py "$OWNCLOUD" files-scan (--user jdoe | --all) [--path sub/dir] --confirm
|
||||||
py "$OWNCLOUD" transfer-ownership <src> <dst> [--path folder] --confirm
|
py "$OWNCLOUD" transfer-ownership <src> <dst> [--path folder] --confirm
|
||||||
py "$OWNCLOUD" trashbin-cleanup (--user jdoe ... | --all-users) --confirm
|
py "$OWNCLOUD" trashbin-cleanup --user jdoe --confirm
|
||||||
py "$OWNCLOUD" versions-cleanup (--user jdoe ... | --all-users) --confirm
|
py "$OWNCLOUD" versions-cleanup --user jdoe --confirm
|
||||||
|
# fleet-wide purge needs the extra ack flag on top of --confirm:
|
||||||
|
py "$OWNCLOUD" versions-cleanup --all-users --force-all-users --confirm
|
||||||
```
|
```
|
||||||
|
|
||||||
### Passwords + the credential rule
|
### Passwords + the credential rule
|
||||||
@@ -90,6 +92,9 @@ line (suggested path `infrastructure/owncloud-users/<uid>.sops.yaml`).
|
|||||||
- `trashbin-cleanup` / `versions-cleanup` with **no** `--user` and **no**
|
- `trashbin-cleanup` / `versions-cleanup` with **no** `--user` and **no**
|
||||||
`--all-users` -> **hard-fail exit 2** (occ treats "no user" as *every* user; a
|
`--all-users` -> **hard-fail exit 2** (occ treats "no user" as *every* user; a
|
||||||
fleet-wide purge must be made explicit with `--all-users`).
|
fleet-wide purge must be made explicit with `--all-users`).
|
||||||
|
- `--all-users` on those two also **hard-fails exit 2** unless
|
||||||
|
`--force-all-users` is passed **in addition to** `--confirm` - a single stray
|
||||||
|
`--confirm` must never be enough to irreversibly purge every user's history.
|
||||||
- invalid uid on `user-add` -> **exit 2**.
|
- invalid uid on `user-add` -> **exit 2**.
|
||||||
|
|
||||||
## Access channel
|
## Access channel
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ chk 2 "user-modify no fields" -- py "$OC" user-modify zz.dry --confirm
|
|||||||
chk 2 "files-scan no target" -- py "$OC" files-scan --confirm
|
chk 2 "files-scan no target" -- py "$OC" files-scan --confirm
|
||||||
chk 2 "trashbin-cleanup no target"-- py "$OC" trashbin-cleanup --confirm
|
chk 2 "trashbin-cleanup no target"-- py "$OC" trashbin-cleanup --confirm
|
||||||
chk 2 "versions-cleanup no target"-- py "$OC" versions-cleanup --confirm
|
chk 2 "versions-cleanup no target"-- py "$OC" versions-cleanup --confirm
|
||||||
|
# --all-users must NOT execute on --confirm alone (needs --force-all-users) -> rc 2
|
||||||
|
chk 2 "versions --all-users no force" -- py "$OC" versions-cleanup --all-users --confirm
|
||||||
|
chk 2 "trashbin --all-users no force" -- py "$OC" trashbin-cleanup --all-users --confirm
|
||||||
|
|
||||||
echo "== read-only commands must succeed (rc 0) =="
|
echo "== read-only commands must succeed (rc 0) =="
|
||||||
chk 0 "status" -- py "$OC" status
|
chk 0 "status" -- py "$OC" status
|
||||||
|
|||||||
@@ -490,6 +490,13 @@ def _cleanup(c: OwncloudClient, args, kind: str) -> int:
|
|||||||
"pass explicit --user, or --all-users to sweep everyone.",
|
"pass explicit --user, or --all-users to sweep everyone.",
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
return 2
|
return 2
|
||||||
|
# A fleet-wide, irreversible purge must not be reachable by a single stray
|
||||||
|
# --confirm: require a dedicated acknowledgement flag on top of it.
|
||||||
|
if args.all_users and not args.force_all_users:
|
||||||
|
print(f"[ERROR] {kind}-cleanup --all-users irreversibly purges EVERY "
|
||||||
|
f"user's {kind} history. This needs --force-all-users IN ADDITION "
|
||||||
|
"to --confirm to proceed.", file=sys.stderr)
|
||||||
|
return 2
|
||||||
target = "ALL users" if args.all_users else ", ".join(users)
|
target = "ALL users" if args.all_users else ", ".join(users)
|
||||||
if not _gated(f"permanently delete {kind} for {target}", args.confirm):
|
if not _gated(f"permanently delete {kind} for {target}", args.confirm):
|
||||||
return 3
|
return 3
|
||||||
@@ -570,8 +577,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
# --- files admin ---
|
# --- files admin ---
|
||||||
fs = sub.add_parser("files-scan"); fs.add_argument("--user"); fs.add_argument("--all", action="store_true"); fs.add_argument("--path"); fs.add_argument("--confirm", action="store_true"); fs.add_argument("--json", action="store_true")
|
fs = sub.add_parser("files-scan"); fs.add_argument("--user"); fs.add_argument("--all", action="store_true"); fs.add_argument("--path"); fs.add_argument("--confirm", action="store_true"); fs.add_argument("--json", action="store_true")
|
||||||
to = sub.add_parser("transfer-ownership"); to.add_argument("source"); to.add_argument("dest"); to.add_argument("--path"); to.add_argument("--confirm", action="store_true"); to.add_argument("--json", action="store_true")
|
to = sub.add_parser("transfer-ownership"); to.add_argument("source"); to.add_argument("dest"); to.add_argument("--path"); to.add_argument("--confirm", action="store_true"); to.add_argument("--json", action="store_true")
|
||||||
tc = sub.add_parser("trashbin-cleanup"); tc.add_argument("--user", action="append"); tc.add_argument("--all-users", action="store_true"); tc.add_argument("--confirm", action="store_true"); tc.add_argument("--json", action="store_true")
|
tc = sub.add_parser("trashbin-cleanup"); tc.add_argument("--user", action="append"); tc.add_argument("--all-users", action="store_true"); tc.add_argument("--force-all-users", action="store_true", help="required with --all-users + --confirm to purge everyone"); tc.add_argument("--confirm", action="store_true"); tc.add_argument("--json", action="store_true")
|
||||||
vc = sub.add_parser("versions-cleanup"); vc.add_argument("--user", action="append"); vc.add_argument("--all-users", action="store_true"); vc.add_argument("--confirm", action="store_true"); vc.add_argument("--json", action="store_true")
|
vc = sub.add_parser("versions-cleanup"); vc.add_argument("--user", action="append"); vc.add_argument("--all-users", action="store_true"); vc.add_argument("--force-all-users", action="store_true", help="required with --all-users + --confirm to purge everyone"); vc.add_argument("--confirm", action="store_true"); vc.add_argument("--json", action="store_true")
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user