diff --git a/README.md b/README.md
index b3f4433f609a6509b8db3bf372a9012776c73f5e..41c440140b7ca742642daf45fd89d1c7f599bc30 100644
--- a/README.md
+++ b/README.md
@@ -33,22 +33,16 @@ RWM can:
 * restic with S3 repository
 * configurable backup manager/executor
 
+* create, delete and list policed storage buckets
+* check if used bucket is configured with expected policies
 
-todo:
-
-* check if used bucket is configured for versioning
-* check if used access_key does not have administrator privileges to manipulate
-  with WORM policies
+TODO:
 * generate and store current bucket state state-data
 * recreate bucket contents on local filesystem (or remote bucket) acording to specified
   state data
 * ??? check completeness of the current state of the bucket
 * prune all non-recent object versions to reclaim storage space
-
-
-TBD:
 * unlike in other backup solutions, attacker with credentials can restore any old data from the repository/bucket
-* number of object files vs size
 
 
 ## Usage
@@ -61,45 +55,14 @@ make install
 ```
 
 
-### Low-level S3
-
-```
-cp examples/rwm-rclone.conf rwm.conf
-rwm aws s3 ls s3://
-rwm aws s3api list-buckets
-rwm rclone lsd rwmbe:/
-```
-
-
-### Simple copy: rclone with crypt overlay
-
-rclone_crypt defines single default remote named "rwmbe:/" pointed to `rwm_rclone_crypt_bucket` path.
-
-```
-cp examples/rwm-rclone.conf rwm.conf
-rwm rclone_crypt sync /data rwmbe:/
-rwm rclone_crypt lsl rwmbe:/
-```
-
-
-### Restic: manual restic backup
-
-```
-cp examples/rwm-restic.conf rwm.conf
-rwm restic init
-rwm restic backup /data
-rwm restic snapshots
-rwm restic mount /mnt/restore
-```
-
-
 ### RWM: simple backups
 
-backups follows standard restic procedures, but adds profile like configuration to easily run in schedulers
+Backups follows standard restic procedures, but adds profile like configuration
+to easily run in schedulers.
 
 ```
 cp examples/rwm-backups.conf rwm.conf
-rwm restic init  # should create bucket on it's own
+rwm restic init
 
 rwm backup_all
 rwm restic snapshots
@@ -109,7 +72,7 @@ rwm restic mount /mnt/restore
 
 ### RWM: backups with policed buckets
 
-Have two S3 accounts (*admin* and *user1*), create storage bucket and use it.
+Two distinct S3 accounts required (*admin*, *user1*)
 
 ```
 cp examples/rwm-admin.conf admin.conf
@@ -127,6 +90,38 @@ rwm restic mount /mnt/restore
 ```
 
 
+### Other usages
+
+#### AWS cli
+
+```
+cp examples/rwm-rclone.conf rwm.conf
+rwm aws s3 ls s3://
+rwm aws s3api list-buckets
+rwm rclone lsd rwmbe:/
+```
+
+#### rclone with crypt overlay
+
+rclone_crypt defines single default remote named "rwmbe:/" pointed to `rwm_rclone_crypt_bucket` path.
+
+```
+cp examples/rwm-rclone.conf rwm.conf
+rwm rclone_crypt sync /data rwmbe:/
+rwm rclone_crypt lsl rwmbe:/
+```
+
+#### Restic: manual restic backup
+
+```
+cp examples/rwm-restic.conf rwm.conf
+rwm restic init
+rwm restic backup /data
+rwm restic snapshots
+rwm restic mount /mnt/restore
+```
+
+
 ## Notes
 
 * executed tools stdout is buffered, eg. `restic mount` does not print immediate output as normal
diff --git a/rwm.py b/rwm.py
index e4cd4ee861ac08ab9103fc8a37cb27d27234255d..a04cd64c43d9d38ba31724be3b3e1d1ef864856e 100755
--- a/rwm.py
+++ b/rwm.py
@@ -105,6 +105,18 @@ class BackupResult:
 class StorageManager:
     """s3 policed bucket manager"""
 
+    USER_BUCKET_POLICY_ACTIONS = [
+        # backups
+        "s3:ListBucket",
+        "s3:GetObject",
+        "s3:PutObject",
+        "s3:DeleteObject",
+        # check policies
+        "s3:GetBucketPolicy",
+        "s3:ListBucketVersions",
+        "s3:GetBucketVersioning"
+    ]
+
     def __init__(self, url, access_key, secret_key):
         self.url = url
         self.access_key = access_key
@@ -131,7 +143,8 @@ class StorageManager:
         try:
             return json.loads(self.s3.Bucket(name).Policy().policy)
         except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as exc:
-            logger.error("rwm bucket_policy error, %s", (exc))
+            if "NoSuchBucketPolicy" not in str(exc):
+                logger.error("rwm bucket_policy error, %s", (exc))
             return None
 
     def list_buckets(self):
@@ -149,16 +162,16 @@ class StorageManager:
             raise ValueError("must specify value for bucket and user")
 
         bucket = self.bucket_create(bucket_name)
-        tenant, manager_username = bucket.Acl().owner["ID"].split("$")
+        tenant, admin_username = bucket.Acl().owner["ID"].split("$")
 
         # grants basic RW access to user in same tenant
         bucket_policy = {
             "Version": "2012-10-17",
             "Statement": [
-                # full access to manager
+                # full access to admin
                 {
                     "Effect": "Allow",
-                    "Principal": {"AWS": [f"arn:aws:iam::{tenant}:user/{manager_username}"]},
+                    "Principal": {"AWS": [f"arn:aws:iam::{tenant}:user/{admin_username}"]},
                     "Action": ["*"],
                     "Resource": [f"arn:aws:s3:::{bucket.name}", f"arn:aws:s3:::{bucket.name}/*"]
                 },
@@ -166,10 +179,7 @@ class StorageManager:
                 {
                     "Effect": "Allow",
                     "Principal": {"AWS": [f"arn:aws:iam::{tenant}:user/{target_username}"]},
-                    "Action": [
-                        "s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject",
-                        "s3:GetBucketPolicy", "s3:ListBucketVersions", "s3:GetBucketVersioning"
-                    ],
+                    "Action": self.USER_BUCKET_POLICY_ACTIONS,
                     "Resource": [f"arn:aws:s3:::{bucket.name}", f"arn:aws:s3:::{bucket.name}/*"]
                 }
             ]
@@ -180,7 +190,7 @@ class StorageManager:
         bucket.Versioning().enable()
 
         return bucket
-    
+
     def storage_delete(self, bucket_name):
         """storage delete"""
 
@@ -189,21 +199,62 @@ class StorageManager:
         bucket.object_versions.all().delete()
         bucket.delete()
 
+    @staticmethod
+    def _policy_statements_admin(policy):
+        """policy helper"""
+        return list(filter(lambda stmt: stmt["Action"] == ["*"], policy["Statement"]))
+
+    @staticmethod
+    def _policy_statements_user(policy):
+        """policy helper"""
+        return list(filter(lambda stmt: stmt["Action"] != ["*"], policy["Statement"]))
+
     def storage_check_policy(self, name):
         """storage check bucket policy"""
 
         if not (policy := self.bucket_policy(name)):
             return False
 
-        if (
+        admin_statements = self._policy_statements_admin(policy)
+        user_statements = self._policy_statements_user(policy)
+
+        if (  # pylint: disable=too-many-boolean-expressions
+            # only two expected statements should be present on a bucket
             len(policy["Statement"]) == 2
-            and len(list(filter(lambda stmt: stmt["Action"] == ["*"], policy["Statement"]))) == 1
+            and len(admin_statements) == 1
+            and len(user_statements) == 1
+            # with distinct identities for admin and user
+            and admin_statements[0]["Principal"] != user_statements[0]["Principal"]
+            # user should have only limited access
+            and sorted(self.USER_BUCKET_POLICY_ACTIONS) == sorted(user_statements[0]["Action"])
+            # the bucket should be versioned
             and self.s3.Bucket(name).Versioning().status == "Enabled"
         ):
             return True
-        
+
         return False
 
+    def storage_list(self):
+        """storage list"""
+
+        output = []
+        for item in self.list_buckets():
+            result = {
+                "name": item.name,
+                "policy": "OK" if self.storage_check_policy(item.name) else "FAILED",
+                "owner": self.bucket_owner(item.name).split("$")[-1]
+            }
+
+            if result["policy"] == "OK":
+                user_statement = self._policy_statements_user(self.bucket_policy(item.name))[0]
+                result["target_user"] = user_statement["Principal"]["AWS"][0].split("/")[-1]
+            else:
+                result["target_user"] = None
+
+            output.append(result)
+
+        return output
+
 
 class RWM:
     """rwm impl"""
@@ -311,8 +362,8 @@ class RWM:
     def backup_cmd(self, name) -> subprocess.CompletedProcess:
         """backup command"""
 
-        # TODO: check target backup policy, restic automatically creates 
-        # bucket if ot does not exist with null-policy
+        if not self.storage_manager.storage_check_policy(self.config["rwm_restic_bucket"]):
+            logger.warning("used bucket does not have expected policy")
 
         wrap_output(backup_proc := self._restic_backup(name))
         if backup_proc.returncode != 0:
@@ -383,11 +434,14 @@ class RWM:
         return ret
 
     def storage_list_cmd(self):
-        pass
+        """storage_list command"""
 
-    def storage_restore(self, bucket_name, target_username):
-        """https://gitlab.cesnet.cz/709/public/restic/aws/-/blob/main/bucket_copy.sh?ref_type=heads"""
-        pass
+        print(tabulate(
+            self.storage_manager.storage_list(),
+            headers="keys",
+            numalign="left"
+        ))
+        return 0
 
 
 def configure_logging(debug):
@@ -425,7 +479,7 @@ def parse_arguments(argv):
 
     backup_cmd_parser = subparsers.add_parser("backup", help="backup command")
     backup_cmd_parser.add_argument("name", help="backup config name")
-    subparsers.add_parser("backup_all", help="backup all command")
+    _ = subparsers.add_parser("backup_all", help="backup all command")
 
     storage_create_cmd_parser = subparsers.add_parser("storage_create", help="storage_create command")
     storage_create_cmd_parser.add_argument("bucket_name", help="bucket name")
@@ -434,6 +488,7 @@ def parse_arguments(argv):
     storage_delete_cmd_parser.add_argument("bucket_name", help="bucket name")
     storage_check_policy_cmd_parser = subparsers.add_parser("storage_check_policy", help="storage_check_policy command; use --debug to show policy")
     storage_check_policy_cmd_parser.add_argument("bucket_name", help="bucket name")
+    _ = subparsers.add_parser("storage_list", help="storage_list command")
 
     return parser.parse_args(argv)
 
@@ -478,6 +533,8 @@ def main(argv=None):
         ret = rwmi.storage_delete_cmd(args.bucket_name)
     if args.command == "storage_check_policy":
         ret = rwmi.storage_check_policy_cmd(args.bucket_name)
+    if args.command == "storage_list":
+        ret = rwmi.storage_list_cmd()
 
     logger.debug("rwm finished with %s (ret %d)", "success" if ret == 0 else "errors", ret)
     return ret
diff --git a/tests/test_default.py b/tests/test_default.py
index 8c3e4531c953a2ad95acc3184355abf455cb0153..f4e8e4fd7004ea73f6a93332d9d29fc88aecd0cd 100644
--- a/tests/test_default.py
+++ b/tests/test_default.py
@@ -34,16 +34,16 @@ def test_main(tmpworkdir: str):  # pylint: disable=unused-argument
     mock_proc = Mock(return_value=CompletedProcess(args='dummy', returncode=0))
     mock_ok = Mock(return_value=0)
 
-    with patch.object(rwm.RWM, f"aws_cmd", mock_proc):
+    with patch.object(rwm.RWM, "aws_cmd", mock_proc):
         assert rwm_main(["aws", "dummy"]) == 0
-    with patch.object(rwm.RWM, f"rclone_cmd", mock_proc):
+    with patch.object(rwm.RWM, "rclone_cmd", mock_proc):
         assert rwm_main(["rclone", "dummy"]) == 0
-    with patch.object(rwm.RWM, f"rclone_crypt_cmd", mock_proc):
+    with patch.object(rwm.RWM, "rclone_crypt_cmd", mock_proc):
         assert rwm_main(["rclone_crypt", "dummy"]) == 0
-    with patch.object(rwm.RWM, f"restic_cmd", mock_proc):
+    with patch.object(rwm.RWM, "restic_cmd", mock_proc):
         assert rwm_main(["restic", "dummy"]) == 0
 
-    with patch.object(rwm.RWM, f"backup_cmd", mock_proc):
+    with patch.object(rwm.RWM, "backup_cmd", mock_proc):
         assert rwm_main(["backup", "dummy"]) == 0
     with patch.object(rwm.RWM, "backup_all_cmd", mock_ok):
         assert rwm_main(["backup_all"]) == 0
@@ -54,3 +54,5 @@ def test_main(tmpworkdir: str):  # pylint: disable=unused-argument
         assert rwm_main(["storage_delete", "bucket"]) == 0
     with patch.object(rwm.RWM, "storage_check_policy_cmd", mock_ok):
         assert rwm_main(["storage_check_policy", "bucket"]) == 0
+    with patch.object(rwm.RWM, "storage_list_cmd", mock_ok):
+        assert rwm_main(["storage_list"]) == 0
diff --git a/tests/test_rwm.py b/tests/test_rwm.py
index b45cf9e3921987ae570d9e41876c5916b21d8f30..6da1bfa38b53517abba38499d7d9084c55cbdb07 100644
--- a/tests/test_rwm.py
+++ b/tests/test_rwm.py
@@ -142,7 +142,6 @@ def test_backup_cmd(tmpworkdir: str, motoserver: str):  # pylint: disable=unused
 def test_backup_cmd_excludes(tmpworkdir: str, motoserver: str):  # pylint: disable=unused-argument
     """test backup command"""
 
-    import os
     trwm = rwm.RWM({
         "rwm_s3_endpoint_url": motoserver,
         "rwm_s3_access_key": "dummy",
@@ -169,7 +168,7 @@ def test_backup_cmd_excludes(tmpworkdir: str, motoserver: str):  # pylint: disab
     Path("testdatadir/var/proc").mkdir()
     Path("testdatadir/var/proc/data").write_text("dummydata", encoding="utf-8")
 
-    assert trwm.restic_cmd(["init"]).returncode == 0 
+    assert trwm.restic_cmd(["init"]).returncode == 0
     assert trwm.backup_cmd("testcfg").returncode == 0
 
     snapshots = _restic_list_snapshots(trwm)
@@ -187,6 +186,7 @@ def test_backup_cmd_error_handling(tmpworkdir: str, motoserver: str):  # pylint:
     """test backup command err cases"""
 
     rwm_conf = {
+        "rwm_restic_bucket": "restictest",
         "rwm_backups": {
             "dummycfg": {"filesdirs": ["dummydir"]}
         }
@@ -285,3 +285,17 @@ def test_storage_check_policy_cmd(tmpworkdir: str, microceph: str, radosuser_adm
     mock = Mock(return_value=False)
     with patch.object(rwm.StorageManager, "storage_check_policy", mock):
         assert trwm.storage_check_policy_cmd("dummy") == 1
+
+
+def test_storage_list_cmd(tmpworkdir: str, microceph: str, radosuser_admin: rwm.StorageManager):  # pylint: disable=unused-argument
+    """test storage check policy command"""
+
+    trwm = rwm.RWM({
+        "rwm_s3_endpoint_url": radosuser_admin.url,
+        "rwm_s3_access_key": radosuser_admin.access_key,
+        "rwm_s3_secret_key": radosuser_admin.secret_key,
+    })
+
+    mock = Mock(return_value=[])
+    with patch.object(rwm.StorageManager, "storage_list", mock):
+        assert trwm.storage_list_cmd() == 0
diff --git a/tests/test_storage.py b/tests/test_storage.py
index 27602e45f6da4fea3826775f06577bd4c83ae4a8..7121153216f5d0431977b685f7790e09e35d742a 100644
--- a/tests/test_storage.py
+++ b/tests/test_storage.py
@@ -47,7 +47,7 @@ def test_storage_create(
     """test manager storage_create"""
 
     bucket = radosuser_admin.storage_create("testbuckx", "test1")
-    
+
     assert radosuser_admin.list_objects(bucket.name) == []
     assert radosuser_admin.storage_check_policy(bucket.name)
 
@@ -70,7 +70,6 @@ def test_storage_delete(
     target_username = "test1"
     bucket = radosuser_admin.storage_create(bucket_name, target_username)
 
-    # 
     bucket = radosuser_test1.s3.Bucket(bucket.name)
     bucket.upload_fileobj(BytesIO(b"dummydata"), "dummykey")
     assert len(radosuser_test1.list_objects(bucket.name)) == 1
@@ -100,7 +99,7 @@ def test_storage_check_policy(
 
     bucket_name = "rwmbackup-test1"
     target_username = "test1"
-    
+
     assert radosuser_admin.bucket_create(bucket_name)
     assert not radosuser_admin.storage_check_policy(bucket_name)
     radosuser_admin.storage_delete(bucket_name)
@@ -145,3 +144,18 @@ def test_storage_backup_usage(
 
     with pytest.raises(radosuser_test1.s3.meta.client.exceptions.ClientError, match=r"AccessDenied"):
         assert radosuser_test1.storage_delete(bucket_name)
+
+
+def test_storage_list(
+    tmpworkdir: str,
+    microceph: str,
+    radosuser_admin: rwm.StorageManager,
+):  # pylint: disable=unused-argument
+    """test managet list storage"""
+
+    bucket_name = "rwmbackup-test1"
+    target_username = "test1"
+
+    radosuser_admin.bucket_create("no-acl-dummy")
+    radosuser_admin.storage_create(bucket_name, target_username)
+    assert radosuser_admin.storage_list()