From d8abe507d45b56f0efdcc5762a6d22f86c87e181 Mon Sep 17 00:00:00 2001 From: Paul Allen Date: Thu, 20 Aug 2020 13:00:17 -0700 Subject: [PATCH 01/34] =?UTF-8?q?PLA-17940=20updating=20constraints=20and?= =?UTF-8?q?=20fixing=20s3=5Fenable=5Faccess=5Flogging=20to=E2=80=A6=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * PLA-17940 updating constraints and fixing s3_enable_access_logging to not remediate the logging target bucket * PLA-17940 fixing copyright info --- .gitignore | 2 + .../jobs/ec2_close_port_22/constraints.txt | 30 +++++-- .../ec2_close_port_22/requirements-dev.txt | 6 ++ .../jobs/ec2_close_port_22/requirements.txt | 8 +- .../jobs/ec2_close_port_3389/constraints.txt | 30 +++++-- .../ec2_close_port_3389/requirements-dev.txt | 6 ++ .../jobs/ec2_close_port_3389/requirements.txt | 8 +- .../constraints.txt | 30 +++++-- .../requirements-dev.txt | 6 ++ .../requirements.txt | 8 +- .../s3_enable_access_logging/constraints.txt | 30 +++++-- .../requirements-dev.txt | 6 ++ .../s3_enable_access_logging/requirements.txt | 8 +- .../s3_enable_access_logging.py | 10 +++ .../constraints.txt | 30 +++++-- .../requirements-dev.txt | 6 ++ .../requirements.txt | 8 +- .../jobs/s3_list_buckets/constraints.txt | 30 +++++-- .../jobs/s3_list_buckets/requirements-dev.txt | 6 ++ .../jobs/s3_list_buckets/requirements.txt | 8 +- .../s3_remove_public_access/constraints.txt | 30 +++++-- .../requirements-dev.txt | 6 ++ .../s3_remove_public_access/requirements.txt | 8 +- .../constraints.txt | 30 +++++-- .../requirements-dev.txt | 6 ++ .../requirements.txt | 8 +- .../constraints.txt | 30 +++++-- .../requirements-dev.txt | 6 ++ .../requirements.txt | 8 +- .../constraints.txt | 30 +++++-- .../requirements-dev.txt | 6 ++ .../requirements.txt | 8 +- .../constraints.txt | 30 +++++-- .../requirements-dev.txt | 6 ++ .../requirements.txt | 8 +- setup.py | 2 +- shared/worker_logging/setup.py | 21 +++++ test/unit/test_s3_enable_access_logging.py | 78 +++++++++++++++++++ tox.ini | 72 +++++++++++++++-- 39 files changed, 574 insertions(+), 95 deletions(-) create mode 100644 remediation_worker/jobs/ec2_close_port_22/requirements-dev.txt create mode 100644 remediation_worker/jobs/ec2_close_port_3389/requirements-dev.txt create mode 100644 remediation_worker/jobs/rds_backup_retention_30_days/requirements-dev.txt create mode 100644 remediation_worker/jobs/s3_enable_access_logging/requirements-dev.txt create mode 100644 remediation_worker/jobs/s3_enable_default_encryption/requirements-dev.txt create mode 100644 remediation_worker/jobs/s3_list_buckets/requirements-dev.txt create mode 100644 remediation_worker/jobs/s3_remove_public_access/requirements-dev.txt create mode 100644 remediation_worker/jobs/s3_remove_public_admin_acl/requirements-dev.txt create mode 100644 remediation_worker/jobs/security_group_close_port_22/requirements-dev.txt create mode 100644 remediation_worker/jobs/security_group_close_port_3389/requirements-dev.txt create mode 100644 remediation_worker/jobs/security_group_close_port_5432/requirements-dev.txt create mode 100644 shared/worker_logging/setup.py diff --git a/.gitignore b/.gitignore index 63db5e0..444053d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ pip-selfcheck.json venv test/unit/__pycache__ remediation_worker.egg-info +.tox +__pycache__ diff --git a/remediation_worker/jobs/ec2_close_port_22/constraints.txt b/remediation_worker/jobs/ec2_close_port_22/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/ec2_close_port_22/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_22/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/ec2_close_port_22/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_22/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_22/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/ec2_close_port_22/requirements.txt b/remediation_worker/jobs/ec2_close_port_22/requirements.txt index f938cb2..b239388 100644 --- a/remediation_worker/jobs/ec2_close_port_22/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_22/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/ec2_close_port_3389/constraints.txt b/remediation_worker/jobs/ec2_close_port_3389/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/constraints.txt +++ b/remediation_worker/jobs/ec2_close_port_3389/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/ec2_close_port_3389/requirements-dev.txt b/remediation_worker/jobs/ec2_close_port_3389/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/ec2_close_port_3389/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/ec2_close_port_3389/requirements.txt b/remediation_worker/jobs/ec2_close_port_3389/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_3389/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/rds_backup_retention_30_days/constraints.txt b/remediation_worker/jobs/rds_backup_retention_30_days/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/rds_backup_retention_30_days/constraints.txt +++ b/remediation_worker/jobs/rds_backup_retention_30_days/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/rds_backup_retention_30_days/requirements-dev.txt b/remediation_worker/jobs/rds_backup_retention_30_days/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/rds_backup_retention_30_days/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/rds_backup_retention_30_days/requirements.txt b/remediation_worker/jobs/rds_backup_retention_30_days/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/rds_backup_retention_30_days/requirements.txt +++ b/remediation_worker/jobs/rds_backup_retention_30_days/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_enable_access_logging/constraints.txt b/remediation_worker/jobs/s3_enable_access_logging/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/constraints.txt +++ b/remediation_worker/jobs/s3_enable_access_logging/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/s3_enable_access_logging/requirements-dev.txt b/remediation_worker/jobs/s3_enable_access_logging/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/s3_enable_access_logging/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/s3_enable_access_logging/requirements.txt b/remediation_worker/jobs/s3_enable_access_logging/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/requirements.txt +++ b/remediation_worker/jobs/s3_enable_access_logging/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py index 077f862..978d5d1 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py +++ b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py @@ -25,6 +25,10 @@ logging.basicConfig(level=logging.INFO) +class SelfRemediationError(ValueError): + pass + + class S3EnableAccessLogging(object): def parse(self, payload): """Parse payload received from Remediation Service. @@ -131,6 +135,12 @@ def remediate(self, region, client, source_bucket, target_bucket, target_prefix) :rtype: int :raises: botocore.exceptions.ClientError """ + if source_bucket == target_bucket: + raise SelfRemediationError( + f"Cannot remediate the logging bucket (i.e. write access logs to self). " + f"Consider suppressing the violation for this bucket ({source_bucket})." + ) + self.ensure_log_target_bucket(client, target_bucket, region) logging.info("ensuring logs can be delivered") self.grant_log_delivery_permissions(client, target_bucket) diff --git a/remediation_worker/jobs/s3_enable_default_encryption/constraints.txt b/remediation_worker/jobs/s3_enable_default_encryption/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/s3_enable_default_encryption/constraints.txt +++ b/remediation_worker/jobs/s3_enable_default_encryption/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/s3_enable_default_encryption/requirements-dev.txt b/remediation_worker/jobs/s3_enable_default_encryption/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/s3_enable_default_encryption/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt b/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt +++ b/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_list_buckets/constraints.txt b/remediation_worker/jobs/s3_list_buckets/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/s3_list_buckets/constraints.txt +++ b/remediation_worker/jobs/s3_list_buckets/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/s3_list_buckets/requirements-dev.txt b/remediation_worker/jobs/s3_list_buckets/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/s3_list_buckets/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/s3_list_buckets/requirements.txt b/remediation_worker/jobs/s3_list_buckets/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/s3_list_buckets/requirements.txt +++ b/remediation_worker/jobs/s3_list_buckets/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_remove_public_access/constraints.txt b/remediation_worker/jobs/s3_remove_public_access/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/s3_remove_public_access/constraints.txt +++ b/remediation_worker/jobs/s3_remove_public_access/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/s3_remove_public_access/requirements-dev.txt b/remediation_worker/jobs/s3_remove_public_access/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/s3_remove_public_access/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/s3_remove_public_access/requirements.txt b/remediation_worker/jobs/s3_remove_public_access/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/s3_remove_public_access/requirements.txt +++ b/remediation_worker/jobs/s3_remove_public_access/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_remove_public_admin_acl/constraints.txt b/remediation_worker/jobs/s3_remove_public_admin_acl/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/s3_remove_public_admin_acl/constraints.txt +++ b/remediation_worker/jobs/s3_remove_public_admin_acl/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/s3_remove_public_admin_acl/requirements-dev.txt b/remediation_worker/jobs/s3_remove_public_admin_acl/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/s3_remove_public_admin_acl/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/s3_remove_public_admin_acl/requirements.txt b/remediation_worker/jobs/s3_remove_public_admin_acl/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/s3_remove_public_admin_acl/requirements.txt +++ b/remediation_worker/jobs/s3_remove_public_admin_acl/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/security_group_close_port_22/constraints.txt b/remediation_worker/jobs/security_group_close_port_22/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/security_group_close_port_22/constraints.txt +++ b/remediation_worker/jobs/security_group_close_port_22/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/security_group_close_port_22/requirements-dev.txt b/remediation_worker/jobs/security_group_close_port_22/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/security_group_close_port_22/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/security_group_close_port_22/requirements.txt b/remediation_worker/jobs/security_group_close_port_22/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/security_group_close_port_22/requirements.txt +++ b/remediation_worker/jobs/security_group_close_port_22/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/security_group_close_port_3389/constraints.txt b/remediation_worker/jobs/security_group_close_port_3389/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/constraints.txt +++ b/remediation_worker/jobs/security_group_close_port_3389/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/security_group_close_port_3389/requirements-dev.txt b/remediation_worker/jobs/security_group_close_port_3389/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/security_group_close_port_3389/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/security_group_close_port_3389/requirements.txt b/remediation_worker/jobs/security_group_close_port_3389/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/requirements.txt +++ b/remediation_worker/jobs/security_group_close_port_3389/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/security_group_close_port_5432/constraints.txt b/remediation_worker/jobs/security_group_close_port_5432/constraints.txt index 61279bb..6b211d2 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/constraints.txt +++ b/remediation_worker/jobs/security_group_close_port_5432/constraints.txt @@ -1,9 +1,3 @@ -boto3==1.14.9 \ - --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ - --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 -botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 docutils==0.15.2 \ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ @@ -23,3 +17,27 @@ urllib3==1.25.9 \ six==1.15.0 \ --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/security_group_close_port_5432/requirements-dev.txt b/remediation_worker/jobs/security_group_close_port_5432/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/security_group_close_port_5432/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/security_group_close_port_5432/requirements.txt b/remediation_worker/jobs/security_group_close_port_5432/requirements.txt index f938cb2..7ca0574 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/requirements.txt +++ b/remediation_worker/jobs/security_group_close_port_5432/requirements.txt @@ -1,2 +1,6 @@ -boto3==1.14.9 -botocore==1.17.9 +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/setup.py b/setup.py index 52a2e05..d71e655 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 VMware Corp +# Copyright (c) 2020 VMware Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/shared/worker_logging/setup.py b/shared/worker_logging/setup.py new file mode 100644 index 0000000..8b97fd2 --- /dev/null +++ b/shared/worker_logging/setup.py @@ -0,0 +1,21 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from setuptools import setup +from setuptools import find_packages + + +setup( + name="worker_logging", version="0.0.1", packages=find_packages(), +) diff --git a/test/unit/test_s3_enable_access_logging.py b/test/unit/test_s3_enable_access_logging.py index 63a73c9..5c0d8ca 100644 --- a/test/unit/test_s3_enable_access_logging.py +++ b/test/unit/test_s3_enable_access_logging.py @@ -19,6 +19,7 @@ from remediation_worker.jobs.s3_enable_access_logging.s3_enable_access_logging import ( S3EnableAccessLogging, + SelfRemediationError, ) @@ -112,6 +113,79 @@ def full_payload(): ) +@pytest.fixture +def self_payload(): + return json.dumps( + { + "cloudAccount": { + "provider": "", + "roleArn": "arn:aws:iam::530342348278:role/SecureStateRemediation", + }, + "notificationInfo": { + "RuleId": "5c6cc5cc03dcc90f3631468d", + "RuleName": "", + "RuleDisplayName": "", + "Level": "Low", + "Service": "s3", + "FindingInfo": { + "FindingId": "05eedc79-65b5-4774-8a6a-cfffb17a3a99", + "ObjectId": "vss-logging-target-530342348278-us-east-1", + "ObjectChain": "{" + ' "cloudAccountId": "530342348278",' + ' "creationTime": "2020-06-23T21:40:33.000Z",' + ' "depthCount": {' + ' "depth_0": 1,' + ' "depth_1": 1' + " }," + ' "entityId": "AWS.S3.530342348278.us-east-1.Bucket.rule-executor-s3-test-892fbb42-45ee-489b-bcc9-e9a4dc285ea0",' # noqa: E501 + ' "entityName": "rule-executor-s3-test-892fbb42-45ee-489b-bcc9-e9a4dc285ea0",' + ' "entityType": "AWS.S3.Bucket",' + ' "lastUpdateTime": "2020-06-23T21:40:33.000Z",' + ' "partitionKey": "530342348278",' + ' "properties": [{' + ' "name": "BucketName",' + ' "stringV": "rule-executor-s3-test-892fbb42-45ee-489b-bcc9-e9a4dc285ea0",' + ' "type": "string"' + " }, {" + ' "name": "VersioningStatus",' + ' "stringV": "Enabled",' + ' "type": "string"' + " }, {" + ' "name": "CreationDate",' + ' "stringV": "2020-06-22T20:48:49.000Z",' + ' "type": "datetime"' + " }, {" + ' "boolV": false,' + ' "name": "ReplicationEnabled",' + ' "type": "bool"' + " }, {" + ' "name": "VersioningMFADelete",' + ' "stringV": "Disabled",' + ' "type": "string"' + " }, {" + ' "name": "Location",' + ' "stringV": "us-east-1",' + ' "type": "string"' + " }, {" + ' "boolV": false,' + ' "name": "LoggingEnabled",' + ' "type": "bool"' + " }]," + ' "provider": "AWS",' + ' "region": "us-east-1",' + ' "service": "S3"' + "}", + "CloudTags": None, + "RiskScore": 10, + "Region": "us-east-1", + "Service": "s3", + }, + }, + "autoRemediate": False, + } + ) + + class TestS3EnableAccessLogging(object): def test_parse_payload_success(self, full_payload): obj = S3EnableAccessLogging() @@ -172,3 +246,7 @@ def put_bucket_logging(self, **kwargs): assert action.remediate( "region", client, "source_bucket", "target_bucket", "target_prefix" ) + + def test_dont_log_to_self(self, self_payload): + with pytest.raises(SelfRemediationError): + assert S3EnableAccessLogging().run([None, self_payload]) diff --git a/tox.ini b/tox.ini index 13d5773..bc2dd70 100644 --- a/tox.ini +++ b/tox.ini @@ -2,9 +2,16 @@ minversion = 3.6.0 skip_missing_interpreters = true envlist = - unit -toxworkdir = {env:TOX_WORK_DIR:{homedir}/envs/} - + unit-security-group-close-port-5432 + unit-s3-remove-public-admin-acl + unit-s3-enable-access-logging + unit-ec2-close-port-3389 + unit-s3-enable-default-encryption + unit-ec2-close-port-22 + unit-s3-list-buckets + unit-security-group-close-port-3389 + unit-rds-backup-retention-30-days + unit-security-group-close-port-22 [testenv] passenv = @@ -23,10 +30,63 @@ passenv = # Used to make tox (and python) work correctly on macOS OBJC_DISABLE_INITIALIZE_FORK_SAFETY -deps = -r requirements.txt +[testenv:unit-security-group-close-port-5432] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_security_group_close_port_5432.py +deps = -r remediation_worker/jobs/security_group_close_port_5432/requirements-dev.txt + +[testenv:unit-s3-remove-public-admin-acl] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_s3_remove_public_admin_acl.py +deps = -r remediation_worker/jobs/s3_remove_public_admin_acl/requirements-dev.txt + +[testenv:unit-s3-enable-access-logging] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_s3_enable_access_logging.py +deps = -r remediation_worker/jobs/s3_enable_access_logging/requirements-dev.txt + +[testenv:unit-ec2-close-port-3389] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_3389.py +deps = -r remediation_worker/jobs/ec2_close_port_3389/requirements-dev.txt + +[testenv:unit-s3-enable-default-encryption] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_s3_enable_default_encryption.py +deps = -r remediation_worker/jobs/s3_enable_default_encryption/requirements-dev.txt + +[testenv:unit-ec2-close-port-22] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_ec2_close_port_22.py +deps = -r remediation_worker/jobs/ec2_close_port_22/requirements-dev.txt + +[testenv:unit-s3-list-buckets] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_s3_list_buckets.py +deps = -r remediation_worker/jobs/s3_list_buckets/requirements-dev.txt + +[testenv:unit-security-group-close-port-3389] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_security_group_close_port_3389.py +deps = -r remediation_worker/jobs/security_group_close_port_3389/requirements-dev.txt + +[testenv:unit-rds-backup-retention-30-days] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_rds_backup_retention_30_days.py +deps = -r remediation_worker/jobs/rds_backup_retention_30_days/requirements-dev.txt -[testenv:unit] +[testenv:unit-security-group-close-port-22] description = Unit test the project changedir = test -commands = pytest --capture=no --basetemp="{envtmpdir}" {posargs} +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_security_group_close_port_22.py +deps = -r remediation_worker/jobs/security_group_close_port_22/requirements-dev.txt From e0d5d3a6d11fb250bfb90e6617d78a236b05e72c Mon Sep 17 00:00:00 2001 From: Vikramjeet Singh <58273802+vikramsinghvirdi@users.noreply.github.com> Date: Tue, 25 Aug 2020 13:09:16 -0700 Subject: [PATCH 02/34] Updated readme to have link for reporting issues (#4) Co-authored-by: svikramjeet --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index dad67ce..9e09942 100644 --- a/README.md +++ b/README.md @@ -94,5 +94,8 @@ Members: * [VMware Code](https://code.vmware.com/home) * [VMware Developer Community](https://communities.vmware.com/community/vmtn/developer) +## Feedback +If you find a bug, please open a [GitHub issue](https://github.com/vmware-samples/secure-state-remediation-jobs/issues). + [license-img]: https://img.shields.io/badge/License-Apache%202.0-blue.svg [license]: https://opensource.org/licenses/Apache-2.0 From afd8f070721272425d8a5eade0e922a29f252029 Mon Sep 17 00:00:00 2001 From: Zuber Date: Fri, 28 Aug 2020 13:39:56 -0700 Subject: [PATCH 03/34] S3 access logs permissions (#6) * Update minimum permissions for the job * Add more logs when permission is missing Co-authored-by: Mohammad Zuber Khan --- .../jobs/s3_enable_access_logging/minimum_policy.json | 3 ++- .../s3_enable_access_logging/s3_enable_access_logging.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/remediation_worker/jobs/s3_enable_access_logging/minimum_policy.json b/remediation_worker/jobs/s3_enable_access_logging/minimum_policy.json index 6abf920..7f49d4c 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/minimum_policy.json +++ b/remediation_worker/jobs/s3_enable_access_logging/minimum_policy.json @@ -8,7 +8,8 @@ "s3:PutBucketLogging", "s3:PutBucketAcl", "s3:GetBucketAcl", - "s3:CreateBucket" + "s3:CreateBucket", + "s3:ListBucket" ], "Resource": "*" } diff --git a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py index 978d5d1..fcd360b 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py +++ b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py @@ -101,7 +101,6 @@ def grant_log_delivery_permissions(self, client, bucket_name): def ensure_log_target_bucket(self, client, target_bucket, region): try: client.head_bucket(Bucket=target_bucket) - return None except ClientError as e: if e.response["Error"]["Code"] == "404": # The bucket does not exist @@ -112,6 +111,10 @@ def ensure_log_target_bucket(self, client, target_bucket, region): Bucket=target_bucket, CreateBucketConfiguration={"LocationConstraint": region}, ) + elif e.response["Error"]["Code"] == "403": + # The assumed role does not have the permission + logging.error("Not enough permissions to list buckets") + raise e else: raise e From dfbd7ce1c75864eeff17bbe2e45b4eb6b5c91de1 Mon Sep 17 00:00:00 2001 From: Zuber Date: Tue, 1 Sep 2020 15:58:17 -0700 Subject: [PATCH 04/34] change the way cloudAccountId is parsed from Job Paramaters (#9) Co-authored-by: Mohammad Zuber Khan --- .../s3_enable_access_logging/s3_enable_access_logging.py | 8 +++++--- test/unit/test_s3_enable_access_logging.py | 8 -------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py index fcd360b..875fbaa 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py +++ b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py @@ -44,9 +44,11 @@ def parse(self, payload): finding_info = notification_info.get("FindingInfo", None) source_bucket = finding_info.get("ObjectId", None) - cloud_account = remediation_entry.get("cloudAccount") - role_arn = cloud_account.get("roleArn") - cloud_account_id = role_arn.split(":")[4] + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + object_chain_dict = json.loads(object_chain) + cloud_account_id = object_chain_dict["cloudAccountId"] region = finding_info.get("Region") logging.info(f"cloud_account_id: {cloud_account_id}") diff --git a/test/unit/test_s3_enable_access_logging.py b/test/unit/test_s3_enable_access_logging.py index 5c0d8ca..711d678 100644 --- a/test/unit/test_s3_enable_access_logging.py +++ b/test/unit/test_s3_enable_access_logging.py @@ -37,10 +37,6 @@ def invalid_payload(): def full_payload(): return json.dumps( { - "cloudAccount": { - "provider": "", - "roleArn": "arn:aws:iam::530342348278:role/SecureStateRemediation", - }, "notificationInfo": { "RuleId": "5c6cc5cc03dcc90f3631468d", "RuleName": "", @@ -117,10 +113,6 @@ def full_payload(): def self_payload(): return json.dumps( { - "cloudAccount": { - "provider": "", - "roleArn": "arn:aws:iam::530342348278:role/SecureStateRemediation", - }, "notificationInfo": { "RuleId": "5c6cc5cc03dcc90f3631468d", "RuleName": "", From 8417cee129d6f9428aebee9d343a087f0947089a Mon Sep 17 00:00:00 2001 From: Zuber Date: Mon, 14 Sep 2020 14:20:17 -0700 Subject: [PATCH 05/34] PLA-16779: Add remediation job for azure security group port 22 (#10) * PLA-16779: Add remediation job for azure security group port 22 * add test dependencies * update README.md * add deployment info to the README * add rule information Co-authored-by: Mohammad Zuber Khan --- .../README.md | 69 ++++++ .../__init__.py | 0 ...re_network_security_group_close_port_22.py | 209 ++++++++++++++++++ .../constraints.txt | 109 +++++++++ .../requirements-dev.txt | 33 +++ .../requirements.txt | 6 + ...re_network_security_group_close_port_22.py | 96 ++++++++ tox.ini | 7 + 8 files changed, 529 insertions(+) create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/README.md create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/__init__.py create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/constraints.txt create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/requirements.txt create mode 100644 test/unit/test_azure_network_security_group_close_port_22.py diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md new file mode 100644 index 0000000..b0a7a35 --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md @@ -0,0 +1,69 @@ +# Close Port 22 for a Network Security Group + +This job blocks public access to port 22. + +### Applicable Rule + +#### Rule ID: +5c8c26847a550e1fb6560cab + +#### Rule Name: +The security group allows access to SSH port (22) + +## Getting Started + +### Prerequisites + +The provided Azure service principal must have permissions to make changes to the network security groups and security +rules. +Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-network-security-group#permissions): + + +### Running the script + +You may run this script using following commands: +```shell script + pip install -r requirements.txt + python3 azure_network_security_group_close_port_22.py +``` + +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + + +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. + +## License + +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/__init__.py b/remediation_worker/jobs/azure_network_security_group_close_port_22/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py new file mode 100644 index 0000000..9ebf28a --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py @@ -0,0 +1,209 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging + +from azure.mgmt.network import NetworkManagementClient +from azure.common.credentials import ServicePrincipalCredentials + +logging.basicConfig(level=logging.INFO) + + +def logcall(f, *args, **kwargs): + logging.info( + "%s(%s)", + f.__name__, + ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), + ) + logging.info(f(*args, **kwargs)) + + +class NetworkSecurityGroupClosePort22(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + + security_group_name = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectId" + ] + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" security_group: {security_group_name}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" region: {region}") + + return { + "security_group_name": security_group_name, + "resource_group_name": resource_group_name, + "subscription_id": subscription_id, + "region": region, + } + + def remediate(self, client, resource_group_name, security_group_name): + """Block public access to port 22 + + :param client: Instance of the Azure NetworkManagementClient. + :param resource_group_name: The name of the resource group to which the security_group belongs + :param security_group_name: The name of the security group. You must specify the + security group name in the request. + :type security_group_name: str. + :type resource_group_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + port = 22 + + network_security_group = client.network_security_groups.get( + resource_group_name=resource_group_name, + network_security_group_name=security_group_name, + ) + + security_rules = network_security_group.security_rules + + for rule in security_rules: + if ( + rule.access != "Allow" + or rule.direction != "Inbound" + or rule.source_address_prefix != "*" + ): + continue + if rule.destination_port_range is not None: + if int(rule.destination_port_range) == port: + security_rules.remove(rule) + else: + port_ranges = rule.destination_port_ranges + new_ranges = self._find_and_remove_port(port_ranges, port) + rule.destination_port_ranges = new_ranges + + network_security_group.security_rules = security_rules + + # Revoke permission for port 22 + logging.info("revoking permissions for port 22") + try: + logging.info( + " executing client.network_security_groups.create_or_update" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" network_security_group_name={security_group_name}") + client.network_security_groups.create_or_update( + resource_group_name, security_group_name, network_security_group + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def _find_and_remove_port(self, port_ranges, port): + """Remove the port from the port range. + + :param port_ranges: port ranges to be updated. + :param port: port to be removed + :type port_ranges: list. + :type port: int + :returns: list of port_ranges + """ + result = [] + for port_range in port_ranges: + if "-" in port_range: + boundaries = port_range.split("-") + if int(boundaries[0]) <= port and int(boundaries[1]) >= port: + if int(boundaries[0]) == port: + new_range_start = port + 1 + new_range_end = int(boundaries[1]) + if new_range_start != new_range_end: + result.append( + str(new_range_start) + "-" + str(new_range_end) + ) + else: + result.append(str(new_range_start)) + elif int(boundaries[1]) == port: + new_range_start = int(boundaries[0]) + new_range_end = port - 1 + if new_range_start != new_range_end: + result.append( + str(new_range_start) + "-" + str(new_range_end) + ) + else: + result.append(str(new_range_start)) + else: + range1_start = int(boundaries[0]) + range1_end = port - 1 + range2_start = port + 1 + range2_end = int(boundaries[1]) + + if range1_start != range1_end: + result.append(str(range1_start) + "-" + str(range1_end)) + else: + result.append(str(range1_start)) + + if range2_start != range2_end: + result.append(str(range2_start) + "-" + str(range2_end)) + else: + result.append(str(range2_start)) + else: + result.append(port_range) + elif int(port_range) != port: + result.append(port_range) + return result + + def run(self, args): + """Run the remediation job. + + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ServicePrincipalCredentials( + client_id=os.environ.get("AZURE_CLIENT_ID"), + secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant=os.environ.get("AZURE_TENANT_ID"), + ) + + client = NetworkManagementClient(credentials, params["subscription_id"]) + return self.remediate( + client, params["resource_group_name"], params["security_group_name"] + ) + + +if __name__ == "__main__": + sys.exit(NetworkSecurityGroupClosePort22().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/constraints.txt b/remediation_worker/jobs/azure_network_security_group_close_port_22/constraints.txt new file mode 100644 index 0000000..42600dd --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/constraints.txt @@ -0,0 +1,109 @@ +adal==1.2.4 \ + --hash=sha256:7a15d22b1ee7ce1be92441199958748982feba6b7dec35fbf60f9b607bad1bc0 \ + --hash=sha256:b332316f54d947f39acd9628e7d61d90f6e54d413d6f97025a51482c96bac6bc +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.0 \ + --hash=sha256:84bff2b05ce989942e7ca3a13237441fbd8ff6855aaf2979b2bc94b74a02be5f \ + --hash=sha256:c89bbdcdc13ad45fe57d775ed87b15baf6d0b039a1ecd0a1bc91d2f713cb1f08 +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.2 \ + --hash=sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e \ + --hash=sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c \ + --hash=sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e \ + --hash=sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1 \ + --hash=sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4 \ + --hash=sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2 \ + --hash=sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c \ + --hash=sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0 \ + --hash=sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798 \ + --hash=sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1 \ + --hash=sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4 \ + --hash=sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731 \ + --hash=sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4 \ + --hash=sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c \ + --hash=sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487 \ + --hash=sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e \ + --hash=sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f \ + --hash=sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123 \ + --hash=sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c \ + --hash=sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b \ + --hash=sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650 \ + --hash=sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad \ + --hash=sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75 \ + --hash=sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82 \ + --hash=sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7 \ + --hash=sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15 \ + --hash=sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa \ + --hash=sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.0 \ + --hash=sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b \ + --hash=sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd \ + --hash=sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a \ + --hash=sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07 \ + --hash=sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71 \ + --hash=sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756 \ + --hash=sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559 \ + --hash=sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f \ + --hash=sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261 \ + --hash=sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053 \ + --hash=sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2 \ + --hash=sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f \ + --hash=sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b \ + --hash=sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77 \ + --hash=sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83 \ + --hash=sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f \ + --hash=sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67 \ + --hash=sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c \ + --hash=sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6 +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msal==1.4.3 \ + --hash=sha256:51b8e8e0d918d9b4813f006324e7c4e21eb76268dd4c1a06d811a3475ad4ac57 \ + --hash=sha256:82c0ca1103f4a040f3fa5325bfd6fb6c8273fbd1d6f7c1ea92bbc94fcc360c46 +msrest==0.6.18 \ + --hash=sha256:4993023011663b4273f15432fab75cc747dfa0bca1816d8122a7d1f9fdd9288d \ + --hash=sha256:5f4ef9b8cc207d93978b1a58f055179686b9f30a5e28041872db97a4a1c49b96 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +pyjwt==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt b/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt new file mode 100644 index 0000000..1143bce --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements.txt b/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements.txt new file mode 100644 index 0000000..3c68ef7 --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-network==11.0.0 \ + --hash=sha256:0a4bda7341e33b2cfa567928f4374fe4e0c5710a328174f780813359ef15786b \ + --hash=sha256:7fdfc631c660cb173eee88abbb7b8be7742f91b522be6017867f217409cd69bc diff --git a/test/unit/test_azure_network_security_group_close_port_22.py b/test/unit/test_azure_network_security_group_close_port_22.py new file mode 100644 index 0000000..0c6e394 --- /dev/null +++ b/test/unit/test_azure_network_security_group_close_port_22.py @@ -0,0 +1,96 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from azure.mgmt.network.models import NetworkSecurityGroup +from azure.mgmt.network.models import SecurityRule +from mock import Mock + +from remediation_worker.jobs.azure_network_security_group_close_port_22.azure_network_security_group_close_port_22 import ( + NetworkSecurityGroupClosePort22, +) + + +@pytest.fixture +def valid_payload1(): + return """ +{ + "notificationInfo": { + "RuleId": "5c6cc5d903dcc90f363146b6", + "Service": "Network", + "FindingInfo": { + "FindingId": "e1606076-d55c-42c5-9ca7-93e933b1e672", + "ObjectId": "security_group_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestNetworkSecurityGroupClosePort22(object): + def test_parse_payload(self, valid_payload1): + params = NetworkSecurityGroupClosePort22().parse(valid_payload1) + assert params["security_group_name"] == "security_group_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + client.network_security_groups.get.return_value = NetworkSecurityGroup( + id="nsg", + security_rules=[ + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_ranges=["22", "3389"], + ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_ranges=["20-30", "3389"], + ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_range="22", + ), + ], + ) + + action = NetworkSecurityGroupClosePort22() + assert action.remediate(client, "security_group_name", "resource_group") == 0 + assert client.network_security_groups.create_or_update.call_count == 1 + + call_args = client.network_security_groups.create_or_update.call_args + updated_sg = call_args.args[2] + security_rules = updated_sg.security_rules + assert len(security_rules) == 2 + assert security_rules[0].destination_port_ranges == ["3389"] + assert security_rules[1].destination_port_ranges == ["20-21", "23-30", "3389"] + + def test_remediate_with_exception(self): + client = Mock() + client.network_security_groups.create_or_update.side_effect = Exception + action = NetworkSecurityGroupClosePort22() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index bc2dd70..6c3c30d 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ envlist = unit-security-group-close-port-3389 unit-rds-backup-retention-30-days unit-security-group-close-port-22 + azure_network_security_group_close_port_22 [testenv] passenv = @@ -90,3 +91,9 @@ description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_security_group_close_port_22.py deps = -r remediation_worker/jobs/security_group_close_port_22/requirements-dev.txt + +[testenv:unit-azure-network-security-group-close-port-22] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_network_security_group_close_port_22.py +deps = -r remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt From 1fc40f0a8a79913c461eab360111a63b6b239ff8 Mon Sep 17 00:00:00 2001 From: Zuber Date: Mon, 14 Sep 2020 14:33:20 -0700 Subject: [PATCH 06/34] Add remediation job for closing port 22 for VM (#11) * PLA-18743: Add remediation job for closing port 22 for VM * add tests for azure_vm_close_port_22 to tox * add deployment details Co-authored-by: Mohammad Zuber Khan --- .../jobs/azure_vm_close_port_22/README.md | 69 ++++++ .../jobs/azure_vm_close_port_22/__init__.py | 0 .../azure_vm_close_port_22.py | 226 ++++++++++++++++++ .../azure_vm_close_port_22/constraints.txt | 115 +++++++++ .../requirements-dev.txt | 33 +++ .../azure_vm_close_port_22/requirements.txt | 9 + test/unit/test_azure_vm_close_port_22.py | 129 ++++++++++ tox.ini | 7 + 8 files changed, 588 insertions(+) create mode 100644 remediation_worker/jobs/azure_vm_close_port_22/README.md create mode 100644 remediation_worker/jobs/azure_vm_close_port_22/__init__.py create mode 100644 remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py create mode 100644 remediation_worker/jobs/azure_vm_close_port_22/constraints.txt create mode 100644 remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_vm_close_port_22/requirements.txt create mode 100644 test/unit/test_azure_vm_close_port_22.py diff --git a/remediation_worker/jobs/azure_vm_close_port_22/README.md b/remediation_worker/jobs/azure_vm_close_port_22/README.md new file mode 100644 index 0000000..1333f0f --- /dev/null +++ b/remediation_worker/jobs/azure_vm_close_port_22/README.md @@ -0,0 +1,69 @@ +# Close Port 22 for a VM + +This job blocks public access to port 22 for the VM. + +### Applicable Rule + +##### Rule ID: +d7a3ad03-860c-4928-9ba8-789e84a835be + +##### Rule Name: +A virtual machine is publicly accessible to the internet via port 22 + +## Getting Started + +### Prerequisites + +The provided Azure service principal must have permissions to make changes to the network security groups and security +rules. +Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-network-security-group#permissions): + + +### Running the script + +You may run this script using following commands: +```shell script + pip install -r requirements.txt + python3 azure_vm_close_port_22.py +``` + +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: +```shell script + python3 -m pytest test +``` + +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + + +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. + +## License + +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_vm_close_port_22/__init__.py b/remediation_worker/jobs/azure_vm_close_port_22/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py new file mode 100644 index 0000000..cc031ab --- /dev/null +++ b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py @@ -0,0 +1,226 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging + +from azure.mgmt.compute import ComputeManagementClient +from azure.mgmt.network import NetworkManagementClient +from azure.common.credentials import ServicePrincipalCredentials + +logging.basicConfig(level=logging.INFO) + + +def logcall(f, *args, **kwargs): + logging.info( + "%s(%s)", + f.__name__, + ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), + ) + logging.info(f(*args, **kwargs)) + + +class VMSecurityGroupClosePort22(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + + vm_name = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" vm_name: {vm_name}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + + return { + "vm_name": vm_name, + "resource_group_name": resource_group_name, + "subscription_id": subscription_id, + "region": region, + } + + def remediate(self, compute_client, network_client, resource_group_name, vm_name): + """Block public access to port 22 of the VM + + :param compute_client: Instance of the Azure ComputeManagementClient. + :param network_client: Instance of the Azure NetworkManagementClient. + :param resource_group_name: The name of the resource group to which the security_group belongs + :param vm_name: The name of the VM. You must specify the VM name in the request. + :type vm_name: str. + :type resource_group_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + port = 22 + vm = compute_client.virtual_machines.get(resource_group_name, vm_name) + nw_interfaces = vm.network_profile.network_interfaces + + for nw_interface in nw_interfaces: + nw_interface_name = self._get_name_from_id(nw_interface.id) + nw_interface_details = network_client.network_interfaces.get( + resource_group_name, nw_interface_name + ) + security_group_name = self._get_name_from_id( + nw_interface_details.network_security_group.id + ) + network_security_group = network_client.network_security_groups.get( + resource_group_name=resource_group_name, + network_security_group_name=security_group_name, + ) + + security_rules = network_security_group.security_rules + + for rule in security_rules: + if ( + rule.access != "Allow" + or rule.direction != "Inbound" + or rule.source_address_prefix != "*" + ): + continue + if rule.destination_port_range is not None: + if int(rule.destination_port_range) == port: + security_rules.remove(rule) + else: + port_ranges = rule.destination_port_ranges + new_ranges = self._find_and_remove_port(port_ranges, port) + rule.destination_port_ranges = new_ranges + + network_security_group.security_rules = security_rules + + # Revoke permission for port 22 + logging.info("revoking permissions for port 22") + try: + logging.info( + " executing client.network_security_groups.create_or_update" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" network_security_group_name={security_group_name}") + network_client.network_security_groups.create_or_update( + resource_group_name, security_group_name, network_security_group + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def _get_name_from_id(self, id): + return id.split("/")[-1] + + def _find_and_remove_port(self, port_ranges, port): + """Remove the port from the port range. + + :param port_ranges: port ranges to be updated. + :param port: port to be removed + :type port_ranges: list. + :type port: int + :returns: list of port_ranges + """ + result = [] + for port_range in port_ranges: + if "-" in port_range: + boundaries = port_range.split("-") + if int(boundaries[0]) <= port and int(boundaries[1]) >= port: + if int(boundaries[0]) == port: + new_range_start = port + 1 + new_range_end = int(boundaries[1]) + if new_range_start != new_range_end: + result.append( + str(new_range_start) + "-" + str(new_range_end) + ) + else: + result.append(str(new_range_start)) + elif int(boundaries[1]) == port: + new_range_start = int(boundaries[0]) + new_range_end = port - 1 + if new_range_start != new_range_end: + result.append( + str(new_range_start) + "-" + str(new_range_end) + ) + else: + result.append(str(new_range_start)) + else: + range1_start = int(boundaries[0]) + range1_end = port - 1 + range2_start = port + 1 + range2_end = int(boundaries[1]) + + if range1_start != range1_end: + result.append(str(range1_start) + "-" + str(range1_end)) + else: + result.append(str(range1_start)) + + if range2_start != range2_end: + result.append(str(range2_start) + "-" + str(range2_end)) + else: + result.append(str(range2_start)) + else: + result.append(port_range) + elif int(port_range) != port: + result.append(port_range) + return result + + def run(self, args): + """Run the remediation job. + + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ServicePrincipalCredentials( + client_id=os.environ.get("AZURE_CLIENT_ID"), + secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant=os.environ.get("AZURE_TENANT_ID"), + ) + + compute_client = ComputeManagementClient(credentials, params["subscription_id"]) + nw_client = NetworkManagementClient(credentials, params["subscription_id"]) + return self.remediate( + compute_client, + nw_client, + params["resource_group_name"], + params["security_group_name"], + ) + + +if __name__ == "__main__": + sys.exit(VMSecurityGroupClosePort22().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_vm_close_port_22/constraints.txt b/remediation_worker/jobs/azure_vm_close_port_22/constraints.txt new file mode 100644 index 0000000..3ab47a3 --- /dev/null +++ b/remediation_worker/jobs/azure_vm_close_port_22/constraints.txt @@ -0,0 +1,115 @@ +adal==1.2.4 \ + --hash=sha256:7a15d22b1ee7ce1be92441199958748982feba6b7dec35fbf60f9b607bad1bc0 \ + --hash=sha256:b332316f54d947f39acd9628e7d61d90f6e54d413d6f97025a51482c96bac6bc +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.0 \ + --hash=sha256:84bff2b05ce989942e7ca3a13237441fbd8ff6855aaf2979b2bc94b74a02be5f \ + --hash=sha256:c89bbdcdc13ad45fe57d775ed87b15baf6d0b039a1ecd0a1bc91d2f713cb1f08 +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-network==11.0.0 \ + --hash=sha256:0a4bda7341e33b2cfa567928f4374fe4e0c5710a328174f780813359ef15786b \ + --hash=sha256:7fdfc631c660cb173eee88abbb7b8be7742f91b522be6017867f217409cd69bc +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.2 \ + --hash=sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e \ + --hash=sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c \ + --hash=sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e \ + --hash=sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1 \ + --hash=sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4 \ + --hash=sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2 \ + --hash=sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c \ + --hash=sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0 \ + --hash=sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798 \ + --hash=sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1 \ + --hash=sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4 \ + --hash=sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731 \ + --hash=sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4 \ + --hash=sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c \ + --hash=sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487 \ + --hash=sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e \ + --hash=sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f \ + --hash=sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123 \ + --hash=sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c \ + --hash=sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b \ + --hash=sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650 \ + --hash=sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad \ + --hash=sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75 \ + --hash=sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82 \ + --hash=sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7 \ + --hash=sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15 \ + --hash=sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa \ + --hash=sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.0 \ + --hash=sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b \ + --hash=sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd \ + --hash=sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a \ + --hash=sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07 \ + --hash=sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71 \ + --hash=sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756 \ + --hash=sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559 \ + --hash=sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f \ + --hash=sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261 \ + --hash=sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053 \ + --hash=sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2 \ + --hash=sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f \ + --hash=sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b \ + --hash=sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77 \ + --hash=sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83 \ + --hash=sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f \ + --hash=sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67 \ + --hash=sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c \ + --hash=sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6 +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msal==1.4.3 \ + --hash=sha256:51b8e8e0d918d9b4813f006324e7c4e21eb76268dd4c1a06d811a3475ad4ac57 \ + --hash=sha256:82c0ca1103f4a040f3fa5325bfd6fb6c8273fbd1d6f7c1ea92bbc94fcc360c46 +msrest==0.6.18 \ + --hash=sha256:4993023011663b4273f15432fab75cc747dfa0bca1816d8122a7d1f9fdd9288d \ + --hash=sha256:5f4ef9b8cc207d93978b1a58f055179686b9f30a5e28041872db97a4a1c49b96 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +pyjwt==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt b/remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt new file mode 100644 index 0000000..1143bce --- /dev/null +++ b/remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 diff --git a/remediation_worker/jobs/azure_vm_close_port_22/requirements.txt b/remediation_worker/jobs/azure_vm_close_port_22/requirements.txt new file mode 100644 index 0000000..9307e77 --- /dev/null +++ b/remediation_worker/jobs/azure_vm_close_port_22/requirements.txt @@ -0,0 +1,9 @@ +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-compute==13.0.0 \ + --hash=sha256:0848fe37b4b6e49bed07d3969789072da7c259e8a8d21458251c3912f695e7c2 \ + --hash=sha256:7f331bafcbedf25d65aa42038f7553747dab18d7f10a5af3297192d31c45339e +azure-mgmt-network==11.0.0 \ + --hash=sha256:0a4bda7341e33b2cfa567928f4374fe4e0c5710a328174f780813359ef15786b \ + --hash=sha256:7fdfc631c660cb173eee88abbb7b8be7742f91b522be6017867f217409cd69bc diff --git a/test/unit/test_azure_vm_close_port_22.py b/test/unit/test_azure_vm_close_port_22.py new file mode 100644 index 0000000..0b5bf4f --- /dev/null +++ b/test/unit/test_azure_vm_close_port_22.py @@ -0,0 +1,129 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from azure.mgmt.network.models import NetworkSecurityGroup, NetworkInterface +from azure.mgmt.network.models import SecurityRule +from azure.mgmt.compute.models import ( + VirtualMachine, + NetworkProfile, + NetworkInterfaceReference, +) +from mock import Mock + +from remediation_worker.jobs.azure_vm_close_port_22.azure_vm_close_port_22 import ( + VMSecurityGroupClosePort22, +) + + +@pytest.fixture +def valid_payload1(): + return """ +{ + "notificationInfo": { + "RuleId": "5c6cc5e203dcc90f363146ce", + "Service": "Network", + "FindingInfo": { + "FindingId": "e1606076-d55c-42c5-9ca7-93e933b1e672", + "ObjectId": "vm_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Storage.d687b1a3-9b78-43b1-a17b-7de297fd1fce.khanz-test.StorageAccount.testingresourcename\\",\\"entityName\\":\\"testingresourcename\\",\\"entityType\\":\\"Azure.Storage.StorageAccount\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Storage\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestStorageAccountAllowHttpsTrafficOnly(object): + def test_parse_payload(self, valid_payload1): + params = VMSecurityGroupClosePort22().parse(valid_payload1) + assert params["vm_name"] == "vm_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + compute_client = Mock() + nw_profile = NetworkProfile( + network_interfaces=[ + NetworkInterfaceReference( + id="/subscriptions/subscription_id/resourceGroups/resource_group/providers/Microsoft.Network" + "/networkInterfaces/vm_nameVMNic " + ) + ] + ) + + compute_client.virtual_machines.get.return_value = VirtualMachine( + id="/subscriptions/subscription_id/resourceGroups/resource_group/providers/Microsoft.Compute" + "/virtualMachines/vm_name", + location="eastus", + network_profile=nw_profile, + ) + nw_client = Mock() + nw_client.network_interfaces.get.return_value = NetworkInterface( + id="/subscriptions/subscription_id/resourceGroups/resource_group/providers/Microsoft.Network" + "/networkInterfaces/vm_nameVMNic", + network_security_group=NetworkSecurityGroup( + id="/subscriptions/subscription_id/resourceGroups/resource_name/providers/Microsoft.Network" + "/networkSecurityGroups/vm_nameNSG " + ), + ) + + nw_client.network_security_groups.get.return_value = NetworkSecurityGroup( + id="/subscriptions/subscription_id/resourceGroups/resource_name/providers/Microsoft.Network" + "/networkSecurityGroups/vm_nameNSG", + security_rules=[ + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_ranges=["22", "3389"], + ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_ranges=["20-30", "3389"], + ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_range="22", + ), + ], + ) + + action = VMSecurityGroupClosePort22() + assert ( + action.remediate(compute_client, nw_client, "resource_name", "vm_name") == 0 + ) + assert nw_client.network_security_groups.create_or_update.call_count == 1 + call_args = nw_client.network_security_groups.create_or_update.call_args + updated_sg = call_args.args[2] + security_rules = updated_sg.security_rules + assert len(security_rules) == 2 + assert security_rules[0].destination_port_ranges == ["3389"] + assert security_rules[1].destination_port_ranges == ["20-21", "23-30", "3389"] + + # + def test_remediate_with_exception(self): + client = Mock() + client.network_security_groups.create_or_update.side_effect = Exception + action = VMSecurityGroupClosePort22() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index 6c3c30d..f0cbeae 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,7 @@ envlist = unit-rds-backup-retention-30-days unit-security-group-close-port-22 azure_network_security_group_close_port_22 + unit-azure-vm-close-port-22 [testenv] passenv = @@ -97,3 +98,9 @@ description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_network_security_group_close_port_22.py deps = -r remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt + +[testenv:unit-azure-vm-close-port-22] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_vm_close_port_22.py +deps = -r remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt From fb7b5faf746d20f7dd57022f26f21294ff58c2d0 Mon Sep 17 00:00:00 2001 From: Zuber Date: Tue, 15 Sep 2020 10:14:41 -0700 Subject: [PATCH 07/34] Add remediation jobs for storage and RDP violations (#12) * add remediation job for closing RDP access * Add remediation job for remove public access for blob * Add remediation job to allow only https traffic to storage account * update READMEs to fix broken links Co-authored-by: Mohammad Zuber Khan --- .../azure_blob_remove_public_access/README.md | 70 ++++++ .../__init__.py | 0 .../azure_blob_remove_public_access.py | 151 +++++++++++++ .../constraints.txt | 115 ++++++++++ .../requirements-dev.txt | 33 +++ .../requirements.txt | 6 + .../README.md | 5 +- .../README.md | 70 ++++++ .../__init__.py | 0 ..._network_security_group_close_port_3389.py | 206 ++++++++++++++++++ .../constraints.txt | 115 ++++++++++ .../requirements-dev.txt | 33 +++ .../requirements.txt | 6 + .../README.md | 71 ++++++ .../__init__.py | 0 ...torage_account_allow_https_traffic_only.py | 134 ++++++++++++ .../constraints.txt | 115 ++++++++++ .../requirements-dev.txt | 33 +++ .../requirements.txt | 6 + .../jobs/azure_vm_close_port_22/README.md | 4 +- .../jobs/ec2_close_port_22/README.md | 4 +- .../jobs/ec2_close_port_3389/README.md | 4 +- .../rds_backup_retention_30_days/README.md | 19 +- .../jobs/s3_enable_access_logging/README.md | 4 +- .../s3_enable_default_encryption/README.md | 4 +- .../jobs/s3_list_buckets/README.md | 4 +- .../jobs/s3_remove_public_access/README.md | 4 +- .../jobs/s3_remove_public_admin_acl/README.md | 4 +- .../security_group_close_port_22/README.md | 18 +- .../security_group_close_port_3389/README.md | 18 +- .../security_group_close_port_5432/README.md | 18 +- .../test_azure_blob_remove_public_access.py | 68 ++++++ ..._network_security_group_close_port_3389.py | 97 +++++++++ ...torage_account_allow_https_traffic_only.py | 64 ++++++ tox.ini | 23 +- 35 files changed, 1499 insertions(+), 27 deletions(-) create mode 100644 remediation_worker/jobs/azure_blob_remove_public_access/README.md create mode 100644 remediation_worker/jobs/azure_blob_remove_public_access/__init__.py create mode 100644 remediation_worker/jobs/azure_blob_remove_public_access/azure_blob_remove_public_access.py create mode 100644 remediation_worker/jobs/azure_blob_remove_public_access/constraints.txt create mode 100644 remediation_worker/jobs/azure_blob_remove_public_access/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_blob_remove_public_access/requirements.txt create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_3389/README.md create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_3389/__init__.py create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_3389/constraints.txt create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements.txt create mode 100644 remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/README.md create mode 100644 remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/__init__.py create mode 100644 remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py create mode 100644 remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/constraints.txt create mode 100644 remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements.txt create mode 100644 test/unit/test_azure_blob_remove_public_access.py create mode 100644 test/unit/test_azure_network_security_group_close_port_3389.py create mode 100644 test/unit/test_azure_storage_account_allow_https_traffic_only.py diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/README.md b/remediation_worker/jobs/azure_blob_remove_public_access/README.md new file mode 100644 index 0000000..1289804 --- /dev/null +++ b/remediation_worker/jobs/azure_blob_remove_public_access/README.md @@ -0,0 +1,70 @@ +# Remove Blob public access + +This job removes public access to the blobs in a container for a storage account + +### Applicable Rule + +##### Rule ID: +5c8c26997a550e1fb6560cd9 + +##### Rule Name: +Public read access is enabled for blob storage + +## Getting Started + +### Prerequisites + +The provided Azure service principal must have permissions to make changes to the storage account + +Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#storage-account-contributor): + + +### Running the script + +You may run this script using following commands: +```shell script + pip install -r requirements.txt + python3 azure_remove_blob_public_access.py +``` + +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` + +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + + +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. + +## License + +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/__init__.py b/remediation_worker/jobs/azure_blob_remove_public_access/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/azure_blob_remove_public_access.py b/remediation_worker/jobs/azure_blob_remove_public_access/azure_blob_remove_public_access.py new file mode 100644 index 0000000..804f0f9 --- /dev/null +++ b/remediation_worker/jobs/azure_blob_remove_public_access/azure_blob_remove_public_access.py @@ -0,0 +1,151 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging + +from azure.mgmt.storage import StorageManagementClient +from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.storage.models import PublicAccess + +logging.basicConfig(level=logging.INFO) + + +def logcall(f, *args, **kwargs): + logging.info( + "%s(%s)", + f.__name__, + ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), + ) + logging.info(f(*args, **kwargs)) + + +class StorageBlobRemovePublicAccess(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + + object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + object_components = object_id.split(".") + account_name = object_components[0] + container_name = object_components[-1] + + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" account_name: {account_name}") + logging.info(f" container_name: {container_name}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + + return { + "resource_group_name": resource_group_name, + "account_name": account_name, + "container_name": container_name, + "subscription_id": subscription_id, + "region": region, + } + + def remediate(self, client, resource_group_name, account_name, container_name): + """Block public access to blob container + + :param client: Instance of the Azure NetworkManagementClient. + :param resource_group_name: The name of the resource group to which the storage account belongs + :param account_name: The name of the storage account. You must specify the + security group name in the request. + :param container_name: The name of the container having the violation + :type resource_group_name: str. + :type account_name: str. + :type container_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + container = client.blob_containers.get( + resource_group_name=resource_group_name, + account_name=account_name, + container_name=container_name, + ) + + container.public_access = PublicAccess.none + + # Revoke public access permissions for container + logging.info("revoking public access for container") + try: + logging.info(" executing client.blob_containers.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={account_name}") + logging.info(f" container_name={container_name}") + client.blob_containers.update( + resource_group_name=resource_group_name, + account_name=account_name, + container_name=container_name, + blob_container=container, + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def run(self, args): + """Run the remediation job. + + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ServicePrincipalCredentials( + client_id=os.environ.get("AZURE_CLIENT_ID"), + secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant=os.environ.get("AZURE_TENANT_ID"), + ) + + client = StorageManagementClient(credentials, params["subscription_id"]) + return self.remediate( + client, + params["resource_group_name"], + params["account_name"], + params["container_name"], + ) + + +if __name__ == "__main__": + sys.exit(StorageBlobRemovePublicAccess().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/constraints.txt b/remediation_worker/jobs/azure_blob_remove_public_access/constraints.txt new file mode 100644 index 0000000..3ab47a3 --- /dev/null +++ b/remediation_worker/jobs/azure_blob_remove_public_access/constraints.txt @@ -0,0 +1,115 @@ +adal==1.2.4 \ + --hash=sha256:7a15d22b1ee7ce1be92441199958748982feba6b7dec35fbf60f9b607bad1bc0 \ + --hash=sha256:b332316f54d947f39acd9628e7d61d90f6e54d413d6f97025a51482c96bac6bc +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.0 \ + --hash=sha256:84bff2b05ce989942e7ca3a13237441fbd8ff6855aaf2979b2bc94b74a02be5f \ + --hash=sha256:c89bbdcdc13ad45fe57d775ed87b15baf6d0b039a1ecd0a1bc91d2f713cb1f08 +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-network==11.0.0 \ + --hash=sha256:0a4bda7341e33b2cfa567928f4374fe4e0c5710a328174f780813359ef15786b \ + --hash=sha256:7fdfc631c660cb173eee88abbb7b8be7742f91b522be6017867f217409cd69bc +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.2 \ + --hash=sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e \ + --hash=sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c \ + --hash=sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e \ + --hash=sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1 \ + --hash=sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4 \ + --hash=sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2 \ + --hash=sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c \ + --hash=sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0 \ + --hash=sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798 \ + --hash=sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1 \ + --hash=sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4 \ + --hash=sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731 \ + --hash=sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4 \ + --hash=sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c \ + --hash=sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487 \ + --hash=sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e \ + --hash=sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f \ + --hash=sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123 \ + --hash=sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c \ + --hash=sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b \ + --hash=sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650 \ + --hash=sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad \ + --hash=sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75 \ + --hash=sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82 \ + --hash=sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7 \ + --hash=sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15 \ + --hash=sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa \ + --hash=sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.0 \ + --hash=sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b \ + --hash=sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd \ + --hash=sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a \ + --hash=sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07 \ + --hash=sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71 \ + --hash=sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756 \ + --hash=sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559 \ + --hash=sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f \ + --hash=sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261 \ + --hash=sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053 \ + --hash=sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2 \ + --hash=sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f \ + --hash=sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b \ + --hash=sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77 \ + --hash=sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83 \ + --hash=sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f \ + --hash=sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67 \ + --hash=sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c \ + --hash=sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6 +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msal==1.4.3 \ + --hash=sha256:51b8e8e0d918d9b4813f006324e7c4e21eb76268dd4c1a06d811a3475ad4ac57 \ + --hash=sha256:82c0ca1103f4a040f3fa5325bfd6fb6c8273fbd1d6f7c1ea92bbc94fcc360c46 +msrest==0.6.18 \ + --hash=sha256:4993023011663b4273f15432fab75cc747dfa0bca1816d8122a7d1f9fdd9288d \ + --hash=sha256:5f4ef9b8cc207d93978b1a58f055179686b9f30a5e28041872db97a4a1c49b96 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +pyjwt==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/requirements-dev.txt b/remediation_worker/jobs/azure_blob_remove_public_access/requirements-dev.txt new file mode 100644 index 0000000..1143bce --- /dev/null +++ b/remediation_worker/jobs/azure_blob_remove_public_access/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/requirements.txt b/remediation_worker/jobs/azure_blob_remove_public_access/requirements.txt new file mode 100644 index 0000000..50850c0 --- /dev/null +++ b/remediation_worker/jobs/azure_blob_remove_public_access/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-storage==11.1.0 \ + --hash=sha256:62a6a8c1c359026ec560856da25221b66b6f1e0a84763a04e863c6e911bc1a5e \ + --hash=sha256:ef23587c1b6dc0866ebf0e91e83ba05d7f7e4fea7951b704781b9cd9f5f27f1c diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md index b0a7a35..0e0e781 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md @@ -56,13 +56,14 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who + participated in this project. ## License diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/README.md b/remediation_worker/jobs/azure_network_security_group_close_port_3389/README.md new file mode 100644 index 0000000..566fd13 --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/README.md @@ -0,0 +1,70 @@ +# Close Port 3389 for a Network Security Group + +This job blocks public access to port 3389 + +### Applicable Rule + +#### Rule ID: +5c8c267e7a550e1fb6560c9c + +#### Rule Name: +The security group allows access to Remote Desktop port (3389) + +## Getting Started + +### Prerequisites + +The provided Azure service principal must have permissions to make changes to the network security groups and security +rules. +Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-network-security-group#permissions): + + +### Running the script + +You may run this script using following commands: +```shell script + pip install -r requirements.txt + python3 azure_network_security_group_close_port_22.py +``` + +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` + +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + + +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. + +## License + +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/__init__.py b/remediation_worker/jobs/azure_network_security_group_close_port_3389/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py new file mode 100644 index 0000000..2e64a02 --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py @@ -0,0 +1,206 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging + +from azure.mgmt.network import NetworkManagementClient +from azure.common.credentials import ServicePrincipalCredentials + +logging.basicConfig(level=logging.INFO) + + +def logcall(f, *args, **kwargs): + logging.info( + "%s(%s)", + f.__name__, + ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), + ) + logging.info(f(*args, **kwargs)) + + +class NetworkSecurityGroupClosePort3389(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + + security_group_name = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectChain"] + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" security_group: {security_group_name}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + + return { + "security_group_name": security_group_name, + "resource_group_name": resource_group_name, + "subscription_id": subscription_id, + "region": region, + } + + def remediate(self, client, resource_group_name, security_group_name): + """Block public access to port 3389 + + :param client: Instance of the Azure NetworkManagementClient. + :param resource_group_name: The name of the resource group to which the security_group belongs + :param security_group_name: The name of the security group. You must specify the + security group name in the request. + :type security_group_name: str. + :type resource_group_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + port = 3389 + + network_security_group = client.network_security_groups.get( + resource_group_name=resource_group_name, + network_security_group_name=security_group_name, + ) + + security_rules = network_security_group.security_rules + + for rule in security_rules: + if ( + rule.access != "Allow" + or rule.direction != "Inbound" + or rule.source_address_prefix != "*" + ): + continue + if rule.destination_port_range is not None: + if int(rule.destination_port_range) == port: + security_rules.remove(rule) + else: + port_ranges = rule.destination_port_ranges + new_ranges = self._find_and_remove_port(port_ranges, port) + rule.destination_port_ranges = new_ranges + + network_security_group.security_rules = security_rules + + # Revoke permission for port 3389 + logging.info("revoking permissions for port 3389") + try: + logging.info( + " executing client.network_security_groups.create_or_update" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" network_security_group_name={security_group_name}") + client.network_security_groups.create_or_update( + resource_group_name, security_group_name, network_security_group + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def _find_and_remove_port(self, port_ranges, port): + """Remove the port from the port range. + + :param port_ranges: port ranges to be updated. + :param port: port to be removed + :type port_ranges: list. + :type port: int + :returns: list of port_ranges + """ + result = [] + for port_range in port_ranges: + if "-" in port_range: + boundaries = port_range.split("-") + if int(boundaries[0]) <= port and int(boundaries[1]) >= port: + if int(boundaries[0]) == port: + new_range_start = port + 1 + new_range_end = int(boundaries[1]) + if new_range_start != new_range_end: + result.append( + str(new_range_start) + "-" + str(new_range_end) + ) + else: + result.append(str(new_range_start)) + elif int(boundaries[1]) == port: + new_range_start = int(boundaries[0]) + new_range_end = port - 1 + if new_range_start != new_range_end: + result.append( + str(new_range_start) + "-" + str(new_range_end) + ) + else: + result.append(str(new_range_start)) + else: + range1_start = int(boundaries[0]) + range1_end = port - 1 + range2_start = port + 1 + range2_end = int(boundaries[1]) + + if range1_start != range1_end: + result.append(str(range1_start) + "-" + str(range1_end)) + else: + result.append(str(range1_start)) + + if range2_start != range2_end: + result.append(str(range2_start) + "-" + str(range2_end)) + else: + result.append(str(range2_start)) + else: + result.append(port_range) + elif int(port_range) != port: + result.append(port_range) + return result + + def run(self, args): + """Run the remediation job. + + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ServicePrincipalCredentials( + client_id=os.environ.get("AZURE_CLIENT_ID"), + secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant=os.environ.get("AZURE_TENANT_ID"), + ) + + client = NetworkManagementClient(credentials, params["subscription_id"]) + return self.remediate( + client, params["resource_group_name"], params["security_group_name"] + ) + + +if __name__ == "__main__": + sys.exit(NetworkSecurityGroupClosePort3389().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/constraints.txt b/remediation_worker/jobs/azure_network_security_group_close_port_3389/constraints.txt new file mode 100644 index 0000000..3ab47a3 --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/constraints.txt @@ -0,0 +1,115 @@ +adal==1.2.4 \ + --hash=sha256:7a15d22b1ee7ce1be92441199958748982feba6b7dec35fbf60f9b607bad1bc0 \ + --hash=sha256:b332316f54d947f39acd9628e7d61d90f6e54d413d6f97025a51482c96bac6bc +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.0 \ + --hash=sha256:84bff2b05ce989942e7ca3a13237441fbd8ff6855aaf2979b2bc94b74a02be5f \ + --hash=sha256:c89bbdcdc13ad45fe57d775ed87b15baf6d0b039a1ecd0a1bc91d2f713cb1f08 +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-network==11.0.0 \ + --hash=sha256:0a4bda7341e33b2cfa567928f4374fe4e0c5710a328174f780813359ef15786b \ + --hash=sha256:7fdfc631c660cb173eee88abbb7b8be7742f91b522be6017867f217409cd69bc +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.2 \ + --hash=sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e \ + --hash=sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c \ + --hash=sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e \ + --hash=sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1 \ + --hash=sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4 \ + --hash=sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2 \ + --hash=sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c \ + --hash=sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0 \ + --hash=sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798 \ + --hash=sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1 \ + --hash=sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4 \ + --hash=sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731 \ + --hash=sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4 \ + --hash=sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c \ + --hash=sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487 \ + --hash=sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e \ + --hash=sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f \ + --hash=sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123 \ + --hash=sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c \ + --hash=sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b \ + --hash=sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650 \ + --hash=sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad \ + --hash=sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75 \ + --hash=sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82 \ + --hash=sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7 \ + --hash=sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15 \ + --hash=sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa \ + --hash=sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.0 \ + --hash=sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b \ + --hash=sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd \ + --hash=sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a \ + --hash=sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07 \ + --hash=sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71 \ + --hash=sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756 \ + --hash=sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559 \ + --hash=sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f \ + --hash=sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261 \ + --hash=sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053 \ + --hash=sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2 \ + --hash=sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f \ + --hash=sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b \ + --hash=sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77 \ + --hash=sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83 \ + --hash=sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f \ + --hash=sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67 \ + --hash=sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c \ + --hash=sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6 +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msal==1.4.3 \ + --hash=sha256:51b8e8e0d918d9b4813f006324e7c4e21eb76268dd4c1a06d811a3475ad4ac57 \ + --hash=sha256:82c0ca1103f4a040f3fa5325bfd6fb6c8273fbd1d6f7c1ea92bbc94fcc360c46 +msrest==0.6.18 \ + --hash=sha256:4993023011663b4273f15432fab75cc747dfa0bca1816d8122a7d1f9fdd9288d \ + --hash=sha256:5f4ef9b8cc207d93978b1a58f055179686b9f30a5e28041872db97a4a1c49b96 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +pyjwt==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements-dev.txt b/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements-dev.txt new file mode 100644 index 0000000..1143bce --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements.txt b/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements.txt new file mode 100644 index 0000000..3c68ef7 --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-network==11.0.0 \ + --hash=sha256:0a4bda7341e33b2cfa567928f4374fe4e0c5710a328174f780813359ef15786b \ + --hash=sha256:7fdfc631c660cb173eee88abbb7b8be7742f91b522be6017867f217409cd69bc diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/README.md b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/README.md new file mode 100644 index 0000000..018f901 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/README.md @@ -0,0 +1,71 @@ +# Allow only https traffic to the storage account + +This job enables only secure traffic to the storage account + +### Applicable Rule + +#### Rule ID: +5c8c269a7a550e1fb6560cdb + +#### Rule Name: +Secure connections are not enabled for storage transactions + + +## Getting Started + +### Prerequisites + +The provided Azure service principal must have permissions to make changes to the storage accounts. + +Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#storage-account-contributor): + + +### Running the script + +You may run this script using following commands: +```shell script + pip install -r requirements.txt + python3 azure_storage_account_allow_https_traffic_only.py +``` + +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` + +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + + +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. + +## License + +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/__init__.py b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py new file mode 100644 index 0000000..36d4c11 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py @@ -0,0 +1,134 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging + +from azure.mgmt.storage import StorageManagementClient +from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.storage.models import StorageAccountUpdateParameters + +logging.basicConfig(level=logging.INFO) + + +def logcall(f, *args, **kwargs): + logging.info( + "%s(%s)", + f.__name__, + ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), + ) + logging.info(f(*args, **kwargs)) + + +class StorageAccountAllowHttpsTrafficOnly(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + logging.info("unparsed params") + logging.info(f" {remediation_entry}") + + account_name = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectChain"] + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" account_name: {account_name}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + + return { + "resource_group_name": resource_group_name, + "account_name": account_name, + "subscription_id": subscription_id, + "region": region, + } + + def remediate(self, client, resource_group_name, account_name): + """Block public access to blob container + + :param client: Instance of the Azure NetworkManagementClient. + :param resource_group_name: The name of the resource group to which the storage account belongs + :param account_name: The name of the storage account. You must specify the + account name in the request. + :type resource_group_name: str. + :type account_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + update_params = StorageAccountUpdateParameters(enable_https_traffic_only=True) + + # Allow only https traffic for the storage account + logging.info("Enabling HTTPS only traffic for storage account") + try: + logging.info( + " executing client.storage_accounts.update" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={account_name}") + client.storage_accounts.update( + resource_group_name=resource_group_name, + account_name=account_name, + parameters=update_params, + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def run(self, args): + """Run the remediation job. + + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ServicePrincipalCredentials( + client_id=os.environ.get("AZURE_CLIENT_ID"), + secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant=os.environ.get("AZURE_TENANT_ID"), + ) + + client = StorageManagementClient(credentials, params["subscription_id"]) + return self.remediate( + client, params["resource_group_name"], params["account_name"], params["container_name"] + ) + + +if __name__ == "__main__": + sys.exit(StorageAccountAllowHttpsTrafficOnly().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/constraints.txt b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/constraints.txt new file mode 100644 index 0000000..3ab47a3 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/constraints.txt @@ -0,0 +1,115 @@ +adal==1.2.4 \ + --hash=sha256:7a15d22b1ee7ce1be92441199958748982feba6b7dec35fbf60f9b607bad1bc0 \ + --hash=sha256:b332316f54d947f39acd9628e7d61d90f6e54d413d6f97025a51482c96bac6bc +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.0 \ + --hash=sha256:84bff2b05ce989942e7ca3a13237441fbd8ff6855aaf2979b2bc94b74a02be5f \ + --hash=sha256:c89bbdcdc13ad45fe57d775ed87b15baf6d0b039a1ecd0a1bc91d2f713cb1f08 +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-network==11.0.0 \ + --hash=sha256:0a4bda7341e33b2cfa567928f4374fe4e0c5710a328174f780813359ef15786b \ + --hash=sha256:7fdfc631c660cb173eee88abbb7b8be7742f91b522be6017867f217409cd69bc +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.2 \ + --hash=sha256:0da50dcbccd7cb7e6c741ab7912b2eff48e85af217d72b57f80ebc616257125e \ + --hash=sha256:12a453e03124069b6896107ee133ae3ab04c624bb10683e1ed1c1663df17c13c \ + --hash=sha256:15419020b0e812b40d96ec9d369b2bc8109cc3295eac6e013d3261343580cc7e \ + --hash=sha256:15a5f59a4808f82d8ec7364cbace851df591c2d43bc76bcbe5c4543a7ddd1bf1 \ + --hash=sha256:23e44937d7695c27c66a54d793dd4b45889a81b35c0751ba91040fe825ec59c4 \ + --hash=sha256:29c4688ace466a365b85a51dcc5e3c853c1d283f293dfcc12f7a77e498f160d2 \ + --hash=sha256:57214fa5430399dffd54f4be37b56fe22cedb2b98862550d43cc085fb698dc2c \ + --hash=sha256:577791f948d34d569acb2d1add5831731c59d5a0c50a6d9f629ae1cefd9ca4a0 \ + --hash=sha256:6539314d84c4d36f28d73adc1b45e9f4ee2a89cdc7e5d2b0a6dbacba31906798 \ + --hash=sha256:65867d63f0fd1b500fa343d7798fa64e9e681b594e0a07dc934c13e76ee28fb1 \ + --hash=sha256:672b539db20fef6b03d6f7a14b5825d57c98e4026401fce838849f8de73fe4d4 \ + --hash=sha256:6843db0343e12e3f52cc58430ad559d850a53684f5b352540ca3f1bc56df0731 \ + --hash=sha256:7057613efefd36cacabbdbcef010e0a9c20a88fc07eb3e616019ea1692fa5df4 \ + --hash=sha256:76ada88d62eb24de7051c5157a1a78fd853cca9b91c0713c2e973e4196271d0c \ + --hash=sha256:837398c2ec00228679513802e3744d1e8e3cb1204aa6ad408b6aff081e99a487 \ + --hash=sha256:8662aabfeab00cea149a3d1c2999b0731e70c6b5bac596d95d13f643e76d3d4e \ + --hash=sha256:95e9094162fa712f18b4f60896e34b621df99147c2cee216cfa8f022294e8e9f \ + --hash=sha256:99cc66b33c418cd579c0f03b77b94263c305c389cb0c6972dac420f24b3bf123 \ + --hash=sha256:9b219511d8b64d3fa14261963933be34028ea0e57455baf6781fe399c2c3206c \ + --hash=sha256:ae8f34d50af2c2154035984b8b5fc5d9ed63f32fe615646ab435b05b132ca91b \ + --hash=sha256:b9aa9d8818c2e917fa2c105ad538e222a5bce59777133840b93134022a7ce650 \ + --hash=sha256:bf44a9a0141a082e89c90e8d785b212a872db793a0080c20f6ae6e2a0ebf82ad \ + --hash=sha256:c0b48b98d79cf795b0916c57bebbc6d16bb43b9fc9b8c9f57f4cf05881904c75 \ + --hash=sha256:da9d3c506f43e220336433dffe643fbfa40096d408cb9b7f2477892f369d5f82 \ + --hash=sha256:e4082d832e36e7f9b2278bc774886ca8207346b99f278e54c9de4834f17232f7 \ + --hash=sha256:e4b9b7af398c32e408c00eb4e0d33ced2f9121fd9fb978e6c1b57edd014a7d15 \ + --hash=sha256:e613514a82539fc48291d01933951a13ae93b6b444a88782480be32245ed4afa \ + --hash=sha256:f5033952def24172e60493b68717792e3aebb387a8d186c43c020d9363ee7281 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.0 \ + --hash=sha256:0c608ff4d4adad9e39b5057de43657515c7da1ccb1807c3a27d4cf31fc923b4b \ + --hash=sha256:0cbfed8ea74631fe4de00630f4bb592dad564d57f73150d6f6796a24e76c76cd \ + --hash=sha256:124af7255ffc8e964d9ff26971b3a6153e1a8a220b9a685dc407976ecb27a06a \ + --hash=sha256:384d7c681b1ab904fff3400a6909261cae1d0939cc483a68bdedab282fb89a07 \ + --hash=sha256:45741f5499150593178fc98d2c1a9c6722df88b99c821ad6ae298eff0ba1ae71 \ + --hash=sha256:4b9303507254ccb1181d1803a2080a798910ba89b1a3c9f53639885c90f7a756 \ + --hash=sha256:4d355f2aee4a29063c10164b032d9fa8a82e2c30768737a2fd56d256146ad559 \ + --hash=sha256:51e40123083d2f946794f9fe4adeeee2922b581fa3602128ce85ff813d85b81f \ + --hash=sha256:8713ddb888119b0d2a1462357d5946b8911be01ddbf31451e1d07eaa5077a261 \ + --hash=sha256:8e924dbc025206e97756e8903039662aa58aa9ba357d8e1d8fc29e3092322053 \ + --hash=sha256:8ecef21ac982aa78309bb6f092d1677812927e8b5ef204a10c326fc29f1367e2 \ + --hash=sha256:8ecf9400d0893836ff41b6f977a33972145a855b6efeb605b49ee273c5e6469f \ + --hash=sha256:9367d00e14dee8d02134c6c9524bb4bd39d4c162456343d07191e2a0b5ec8b3b \ + --hash=sha256:a09fd9c1cca9a46b6ad4bea0a1f86ab1de3c0c932364dbcf9a6c2a5eeb44fa77 \ + --hash=sha256:ab49edd5bea8d8b39a44b3db618e4783ef84c19c8b47286bf05dfdb3efb01c83 \ + --hash=sha256:bea0b0468f89cdea625bb3f692cd7a4222d80a6bdafd6fb923963f2b9da0e15f \ + --hash=sha256:bec7568c6970b865f2bcebbe84d547c52bb2abadf74cefce396ba07571109c67 \ + --hash=sha256:ce82cc06588e5cbc2a7df3c8a9c778f2cb722f56835a23a68b5a7264726bb00c \ + --hash=sha256:dea0ba7fe6f9461d244679efa968d215ea1f989b9c1957d7f10c21e5c7c09ad6 +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msal==1.4.3 \ + --hash=sha256:51b8e8e0d918d9b4813f006324e7c4e21eb76268dd4c1a06d811a3475ad4ac57 \ + --hash=sha256:82c0ca1103f4a040f3fa5325bfd6fb6c8273fbd1d6f7c1ea92bbc94fcc360c46 +msrest==0.6.18 \ + --hash=sha256:4993023011663b4273f15432fab75cc747dfa0bca1816d8122a7d1f9fdd9288d \ + --hash=sha256:5f4ef9b8cc207d93978b1a58f055179686b9f30a5e28041872db97a4a1c49b96 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +pyjwt==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt new file mode 100644 index 0000000..c7e0d39 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 \ No newline at end of file diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements.txt b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements.txt new file mode 100644 index 0000000..50850c0 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.0 \ + --hash=sha256:820e1f3e21f90d36063239c6cb7ca9a6bb644cb120a6b1ead3081cafdf6ceaf8 \ + --hash=sha256:92ccea6c6ac7724d186cb73422d1ad8f525202dce2bdc17f35c695948fadf222 +azure-mgmt-storage==11.1.0 \ + --hash=sha256:62a6a8c1c359026ec560856da25221b66b6f1e0a84763a04e863c6e911bc1a5e \ + --hash=sha256:ef23587c1b6dc0866ebf0e91e83ba05d7f7e4fea7951b704781b9cd9f5f27f1c diff --git a/remediation_worker/jobs/azure_vm_close_port_22/README.md b/remediation_worker/jobs/azure_vm_close_port_22/README.md index 1333f0f..8521103 100644 --- a/remediation_worker/jobs/azure_vm_close_port_22/README.md +++ b/remediation_worker/jobs/azure_vm_close_port_22/README.md @@ -56,13 +56,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/ec2_close_port_22/README.md b/remediation_worker/jobs/ec2_close_port_22/README.md index 585bc1d..bdc9436 100644 --- a/remediation_worker/jobs/ec2_close_port_22/README.md +++ b/remediation_worker/jobs/ec2_close_port_22/README.md @@ -32,13 +32,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/ec2_close_port_3389/README.md b/remediation_worker/jobs/ec2_close_port_3389/README.md index 8ff488b..130f5f6 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/README.md +++ b/remediation_worker/jobs/ec2_close_port_3389/README.md @@ -32,13 +32,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/rds_backup_retention_30_days/README.md b/remediation_worker/jobs/rds_backup_retention_30_days/README.md index 503909b..f013160 100644 --- a/remediation_worker/jobs/rds_backup_retention_30_days/README.md +++ b/remediation_worker/jobs/rds_backup_retention_30_days/README.md @@ -26,6 +26,21 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + + ## Contributing The Secure State team welcomes welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. @@ -34,13 +49,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/s3_enable_access_logging/README.md b/remediation_worker/jobs/s3_enable_access_logging/README.md index f3a03cc..f8f32b1 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/README.md +++ b/remediation_worker/jobs/s3_enable_access_logging/README.md @@ -47,13 +47,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/s3_enable_default_encryption/README.md b/remediation_worker/jobs/s3_enable_default_encryption/README.md index 8f04f6f..e8165db 100644 --- a/remediation_worker/jobs/s3_enable_default_encryption/README.md +++ b/remediation_worker/jobs/s3_enable_default_encryption/README.md @@ -47,13 +47,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/s3_list_buckets/README.md b/remediation_worker/jobs/s3_list_buckets/README.md index b005e42..6751699 100644 --- a/remediation_worker/jobs/s3_list_buckets/README.md +++ b/remediation_worker/jobs/s3_list_buckets/README.md @@ -47,13 +47,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/s3_remove_public_access/README.md b/remediation_worker/jobs/s3_remove_public_access/README.md index b3bc697..602599a 100644 --- a/remediation_worker/jobs/s3_remove_public_access/README.md +++ b/remediation_worker/jobs/s3_remove_public_access/README.md @@ -47,13 +47,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/s3_remove_public_admin_acl/README.md b/remediation_worker/jobs/s3_remove_public_admin_acl/README.md index a854223..3e46299 100644 --- a/remediation_worker/jobs/s3_remove_public_admin_acl/README.md +++ b/remediation_worker/jobs/s3_remove_public_admin_acl/README.md @@ -47,13 +47,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/security_group_close_port_22/README.md b/remediation_worker/jobs/security_group_close_port_22/README.md index 7d9f0c5..671f0bf 100644 --- a/remediation_worker/jobs/security_group_close_port_22/README.md +++ b/remediation_worker/jobs/security_group_close_port_22/README.md @@ -24,6 +24,20 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + ## Contributing The Secure State team welcomes welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. @@ -32,13 +46,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/security_group_close_port_3389/README.md b/remediation_worker/jobs/security_group_close_port_3389/README.md index a07df49..41d3865 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/README.md +++ b/remediation_worker/jobs/security_group_close_port_3389/README.md @@ -24,6 +24,20 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + ## Contributing The Secure State team welcomes welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. @@ -32,13 +46,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/remediation_worker/jobs/security_group_close_port_5432/README.md b/remediation_worker/jobs/security_group_close_port_5432/README.md index 9eb5c02..41c68af 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/README.md +++ b/remediation_worker/jobs/security_group_close_port_5432/README.md @@ -24,6 +24,20 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest ``` +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + ## Contributing The Secure State team welcomes welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. @@ -32,13 +46,13 @@ For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING. ## Versioning -We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags). +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). ## Authors * **VMware Secure State** - *Initial work* -See also the list of [contributors](https://github.com/your/project/contributors) who participated in this project. +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who participated in this project. ## License diff --git a/test/unit/test_azure_blob_remove_public_access.py b/test/unit/test_azure_blob_remove_public_access.py new file mode 100644 index 0000000..1c40787 --- /dev/null +++ b/test/unit/test_azure_blob_remove_public_access.py @@ -0,0 +1,68 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock + +from remediation_worker.jobs.azure_blob_remove_public_access.azure_blob_remove_public_access import ( + StorageBlobRemovePublicAccess, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c6cc5e103dcc90f363146cd", + "Service": "Storage", + "FindingInfo": { + "FindingId": "d0431afd-b82e-4021-8aa6-ba3cf5c60ef7", + "ObjectId": "storage_account_name.default.container_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Storage.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.BlobContainer.storage_account_name.default.container_name\\",\\"entityName\\":\\"storage_account_name.default.container_name\\",\\"entityType\\":\\"Azure.Storage.BlobContainer\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Storage\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestBlobRemovePublicAccess(object): + def test_parse_payload(self, valid_payload): + params = StorageBlobRemovePublicAccess().parse(valid_payload) + assert params["account_name"] == "storage_account_name" + assert params["container_name"] == "container_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + action = StorageBlobRemovePublicAccess() + assert ( + action.remediate(client, "resource_group", "account_name", "container_name") + == 0 + ) + assert client.blob_containers.update.call_count == 1 + + call_args = client.blob_containers.update.call_args + updated_container = call_args[1]["blob_container"] + assert updated_container.public_access == "None" + + def test_remediate_with_exception(self): + client = Mock() + client.blob_containers.update.side_effect = Exception + action = StorageBlobRemovePublicAccess() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/test/unit/test_azure_network_security_group_close_port_3389.py b/test/unit/test_azure_network_security_group_close_port_3389.py new file mode 100644 index 0000000..6ed50ad --- /dev/null +++ b/test/unit/test_azure_network_security_group_close_port_3389.py @@ -0,0 +1,97 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from azure.mgmt.network.models import NetworkSecurityGroup +from azure.mgmt.network.models import SecurityRule +from mock import Mock + +from remediation_worker.jobs.azure_network_security_group_close_port_3389.azure_network_security_group_close_port_3389 \ + import ( + NetworkSecurityGroupClosePort3389, +) + + +@pytest.fixture +def valid_payload1(): + return """ +{ + "notificationInfo": { + "RuleId": "5c6cc5d903dcc90f363146b6", + "Service": "Network", + "FindingInfo": { + "FindingId": "e1606076-d55c-42c5-9ca7-93e933b1e672", + "ObjectId": "security_group_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestNetworkSecurityGroupClosePort22(object): + def test_parse_payload(self, valid_payload1): + params = NetworkSecurityGroupClosePort3389().parse(valid_payload1) + assert params["security_group_name"] == "security_group_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + client.network_security_groups.get.return_value = NetworkSecurityGroup( + id="nsg", + security_rules=[ + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_ranges=["22", "3389"], + ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_ranges=["20-30", "3380-3390"], + ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_range="3389", + ), + ], + ) + + action = NetworkSecurityGroupClosePort3389() + assert action.remediate(client, "security_group_name", "resource_group") == 0 + assert client.network_security_groups.create_or_update.call_count == 1 + + call_args = client.network_security_groups.create_or_update.call_args + updated_sg = call_args.args[2] + security_rules = updated_sg.security_rules + assert len(security_rules) == 2 + assert security_rules[0].destination_port_ranges == ["22"] + assert security_rules[1].destination_port_ranges == ["20-30", "3380-3388", "3390"] + + def test_remediate_with_exception(self): + client = Mock() + client.network_security_groups.create_or_update.side_effect = Exception + action = NetworkSecurityGroupClosePort3389() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/test/unit/test_azure_storage_account_allow_https_traffic_only.py b/test/unit/test_azure_storage_account_allow_https_traffic_only.py new file mode 100644 index 0000000..ba677a6 --- /dev/null +++ b/test/unit/test_azure_storage_account_allow_https_traffic_only.py @@ -0,0 +1,64 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock + +from remediation_worker.jobs.azure_storage_account_allow_https_traffic_only.azure_storage_account_allow_https_traffic_only import ( + StorageAccountAllowHttpsTrafficOnly, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c8c269a7a550e1fb6560cdb", + "Service": "Storage", + "FindingInfo": { + "FindingId": "e1606076-d55c-42c5-9ca7-93e933b1e672", + "ObjectId": "storage_account_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Storage.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.StorageAccount.testingresourcename\\",\\"entityName\\":\\"testingresourcename\\",\\"entityType\\":\\"Azure.Storage.StorageAccount\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Storage\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestStorageAccountAllowHttpsTrafficOnly(object): + def test_parse_payload(self, valid_payload): + params = StorageAccountAllowHttpsTrafficOnly().parse(valid_payload) + assert params["account_name"] == "storage_account_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + action = StorageAccountAllowHttpsTrafficOnly() + assert action.remediate(client, "security_group_name", "resource_group") == 0 + assert client.storage_accounts.update.call_count == 1 + + call_args = client.storage_accounts.update.call_args + update_param = call_args[1]["parameters"] + assert update_param.enable_https_traffic_only == True + + def test_remediate_with_exception(self): + client = Mock() + client.network_security_groups.create_or_update.side_effect = Exception + action = StorageAccountAllowHttpsTrafficOnly() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index f0cbeae..45dcf7e 100644 --- a/tox.ini +++ b/tox.ini @@ -12,8 +12,11 @@ envlist = unit-security-group-close-port-3389 unit-rds-backup-retention-30-days unit-security-group-close-port-22 - azure_network_security_group_close_port_22 + unit-azure-network-security-group-close-port-22 + unit-azure-network-security-group-close-port-3389 unit-azure-vm-close-port-22 + unit-azure-blob-remove-public-access + unit-azure-storage-allow-only-https [testenv] passenv = @@ -99,8 +102,26 @@ changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_network_security_group_close_port_22.py deps = -r remediation_worker/jobs/azure_network_security_group_close_port_22/requirements-dev.txt +[testenv:unit-azure-network-security-group-close-port-3389] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_network_security_group_close_port_3389.py +deps = -r remediation_worker/jobs/azure_network_security_group_close_port_3389/requirements-dev.txt + [testenv:unit-azure-vm-close-port-22] description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_vm_close_port_22.py deps = -r remediation_worker/jobs/azure_vm_close_port_22/requirements-dev.txt + +[testenv:unit-azure-blob-remove-public-access] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_blob_remove_public_access.py +deps = -r remediation_worker/jobs/azure_blob_remove_public_access/requirements-dev.txt + +[testenv:unit-azure-storage-allow-only-https] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_storage_account_allow_https_traffic_only.py +deps = -r remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt From ac7db4e248d247ddb046802bc013a7386ffa2729 Mon Sep 17 00:00:00 2001 From: Zuber Date: Tue, 15 Sep 2020 13:37:47 -0700 Subject: [PATCH 08/34] fix the parameters passed for remediation (#13) Co-authored-by: Mohammad Zuber Khan --- ...azure_storage_account_allow_https_traffic_only.py | 12 +++++------- .../azure_vm_close_port_22/azure_vm_close_port_22.py | 5 +---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py index 36d4c11..1ece258 100644 --- a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py @@ -44,13 +44,13 @@ def parse(self, payload): :raises: KeyError, JSONDecodeError """ remediation_entry = json.loads(payload) - logging.info("unparsed params") - logging.info(f" {remediation_entry}") account_name = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] - object_chain = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectChain"] + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] object_chain_dict = json.loads(object_chain) subscription_id = object_chain_dict["cloudAccountId"] @@ -93,9 +93,7 @@ def remediate(self, client, resource_group_name, account_name): # Allow only https traffic for the storage account logging.info("Enabling HTTPS only traffic for storage account") try: - logging.info( - " executing client.storage_accounts.update" - ) + logging.info(" executing client.storage_accounts.update") logging.info(f" resource_group_name={resource_group_name}") logging.info(f" account_name={account_name}") client.storage_accounts.update( @@ -126,7 +124,7 @@ def run(self, args): client = StorageManagementClient(credentials, params["subscription_id"]) return self.remediate( - client, params["resource_group_name"], params["account_name"], params["container_name"] + client, params["resource_group_name"], params["account_name"] ) diff --git a/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py index cc031ab..88f1d2e 100644 --- a/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py +++ b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py @@ -215,10 +215,7 @@ def run(self, args): compute_client = ComputeManagementClient(credentials, params["subscription_id"]) nw_client = NetworkManagementClient(credentials, params["subscription_id"]) return self.remediate( - compute_client, - nw_client, - params["resource_group_name"], - params["security_group_name"], + compute_client, nw_client, params["resource_group_name"], params["vm_name"], ) From 85691004694244fb9164196f817dcf71c6831302 Mon Sep 17 00:00:00 2001 From: Zuber Date: Wed, 16 Sep 2020 11:12:52 -0700 Subject: [PATCH 09/34] add check for existing permissions before adding new (#15) Co-authored-by: Mohammad Zuber Khan --- .../requirements-dev.txt | 3 + .../s3_enable_access_logging.py | 40 ++++++++++- test/unit/test_s3_enable_access_logging.py | 72 +++++++++++++++++++ 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/remediation_worker/jobs/s3_enable_access_logging/requirements-dev.txt b/remediation_worker/jobs/s3_enable_access_logging/requirements-dev.txt index 9412e93..cf03a93 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/requirements-dev.txt +++ b/remediation_worker/jobs/s3_enable_access_logging/requirements-dev.txt @@ -1,6 +1,9 @@ -r requirements.txt -c constraints.txt +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 pytest==6.0.1 \ --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py index 875fbaa..dd57755 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py +++ b/remediation_worker/jobs/s3_enable_access_logging/s3_enable_access_logging.py @@ -75,10 +75,44 @@ def parse(self, payload): logging.info(return_dict) return return_dict + def check_log_delivery_permissions(self, acl): + write_grant = False + read_acp_grant = False + + grants = acl.get("Grants") + + if grants is None: + return write_grant, read_acp_grant + + for grant in grants: + grantee = grant.get("Grantee") + if grantee is None: + continue + grantee_type = grantee.get("Type") + grantee_uri = grantee.get("URI") + if grantee_type is None or grantee_uri is None: + continue + if ( + grantee_type == "Group" + and grantee_uri == "http://acs.amazonaws.com/groups/s3/LogDelivery" + ): + if grant["Permission"] == "WRITE": + write_grant = True + elif grant["Permission"] == "READ_ACP": + read_acp_grant = True + + return write_grant, read_acp_grant + def grant_log_delivery_permissions(self, client, bucket_name): # Give the group log-delievery WRITE and READ_ACP permisions to the # target bucket acl = client.get_bucket_acl(Bucket=bucket_name) + + # check if the required permissions are already granted, and if present return + write_granted, read_acp_granted = self.check_log_delivery_permissions(acl) + if write_granted and read_acp_granted: + return + write_grant = { "Grantee": { "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", @@ -96,8 +130,10 @@ def grant_log_delivery_permissions(self, client, bucket_name): del acl["ResponseMetadata"] modified_acl = copy.deepcopy(acl) - modified_acl["Grants"].append(write_grant) - modified_acl["Grants"].append(read_acp_grant) + if not write_granted: + modified_acl["Grants"].append(write_grant) + if not read_acp_granted: + modified_acl["Grants"].append(read_acp_grant) client.put_bucket_acl(Bucket=bucket_name, AccessControlPolicy=modified_acl) def ensure_log_target_bucket(self, client, target_bucket, region): diff --git a/test/unit/test_s3_enable_access_logging.py b/test/unit/test_s3_enable_access_logging.py index 711d678..9918c84 100644 --- a/test/unit/test_s3_enable_access_logging.py +++ b/test/unit/test_s3_enable_access_logging.py @@ -15,6 +15,7 @@ import json import pytest +from mock import Mock from botocore.exceptions import ClientError from remediation_worker.jobs.s3_enable_access_logging.s3_enable_access_logging import ( @@ -242,3 +243,74 @@ def put_bucket_logging(self, **kwargs): def test_dont_log_to_self(self, self_payload): with pytest.raises(SelfRemediationError): assert S3EnableAccessLogging().run([None, self_payload]) + + def test_check_log_delivery(self): + acl = { + "Grants": [ + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", + }, + "Permission": "WRITE", + } + ] + } + + action = S3EnableAccessLogging() + write_enabled, read_acp_enabled = action.check_log_delivery_permissions(acl) + assert write_enabled + assert not read_acp_enabled + + def test_grant_log_delivery_permissions(self): + client = Mock() + client.get_bucket_acl.return_value = { + "ResponseMetadata": { + "RequestId": "6B0F579EDDCCAB3C", + "HostId": "9Csk0PXuRLyPhcKimBPbhfEmwQywAXPiWVWdpZPV+rjwVZO1DJMEKD/M65RJL+GguB3UMhOmpAQ=", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amz-id-2": "9Csk0PXuRLyPhcKimBPbhfEmwQywAXPiWVWdpZPV+rjwVZO1DJMEKD/M65RJL+GguB3UMhOmpAQ=", + "x-amz-request-id": "6B0F579EDDCCAB3C", + "date": "Wed, 16 Sep 2020 16:57:36 GMT", + }, + "RetryAttempts": 0, + }, + "Owner": { + "DisplayName": "awsmasteremail", + "ID": "b101f924005dbb04273644ca983ef2ea93d43ad46757f21f65c40d48d75368c3", + }, + "Grants": [ + { + "Grantee": { + "DisplayName": "awsmasteremail", + "ID": "b101f924005dbb04273644ca983ef2ea93d43ad46757f21f65c40d48d75368c3", + "Type": "CanonicalUser", + }, + "Permission": "FULL_CONTROL", + }, + { + "Grantee": { + "Type": "Group", + "URI": "http://acs.amazonaws.com/groups/s3/LogDelivery", + }, + "Permission": "READ_ACP", + }, + ], + } + + bucket_name = "my_bucket" + action = S3EnableAccessLogging() + action.grant_log_delivery_permissions(client, bucket_name) + assert client.put_bucket_acl.call_count == 1 + call_args = client.put_bucket_acl.call_args.kwargs + + assert len(call_args) == 2 + assert call_args.get("Bucket") == bucket_name + + acp = call_args.get("AccessControlPolicy") + assert acp is not None + assert len(acp["Grants"]) >= 2 + write_granted, read_granted = action.check_log_delivery_permissions(acp) + assert write_granted + assert read_granted From 1dbe5c170e27d47489b11ed9e8d2ede887df75dd Mon Sep 17 00:00:00 2001 From: Zuber Date: Fri, 18 Sep 2020 10:03:29 -0700 Subject: [PATCH 10/34] Fix ports range for network security groups (#19) * handle the case when the security rule port is a range * Add minimum permissions for each remediation jobs * add link to built in roles Co-authored-by: Mohammad Zuber Khan --- .../azure_blob_remove_public_access/README.md | 8 +++++-- .../minimum_permissions.json | 19 +++++++++++++++++ .../README.md | 10 ++++++--- ...re_network_security_group_close_port_22.py | 10 ++++++++- .../minimum_permissions.json | 19 +++++++++++++++++ .../README.md | 11 +++++++--- ..._network_security_group_close_port_3389.py | 10 ++++++++- .../minimum_permissions.json | 19 +++++++++++++++++ .../README.md | 8 +++++-- .../minimum_permissions.json | 19 +++++++++++++++++ .../jobs/azure_vm_close_port_22/README.md | 12 ++++++++--- .../minimum_permissions.json | 21 +++++++++++++++++++ ...re_network_security_group_close_port_22.py | 19 ++++++++++++----- ..._network_security_group_close_port_3389.py | 19 ++++++++++++----- 14 files changed, 179 insertions(+), 25 deletions(-) create mode 100644 remediation_worker/jobs/azure_blob_remove_public_access/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_22/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_network_security_group_close_port_3389/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_vm_close_port_22/minimum_permissions.json diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/README.md b/remediation_worker/jobs/azure_blob_remove_public_access/README.md index 1289804..1eee4c2 100644 --- a/remediation_worker/jobs/azure_blob_remove_public_access/README.md +++ b/remediation_worker/jobs/azure_blob_remove_public_access/README.md @@ -14,10 +14,14 @@ Public read access is enabled for blob storage ### Prerequisites -The provided Azure service principal must have permissions to make changes to the storage account +The provided Azure service principal must have the following permissions: +`Microsoft.Storage/storageAccounts/blobServices/containers/read` +`Microsoft.Storage/storageAccounts/blobServices/containers/write` -Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#storage-account-contributor): +A sample role with requisite permissions can be found [here](minimum_permissions.json) +More information about already builtin roles and permissions can be found +[here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) ### Running the script diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/minimum_permissions.json b/remediation_worker/jobs/azure_blob_remove_public_access/minimum_permissions.json new file mode 100644 index 0000000..8c203f8 --- /dev/null +++ b/remediation_worker/jobs/azure_blob_remove_public_access/minimum_permissions.json @@ -0,0 +1,19 @@ +{ + "properties": { + "roleName": "remediate_blob_public_access", + "description": "This role has required permissions to make changes to the blob containers", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Storage/storageAccounts/blobServices/containers/read", + "Microsoft.Storage/storageAccounts/blobServices/containers/write" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } +} \ No newline at end of file diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md index 0e0e781..50a7223 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/README.md @@ -14,10 +14,14 @@ The security group allows access to SSH port (22) ### Prerequisites -The provided Azure service principal must have permissions to make changes to the network security groups and security -rules. -Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-network-security-group#permissions): +The provided Azure service principal must have the following permissions: +`Microsoft.Network/networkSecurityGroups/read` +`Microsoft.Network/networkSecurityGroups/write` +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found +[here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) ### Running the script diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py index 9ebf28a..f4c98cc 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py @@ -105,7 +105,15 @@ def remediate(self, client, resource_group_name, security_group_name): ): continue if rule.destination_port_range is not None: - if int(rule.destination_port_range) == port: + port_range = rule.destination_port_range + if "-" in port_range: + new_ranges = self._find_and_remove_port([port_range], port) + if len(new_ranges) == 1: + rule.destination_port_range = new_ranges[0] + else: + rule.destination_port_range = None + rule.destination_port_ranges = new_ranges + elif int(rule.destination_port_range) == port: security_rules.remove(rule) else: port_ranges = rule.destination_port_ranges diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/minimum_permissions.json b/remediation_worker/jobs/azure_network_security_group_close_port_22/minimum_permissions.json new file mode 100644 index 0000000..0c2654a --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/minimum_permissions.json @@ -0,0 +1,19 @@ +{ + "properties": { + "roleName": "remediate_network_security_group", + "description": "This role has required permissions to make changes in the network security groups", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Network/networkSecurityGroups/read", + "Microsoft.Network/networkSecurityGroups/write" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } +} \ No newline at end of file diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/README.md b/remediation_worker/jobs/azure_network_security_group_close_port_3389/README.md index 566fd13..5a0eb2b 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_3389/README.md +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/README.md @@ -14,9 +14,14 @@ The security group allows access to Remote Desktop port (3389) ### Prerequisites -The provided Azure service principal must have permissions to make changes to the network security groups and security -rules. -Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-network-security-group#permissions): +The provided Azure service principal must have the following permissions: +`Microsoft.Network/networkSecurityGroups/read` +`Microsoft.Network/networkSecurityGroups/write` + +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found +[here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) ### Running the script diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py index 2e64a02..bd0c7ea 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py @@ -102,7 +102,15 @@ def remediate(self, client, resource_group_name, security_group_name): ): continue if rule.destination_port_range is not None: - if int(rule.destination_port_range) == port: + port_range = rule.destination_port_range + if "-" in port_range: + new_ranges = self._find_and_remove_port([port_range], port) + if len(new_ranges) == 1: + rule.destination_port_range = new_ranges[0] + else: + rule.destination_port_range = None + rule.destination_port_ranges = new_ranges + elif int(rule.destination_port_range) == port: security_rules.remove(rule) else: port_ranges = rule.destination_port_ranges diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/minimum_permissions.json b/remediation_worker/jobs/azure_network_security_group_close_port_3389/minimum_permissions.json new file mode 100644 index 0000000..6d33c22 --- /dev/null +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/minimum_permissions.json @@ -0,0 +1,19 @@ +{ + "properties": { + "roleName": "remediate_network_security_group", + "description": "This role has required permission to make changes in the network security groups", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Network/networkSecurityGroups/read", + "Microsoft.Network/networkSecurityGroups/write" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } +} \ No newline at end of file diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/README.md b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/README.md index 018f901..f3d6281 100644 --- a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/README.md +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/README.md @@ -15,10 +15,14 @@ Secure connections are not enabled for storage transactions ### Prerequisites -The provided Azure service principal must have permissions to make changes to the storage accounts. +The provided Azure service principal must have the following permissions: +`Microsoft.Storage/storageAccounts/read` +`Microsoft.Storage/storageAccounts/write` -Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#storage-account-contributor): +A sample role with requisite permissions can be found [here](minimum_permissions.json) +More information about already builtin roles and permissions can be found +[here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) ### Running the script diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/minimum_permissions.json b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/minimum_permissions.json new file mode 100644 index 0000000..56ff5e5 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/minimum_permissions.json @@ -0,0 +1,19 @@ +{ + "properties": { + "roleName": "remediate_storage_account", + "description": "This role has required permissions to make changes to the storage account", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Storage/storageAccounts/write", + "Microsoft.Storage/storageAccounts/read" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } +} \ No newline at end of file diff --git a/remediation_worker/jobs/azure_vm_close_port_22/README.md b/remediation_worker/jobs/azure_vm_close_port_22/README.md index 8521103..edc9aee 100644 --- a/remediation_worker/jobs/azure_vm_close_port_22/README.md +++ b/remediation_worker/jobs/azure_vm_close_port_22/README.md @@ -14,10 +14,16 @@ A virtual machine is publicly accessible to the internet via port 22 ### Prerequisites -The provided Azure service principal must have permissions to make changes to the network security groups and security -rules. -Details for the permissions can be found [here](https://docs.microsoft.com/en-us/azure/virtual-network/manage-network-security-group#permissions): +The provided Azure service principal must have the following permissions: +`Microsoft.Compute/virtualMachines/read` +`Microsoft.Network/networkInterfaces/read` +`Microsoft.Network/networkSecurityGroups/read` +`Microsoft.Network/networkSecurityGroups/write` +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found +[here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) ### Running the script diff --git a/remediation_worker/jobs/azure_vm_close_port_22/minimum_permissions.json b/remediation_worker/jobs/azure_vm_close_port_22/minimum_permissions.json new file mode 100644 index 0000000..ab1625d --- /dev/null +++ b/remediation_worker/jobs/azure_vm_close_port_22/minimum_permissions.json @@ -0,0 +1,21 @@ +{ + "properties": { + "roleName": "remediate_vm", + "description": "This role has required permissions Role with permissions to remediate VM violations for port 22", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Compute/virtualMachines/read", + "Microsoft.Network/networkInterfaces/read", + "Microsoft.Network/networkSecurityGroups/read", + "Microsoft.Network/networkSecurityGroups/write" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } +} \ No newline at end of file diff --git a/test/unit/test_azure_network_security_group_close_port_22.py b/test/unit/test_azure_network_security_group_close_port_22.py index 0c6e394..1e3ee5f 100644 --- a/test/unit/test_azure_network_security_group_close_port_22.py +++ b/test/unit/test_azure_network_security_group_close_port_22.py @@ -58,14 +58,14 @@ def test_remediate_success(self): access="Allow", direction="Inbound", source_address_prefix="*", - destination_port_ranges=["22", "3389"], + destination_port_ranges=["22-30", "3389"], ), SecurityRule( protocol="All", access="Allow", direction="Inbound", source_address_prefix="*", - destination_port_ranges=["20-30", "3389"], + destination_port_range="20-30", ), SecurityRule( protocol="All", @@ -74,6 +74,13 @@ def test_remediate_success(self): source_address_prefix="*", destination_port_range="22", ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_range="35", + ), ], ) @@ -84,9 +91,11 @@ def test_remediate_success(self): call_args = client.network_security_groups.create_or_update.call_args updated_sg = call_args.args[2] security_rules = updated_sg.security_rules - assert len(security_rules) == 2 - assert security_rules[0].destination_port_ranges == ["3389"] - assert security_rules[1].destination_port_ranges == ["20-21", "23-30", "3389"] + assert len(security_rules) == 3 + assert security_rules[0].destination_port_ranges == ["23-30","3389"] + assert security_rules[1].destination_port_ranges == ["20-21", "23-30"] + assert security_rules[1].destination_port_range is None + assert security_rules[2].destination_port_range == "35" def test_remediate_with_exception(self): client = Mock() diff --git a/test/unit/test_azure_network_security_group_close_port_3389.py b/test/unit/test_azure_network_security_group_close_port_3389.py index 6ed50ad..7e5de7e 100644 --- a/test/unit/test_azure_network_security_group_close_port_3389.py +++ b/test/unit/test_azure_network_security_group_close_port_3389.py @@ -59,14 +59,14 @@ def test_remediate_success(self): access="Allow", direction="Inbound", source_address_prefix="*", - destination_port_ranges=["22", "3389"], + destination_port_ranges=["22-30", "3380-3390"], ), SecurityRule( protocol="All", access="Allow", direction="Inbound", source_address_prefix="*", - destination_port_ranges=["20-30", "3380-3390"], + destination_port_range="3380-3395", ), SecurityRule( protocol="All", @@ -75,6 +75,13 @@ def test_remediate_success(self): source_address_prefix="*", destination_port_range="3389", ), + SecurityRule( + protocol="All", + access="Allow", + direction="Inbound", + source_address_prefix="*", + destination_port_range="35", + ), ], ) @@ -85,9 +92,11 @@ def test_remediate_success(self): call_args = client.network_security_groups.create_or_update.call_args updated_sg = call_args.args[2] security_rules = updated_sg.security_rules - assert len(security_rules) == 2 - assert security_rules[0].destination_port_ranges == ["22"] - assert security_rules[1].destination_port_ranges == ["20-30", "3380-3388", "3390"] + assert len(security_rules) == 3 + assert security_rules[0].destination_port_ranges == ["22-30", "3380-3388", "3390"] + assert security_rules[1].destination_port_ranges == ["3380-3388", "3390-3395"] + assert security_rules[1].destination_port_range is None + assert security_rules[2].destination_port_range == "35" def test_remediate_with_exception(self): client = Mock() From f615df2b5b122c1bef7eb5fad40cde572bec009b Mon Sep 17 00:00:00 2001 From: Zuber Date: Mon, 21 Sep 2020 11:29:48 -0700 Subject: [PATCH 11/34] update the remediation job payload (#21) Co-authored-by: Mohammad Zuber Khan --- README.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/README.md b/README.md index 9e09942..8393fcb 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,8 @@ The worker executes jobs in a fashion similar to running `python ./s3-remove-pub The finding payload is in the form: ```$json { - "cloudAccount": { - "provider": , - "roleArn": , - "subscriptionId": , - "applicationId": - }, "notificationInfo": { + "CloudAccountID" : , "RuleID": , "RuleName": , "RuleDisplayName": , From 125e00278ca6903a299a21954c4b27a20a85633b Mon Sep 17 00:00:00 2001 From: Zuber Date: Thu, 22 Oct 2020 09:47:58 -0700 Subject: [PATCH 12/34] PLA-20459: Add rule information for the remediation job (#22) * update the remediation job payload (#21) Co-authored-by: Mohammad Zuber Khan * PLA-20459: Add rule information for the remediation job * add another rule remediated by the job Co-authored-by: Mohammad Zuber Khan --- .../jobs/ec2_close_port_22/README.md | 8 ++++++++ .../jobs/ec2_close_port_3389/README.md | 8 ++++++++ .../rds_backup_retention_30_days/README.md | 8 ++++++++ .../jobs/s3_enable_access_logging/README.md | 8 ++++++++ .../s3_enable_default_encryption/README.md | 8 ++++++++ .../jobs/s3_remove_public_access/README.md | 18 ++++++++++++++++++ .../jobs/s3_remove_public_admin_acl/README.md | 8 ++++++++ .../security_group_close_port_22/README.md | 8 ++++++++ .../security_group_close_port_3389/README.md | 8 ++++++++ .../security_group_close_port_5432/README.md | 8 ++++++++ 10 files changed, 90 insertions(+) diff --git a/remediation_worker/jobs/ec2_close_port_22/README.md b/remediation_worker/jobs/ec2_close_port_22/README.md index bdc9436..54c0278 100644 --- a/remediation_worker/jobs/ec2_close_port_22/README.md +++ b/remediation_worker/jobs/ec2_close_port_22/README.md @@ -2,6 +2,14 @@ This job blocks public access to port 22 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +### Applicable Rule + +##### Rule ID: +5c8c26417a550e1fb6560c3f + +##### Rule Name: +An EC2 instance's SSH port (22) is accessible from the public Internet for any source address + ## Getting Started ### Prerequisites diff --git a/remediation_worker/jobs/ec2_close_port_3389/README.md b/remediation_worker/jobs/ec2_close_port_3389/README.md index 130f5f6..6245d6f 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/README.md +++ b/remediation_worker/jobs/ec2_close_port_3389/README.md @@ -2,6 +2,14 @@ This job blocks public access to port 3389 for both IPv4 and IPv6 for all security groups associated with an EC2 instance. +### Applicable Rule + +##### Rule ID: +5c8c26437a550e1fb6560c42 + +##### Rule Name: +An EC2 instance's Remote Desktop port (3389) is accessible from the public Internet for any source address + ## Getting Started ### Prerequisites diff --git a/remediation_worker/jobs/rds_backup_retention_30_days/README.md b/remediation_worker/jobs/rds_backup_retention_30_days/README.md index f013160..386a614 100644 --- a/remediation_worker/jobs/rds_backup_retention_30_days/README.md +++ b/remediation_worker/jobs/rds_backup_retention_30_days/README.md @@ -4,6 +4,14 @@ This job makes the RDS backup retention period 30 days. It first tries to modify the retention period of the DB instance. If that fails, it will try to set the retention period of the DB cluster that the instance belongs to. +### Applicable Rule + +##### Rule ID: +5c8c264a7a550e1fb6560c4d + +##### Rule Name: +The RDS backup retention period is less than 30 days + ## Getting Started ### Prerequisites diff --git a/remediation_worker/jobs/s3_enable_access_logging/README.md b/remediation_worker/jobs/s3_enable_access_logging/README.md index f8f32b1..6ecd058 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/README.md +++ b/remediation_worker/jobs/s3_enable_access_logging/README.md @@ -2,6 +2,14 @@ This job enables access logging for an S3 bucket. +### Applicable Rule + +##### Rule ID: +5c8c265e7a550e1fb6560c67 + +##### Rule Name: +S3 access logging is not enabled + ## Getting Started ### Prerequisites diff --git a/remediation_worker/jobs/s3_enable_default_encryption/README.md b/remediation_worker/jobs/s3_enable_default_encryption/README.md index e8165db..53adcfd 100644 --- a/remediation_worker/jobs/s3_enable_default_encryption/README.md +++ b/remediation_worker/jobs/s3_enable_default_encryption/README.md @@ -2,6 +2,14 @@ This job enables default encryption for an S3 bucket using AES256. +### Applicable Rule + +##### Rule ID: +1d187035-9fff-48b2-a7c3-ffc56a4da5e6 + +##### Rule Name: +An S3 bucket default encryption is not enabled + ## Getting Started ### Prerequisites diff --git a/remediation_worker/jobs/s3_remove_public_access/README.md b/remediation_worker/jobs/s3_remove_public_access/README.md index 602599a..6d98c45 100644 --- a/remediation_worker/jobs/s3_remove_public_access/README.md +++ b/remediation_worker/jobs/s3_remove_public_access/README.md @@ -2,6 +2,24 @@ This job blocks public access to an S3 bucket. +### Applicable Rule + +##### Rule ID: +* 5c8c26507a550e1fb6560c57 +* 5c8c26517a550e1fb6560c59 +* 5c8c26537a550e1fb6560c5a +* 5c8c26537a550e1fb6560c5b +* 5c8c26547a550e1fb6560c5c +* 5c8c26637a550e1fb6560c6b + +##### Rule Name: +* An S3 bucket is configured so that all users have unrestricted permissions +* Read access to an S3 bucket is unrestricted +* An S3 bucket is configured to allow all users to read permissions +* Write access to an S3 bucket is unrestricted +* An S3 bucket is configured to allow unrestricted access control changes +* The S3 bucket policy allows unrestricted get access + ## Getting Started ### Prerequisites diff --git a/remediation_worker/jobs/s3_remove_public_admin_acl/README.md b/remediation_worker/jobs/s3_remove_public_admin_acl/README.md index 3e46299..ee89274 100644 --- a/remediation_worker/jobs/s3_remove_public_admin_acl/README.md +++ b/remediation_worker/jobs/s3_remove_public_admin_acl/README.md @@ -2,6 +2,14 @@ This job will remove the public "write bucket permissions" permission. All other ACL permissions will be left alone. This means that if "AllUsers" have access to FULL_CONTROL, the permissions will be changed to allow "READ", "WRITE", and "READ_ACP" rather than "FULL_CONTROL" which includes "WRITE_ACP". +### Applicable Rule + +##### Rule ID: +5c8c26617a550e1fb6560c69 + +##### Rule Name: +The S3 bucket policy allows unrestricted access + ## Getting Started ### Prerequisites diff --git a/remediation_worker/jobs/security_group_close_port_22/README.md b/remediation_worker/jobs/security_group_close_port_22/README.md index 671f0bf..112feaa 100644 --- a/remediation_worker/jobs/security_group_close_port_22/README.md +++ b/remediation_worker/jobs/security_group_close_port_22/README.md @@ -2,6 +2,14 @@ This job blocks public access to port 22 for both IPv4 and IPv6. +### Applicable Rule + +##### Rule ID: +5c8c25ec7a550e1fb6560bbe + +##### Rule Name: +A security group's SSH port (22) is accessible through any source address + ## Getting Started ### Prerequisites diff --git a/remediation_worker/jobs/security_group_close_port_3389/README.md b/remediation_worker/jobs/security_group_close_port_3389/README.md index 41d3865..b431123 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/README.md +++ b/remediation_worker/jobs/security_group_close_port_3389/README.md @@ -2,6 +2,14 @@ This job blocks public access to port 3389 for both IPv4 and IPv6. +### Applicable Rule + +##### Rule ID: +5c8c25ef7a550e1fb6560bc4 + +##### Rule Name: +A security group's Remote Desktop port (3389) is accessible through any source address + ## Getting Started ### Prerequisites diff --git a/remediation_worker/jobs/security_group_close_port_5432/README.md b/remediation_worker/jobs/security_group_close_port_5432/README.md index 41c68af..f4682b9 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/README.md +++ b/remediation_worker/jobs/security_group_close_port_5432/README.md @@ -2,6 +2,14 @@ This job blocks public access to port 5432 for both IPv4 and IPv6. +### Applicable Rule + +##### Rule ID: +5c8c25f07a550e1fb6560bc6 + +##### Rule Name: +A security group's PostgreSQL Server port (5432) is accessible through any source address + ## Getting Started ### Prerequisites From a875459d0bdca88f8fd62b4464537591f6b99489 Mon Sep 17 00:00:00 2001 From: Vikramjeet Singh <58273802+vikramsinghvirdi@users.noreply.github.com> Date: Mon, 26 Oct 2020 12:20:05 -0700 Subject: [PATCH 13/34] Master -> Dev Merge (#23) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Release/v1.0.0 (#7) * PLA-17940 updating constraints and fixing s3_enable_access_logging to… (#3) * PLA-17940 updating constraints and fixing s3_enable_access_logging to not remediate the logging target bucket * PLA-17940 fixing copyright info * Updated readme to have link for reporting issues (#4) Co-authored-by: svikramjeet * S3 access logs permissions (#6) * Update minimum permissions for the job * Add more logs when permission is missing Co-authored-by: Mohammad Zuber Khan Co-authored-by: Paul Allen Co-authored-by: svikramjeet Co-authored-by: Zuber Co-authored-by: Mohammad Zuber Khan * Release/v1.1.0 (#17) * PLA-17940 updating constraints and fixing s3_enable_access_logging to… (#3) * PLA-17940 updating constraints and fixing s3_enable_access_logging to not remediate the logging target bucket * PLA-17940 fixing copyright info * Updated readme to have link for reporting issues (#4) Co-authored-by: svikramjeet * S3 access logs permissions (#6) * Update minimum permissions for the job * Add more logs when permission is missing Co-authored-by: Mohammad Zuber Khan * change the way cloudAccountId is parsed from Job Paramaters (#9) Co-authored-by: Mohammad Zuber Khan * PLA-16779: Add remediation job for azure security group port 22 (#10) * PLA-16779: Add remediation job for azure security group port 22 * add test dependencies * update README.md * add deployment info to the README * add rule information Co-authored-by: Mohammad Zuber Khan * Add remediation job for closing port 22 for VM (#11) * PLA-18743: Add remediation job for closing port 22 for VM * add tests for azure_vm_close_port_22 to tox * add deployment details Co-authored-by: Mohammad Zuber Khan * Add remediation jobs for storage and RDP violations (#12) * add remediation job for closing RDP access * Add remediation job for remove public access for blob * Add remediation job to allow only https traffic to storage account * update READMEs to fix broken links Co-authored-by: Mohammad Zuber Khan * fix the parameters passed for remediation (#13) Co-authored-by: Mohammad Zuber Khan * add check for existing permissions before adding new (#15) Co-authored-by: Mohammad Zuber Khan * Fix ports range for network security groups (#19) (#20) * handle the case when the security rule port is a range * Add minimum permissions for each remediation jobs * add link to built in roles Co-authored-by: Mohammad Zuber Khan Co-authored-by: Zuber Co-authored-by: Mohammad Zuber Khan Co-authored-by: Paul Allen Co-authored-by: svikramjeet Co-authored-by: Zuber Co-authored-by: Mohammad Zuber Khan Co-authored-by: Paul Allen Co-authored-by: svikramjeet Co-authored-by: Zuber Co-authored-by: Mohammad Zuber Khan From 06dd6fae9e5fff98ee60d0e52cda90355c6a07e8 Mon Sep 17 00:00:00 2001 From: Vikramjeet Singh <58273802+vikramsinghvirdi@users.noreply.github.com> Date: Mon, 26 Oct 2020 12:29:15 -0700 Subject: [PATCH 14/34] Revert "Master -> Dev Merge (#23)" (#24) This reverts commit a875459d0bdca88f8fd62b4464537591f6b99489. From 4eebd8f8ac8fa9c137b7b9df9daedf99d92ff3d4 Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Tue, 17 Nov 2020 21:25:47 +0530 Subject: [PATCH 15/34] Azure security center enable ddos protection (#27) * Azure security center enable ddos protection remediation script * Removed .DS_Store file * Made changes in README.md * Addressed the review comments * Pre-commit changes added * Made changes in README.md * Added the unit test in tox.ini * fix requirements-dev.txt for tox run Co-authored-by: Mohammad Zuber Khan Co-authored-by: Shrutika Kulkarni --- .../README.md | 63 +++++++ .../__init__.py | 0 ..._security_center_enable_ddos_protection.py | 154 ++++++++++++++++++ .../constraints.txt | 130 +++++++++++++++ .../minimum_permissions.json | 21 +++ .../requirements-dev.txt | 36 ++++ .../requirements.txt | 6 + ..._security_center_enable_ddos_protection.py | 84 ++++++++++ tox.ini | 7 + 9 files changed, 501 insertions(+) create mode 100644 remediation_worker/jobs/azure_security_center_enable_ddos_protection/README.md create mode 100644 remediation_worker/jobs/azure_security_center_enable_ddos_protection/__init__.py create mode 100644 remediation_worker/jobs/azure_security_center_enable_ddos_protection/azure_security_center_enable_ddos_protection.py create mode 100644 remediation_worker/jobs/azure_security_center_enable_ddos_protection/constraints.txt create mode 100644 remediation_worker/jobs/azure_security_center_enable_ddos_protection/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements.txt create mode 100644 test/unit/test_azure_security_center_enable_ddos_protection.py diff --git a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/README.md b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/README.md new file mode 100644 index 0000000..26d5e07 --- /dev/null +++ b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/README.md @@ -0,0 +1,63 @@ +# Enable DDoS protection for Virtual Network + +This job enables DDoS protection for a virtual network by listing all the available DDoS protection plans and assigning any one to the virtual network. + +### Applicable Rule + +##### Rule ID: +3abf3147-ea53-4302-b237-caab4d764c77 + +##### Rule Name: +DDoS Protection Standard should be enabled + +## Getting Started +### Prerequisites +The provided Azure service principal must have the following permissions: +`Microsoft.Network/virtualNetworks/read` +`Microsoft.Network/virtualNetworks/write` +`Microsoft.Network/ddosProtectionPlans/read` +`Microsoft.Network/ddosProtectionPlans/join/action` + +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) + +### Running the script +You may run this script using following commands: + +```shell script + pip install -r requirements.txt + python3 azure_security_center_enable_ddos_protection.py +``` +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: + +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: + ```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python + ``` +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). + +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). +## Versioning +We use SemVer for versioning. For the versions available, see the tags on this repository. + +## Authors +* **VMware Secure State** - *Initial work* +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/graphs/contributors) who participated in this project. + +## License +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/__init__.py b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/azure_security_center_enable_ddos_protection.py b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/azure_security_center_enable_ddos_protection.py new file mode 100644 index 0000000..54fe07e --- /dev/null +++ b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/azure_security_center_enable_ddos_protection.py @@ -0,0 +1,154 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging + +from azure.mgmt.network import NetworkManagementClient +from azure.mgmt.network.models import DdosProtectionPlanListResult, SubResource +from azure.identity import ClientSecretCredential +from azure.core.paging import ItemPaged +from typing import List + +logging.basicConfig(level=logging.INFO) + + +class VirtualNetworkEnableDdosProtection(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + + object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" virtual_network_name: {object_id}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + return { + "resource_group_name": resource_group_name, + "virtual_network_name": object_id, + "subscription_id": subscription_id, + "region": region, + } + + def remediate( + self, client, resource_group_name, virtual_network_name, subscription_id + ): + """Enable DDos protection for a Virtual Network + :param client: Instance of the Azure NetworkManagementClient. + :param resource_group_name: The name of the resource group to which the virtual network belongs. + :param virtual_network_name: The name of the Virtual Network. + :param subscription_id: The Subscription ID of the user. + :type resource_group_name: str. + :type virtual_network_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + ddos_plans_paged: ItemPaged[ + DdosProtectionPlanListResult + ] = client.ddos_protection_plans.list() + ddos_plans_list: List[dict] = list(ddos_plans_paged) + number_of_ddos: int = len(ddos_plans_list) + + if number_of_ddos > 0: + resource_id = ddos_plans_list[0].id + logging.info( + f" Resource ID of Azure DDos Protection Plan={resource_id}" + ) + else: + logging.error( + f" Azure cloud user with subscription ID: {subscription_id} has no active Azure DDos protection plan available" + ) + return 1 + + virtual_network = client.virtual_networks.get( + resource_group_name=resource_group_name, + virtual_network_name=virtual_network_name, + ) + + # Enabling DDoS Protection + virtual_network.enable_ddos_protection = True + + updated_SubResource = SubResource(id=resource_id) + + virtual_network.ddos_protection_plan = updated_SubResource + + logging.info("Enabling DDos protection for Virtual Network") + try: + logging.info(" executing client.virtual_networks.begin_create_or_update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" virtual_network_name={virtual_network_name}") + + client.virtual_networks.begin_create_or_update( + resource_group_name=resource_group_name, + virtual_network_name=virtual_network_name, + parameters=virtual_network, + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + return 0 + + def run(self, args): + """Run the remediation job. + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ClientSecretCredential( + client_id=os.environ.get("AZURE_CLIENT_ID"), + client_secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant_id=os.environ.get("AZURE_TENANT_ID"), + ) + + client = NetworkManagementClient(credentials, params["subscription_id"]) + return self.remediate( + client, + params["resource_group_name"], + params["virtual_network_name"], + params["subscription_id"], + ) + + +if __name__ == "__main__": + sys.exit(VirtualNetworkEnableDdosProtection().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/constraints.txt b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/constraints.txt new file mode 100644 index 0000000..4ef01d4 --- /dev/null +++ b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/constraints.txt @@ -0,0 +1,130 @@ +adal==1.2.5 \ + --hash=sha256:7492aff8f0ba7dd4e1c477303295c645141540fff34c3ca6de0a0b0e6c1c122a \ + --hash=sha256:8003ba03ef04170195b3eddda8a5ab43649ef2c5f0287023d515affb1ccfcfc3 +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.2 \ + --hash=sha256:621b53271f7988b766f8a7d7f7a2c44241e3d2c1d8db13e68089d6da6241748e \ + --hash=sha256:be23d411e19874f375c2ef0327c452be10b1e9a1023ac6afe334598f2920136b +azure-mgmt-core==1.2.1 \ + --hash=sha256:a3906fa77edfedfcc3229dc3b69489d5ed63b107c7eacbc50092e6cbfbfd83f0 \ + --hash=sha256:bd4503a2d81b86780f15936af2e4244c1345062f4c2422f0b377b56cb80d7796 +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-network==16.0.0 \ + --hash=sha256:6159a8c44590cc58841690c27c7d4acb0cd9ad0a1e5178c1d35e0f48e3c3c0e9 \ + --hash=sha256:c0e8358e9d530790dbf3efef6b31bce26e664de5096cbd84c62845067da815d1 +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.3 \ + --hash=sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d \ + --hash=sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b \ + --hash=sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4 \ + --hash=sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f \ + --hash=sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3 \ + --hash=sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579 \ + --hash=sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537 \ + --hash=sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e \ + --hash=sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05 \ + --hash=sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171 \ + --hash=sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca \ + --hash=sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522 \ + --hash=sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c \ + --hash=sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc \ + --hash=sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d \ + --hash=sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808 \ + --hash=sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828 \ + --hash=sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869 \ + --hash=sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d \ + --hash=sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9 \ + --hash=sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0 \ + --hash=sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc \ + --hash=sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15 \ + --hash=sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c \ + --hash=sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a \ + --hash=sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3 \ + --hash=sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1 \ + --hash=sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768 \ + --hash=sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d \ + --hash=sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b \ + --hash=sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e \ + --hash=sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d \ + --hash=sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730 \ + --hash=sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394 \ + --hash=sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1 \ + --hash=sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.2.1 \ + --hash=sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538 \ + --hash=sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f \ + --hash=sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77 \ + --hash=sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b \ + --hash=sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33 \ + --hash=sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e \ + --hash=sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb \ + --hash=sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e \ + --hash=sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7 \ + --hash=sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297 \ + --hash=sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d \ + --hash=sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7 \ + --hash=sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b \ + --hash=sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7 \ + --hash=sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4 \ + --hash=sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8 \ + --hash=sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b \ + --hash=sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851 \ + --hash=sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13 \ + --hash=sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b \ + --hash=sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3 \ + --hash=sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal==1.5.1 \ + --hash=sha256:7efb0256c96a7b2eadab49ce29ecdb91352a91440c12a40bed44303724b62fda \ + --hash=sha256:d84074a997e6fb2a47e22815dce376adcc8790838b6b1bf1fcea29378e2bf3eb +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msrest==0.6.19 \ + --hash=sha256:55f8c3940bc5dc609f8cf9fcd639444716cc212a943606756272e0d0017bbb5b \ + --hash=sha256:87aa64948c3ef3dbf6f6956d2240493e68d714e4621b92b65b3c4d5808297929 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +PyJWT==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +urllib3==1.25.11 \ + --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ + --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e diff --git a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/minimum_permissions.json b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/minimum_permissions.json new file mode 100644 index 0000000..783e9b2 --- /dev/null +++ b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/minimum_permissions.json @@ -0,0 +1,21 @@ +{ + "properties": { + "roleName": "remediate_enable_ddos_protection", + "description": "This role has required permissions to make changes to the virtual network", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Network/virtualNetworks/read", + "Microsoft.Network/virtualNetworks/write", + "Microsoft.Network/ddosProtectionPlans/read", + "Microsoft.Network/ddosProtectionPlans/join/action" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } +} diff --git a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt new file mode 100644 index 0000000..594637e --- /dev/null +++ b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt @@ -0,0 +1,36 @@ +-r requirements.txt +-c constraints.txt + +importlib-metadata==2.0.0 \ + --hash=sha256:77a540690e24b0305878c37ffd421785a6f7e53c8b5720d211b211de8d0e95da \ + --hash=sha256:cefa1a2f919b866c5beb7c9f7b0ebb4061f30a8a9bf16d609b000e2dfaceb9c3 +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.1.2 \ + --hash=sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe \ + --hash=sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +zipp==3.4.0 \ + --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ + --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb diff --git a/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements.txt b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements.txt new file mode 100644 index 0000000..842d033 --- /dev/null +++ b/remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-network==16.0.0 \ + --hash=sha256:6159a8c44590cc58841690c27c7d4acb0cd9ad0a1e5178c1d35e0f48e3c3c0e9 \ + --hash=sha256:c0e8358e9d530790dbf3efef6b31bce26e664de5096cbd84c62845067da815d1 diff --git a/test/unit/test_azure_security_center_enable_ddos_protection.py b/test/unit/test_azure_security_center_enable_ddos_protection.py new file mode 100644 index 0000000..8645f4e --- /dev/null +++ b/test/unit/test_azure_security_center_enable_ddos_protection.py @@ -0,0 +1,84 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock +from remediation_worker.jobs.azure_security_center_enable_ddos_protection.azure_security_center_enable_ddos_protection import ( + VirtualNetworkEnableDdosProtection, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "3abf3147-ea53-4302-b237-caab4d764c77", + "Service": "Network", + "FindingInfo": { + "FindingId": "d0431afd-b82e-4021-8aa6-ba3cf5c60ef7", + "ObjectId": "vnet_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Network.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.network.virtual_network_name\\",\\"entityName\\":\\"virtual_network_name\\",\\"entityType\\":\\"Azure.Network.virtualnetwork\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Network\\",\\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestEnableDdosProtection(object): + def test_parse_payload(self, valid_payload): + params = VirtualNetworkEnableDdosProtection().parse(valid_payload) + assert params["virtual_network_name"] == "vnet_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + action = VirtualNetworkEnableDdosProtection() + DdosProtectionPlanListResult = Mock() + ddos_plans_list = [] + ddos_plans_list.append(DdosProtectionPlanListResult) + client.ddos_protection_plans.list.return_value = ddos_plans_list + assert ( + action.remediate( + client, "resource_group", "virtual_network_name", "subscription_id" + ) + == 0 + ) + assert client.virtual_networks.begin_create_or_update.call_count == 1 + + call_args = client.virtual_networks.begin_create_or_update.call_args + updated_vnet = call_args[1]["parameters"] + assert updated_vnet.enable_ddos_protection is True + + def test_remediate_failure(self): + client = Mock() + action = VirtualNetworkEnableDdosProtection() + ddos_plans_list = [] + client.ddos_protection_plans.list.return_value = ddos_plans_list + assert ( + action.remediate( + client, "resource_group", "virtual_network_name", "subscription_id" + ) + == 1 + ) + + def test_remediate_with_exception(self): + client = Mock() + client.virtual_networks.begin_create_or_update.side_effect = Exception + action = VirtualNetworkEnableDdosProtection() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index 45dcf7e..a19d93c 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ envlist = unit-azure-vm-close-port-22 unit-azure-blob-remove-public-access unit-azure-storage-allow-only-https + unit-azure-security-center-enable-ddos-protection [testenv] passenv = @@ -125,3 +126,9 @@ description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_storage_account_allow_https_traffic_only.py deps = -r remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt + +[testenv:unit-azure-security-center-enable-ddos-protection] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_security_center_enable_ddos_protection.py +deps = -r remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt \ No newline at end of file From 78e69ecd7672e9ffffc5114d0aaf2427b639158f Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Wed, 18 Nov 2020 00:22:41 +0530 Subject: [PATCH 16/34] Azure Storage default network access deny remediation script (#30) * Azure Storage default network access deny remediation script * .DS_Store file removed * Fixed some logging messages * Added unit test in tox.ini * Changed requirements.txt * Fixed requirements-dev.txt --- .../README.md | 61 ++++++++ .../__init__.py | 0 ...ure_storage_default_network_access_deny.py | 125 ++++++++++++++++ .../constraints.txt | 133 ++++++++++++++++++ .../minimum_permissions.json | 19 +++ .../requirements-dev.txt | 33 +++++ .../requirements.txt | 6 + ...ure_storage_default_network_access_deny.py | 63 +++++++++ tox.ini | 7 + 9 files changed, 447 insertions(+) create mode 100644 remediation_worker/jobs/azure_storage_default_network_access_deny/README.md create mode 100644 remediation_worker/jobs/azure_storage_default_network_access_deny/__init__.py create mode 100644 remediation_worker/jobs/azure_storage_default_network_access_deny/azure_storage_default_network_access_deny.py create mode 100644 remediation_worker/jobs/azure_storage_default_network_access_deny/constraints.txt create mode 100644 remediation_worker/jobs/azure_storage_default_network_access_deny/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_storage_default_network_access_deny/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_storage_default_network_access_deny/requirements.txt create mode 100644 test/unit/test_azure_storage_default_network_access_deny.py diff --git a/remediation_worker/jobs/azure_storage_default_network_access_deny/README.md b/remediation_worker/jobs/azure_storage_default_network_access_deny/README.md new file mode 100644 index 0000000..4ff78ee --- /dev/null +++ b/remediation_worker/jobs/azure_storage_default_network_access_deny/README.md @@ -0,0 +1,61 @@ +# Set Storage Account Default Network Access to Deny + +This job sets DefaultAction in network rule set for a Storage Account as Deny. + +### Applicable Rule + +##### Rule ID: +99d645b8-aa87-11ea-bb37-0242ac130002 + +##### Rule Name: +Storage account is publicly accessible + +## Getting Started +### Prerequisites +The provided Azure service principal must have the following permissions: +`Microsoft.Storage/storageAccounts/read` +`Microsoft.Storage/storageAccounts/write` + +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) + +### Running the script +You may run this script using following commands: + +```shell script + pip install -r requirements.txt + python3 azure_storage_default_network_access_deny.py +``` +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: + +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: + ```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python + ``` +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). + +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). +## Versioning +We use SemVer for versioning. For the versions available, see the tags on this repository. + +## Authors +* **VMware Secure State** - *Initial work* +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/graphs/contributors) who participated in this project. + +## License +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_storage_default_network_access_deny/__init__.py b/remediation_worker/jobs/azure_storage_default_network_access_deny/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_storage_default_network_access_deny/azure_storage_default_network_access_deny.py b/remediation_worker/jobs/azure_storage_default_network_access_deny/azure_storage_default_network_access_deny.py new file mode 100644 index 0000000..b2ff87f --- /dev/null +++ b/remediation_worker/jobs/azure_storage_default_network_access_deny/azure_storage_default_network_access_deny.py @@ -0,0 +1,125 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging + +from azure.mgmt.storage import StorageManagementClient +from azure.identity import ClientSecretCredential +from azure.mgmt.storage.models import ( + NetworkRuleSet, + StorageAccountUpdateParameters, +) + +logging.basicConfig(level=logging.INFO) + + +class StorageAccountDefaultActionDeny(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + + object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" account_name: {object_id}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + + return { + "resource_group_name": resource_group_name, + "account_name": object_id, + "subscription_id": subscription_id, + "region": region, + } + + def remediate(self, client, resource_group_name, account_name): + """Set Default Action for network access rule for a Storage Account as Deny + :param client: Instance of the Azure StorageManagementClient. + :param resource_group_name: The name of the resource group to which the storage account belongs + :param account_name: The name of the storage account. + :type resource_group_name: str. + :type account_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + # Setting Default Action for network as Deny + updated_network_rule_set = NetworkRuleSet(default_action="Deny") + logging.info("Setting default action in network rule set to Deny") + try: + logging.info(" executing client.storage_accounts.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={account_name}") + + client.storage_accounts.update( + resource_group_name=resource_group_name, + account_name=account_name, + parameters=StorageAccountUpdateParameters( + network_rule_set=updated_network_rule_set + ), + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def run(self, args): + """Run the remediation job. + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ClientSecretCredential( + client_id=os.environ.get("AZURE_CLIENT_ID"), + client_secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant_id=os.environ.get("AZURE_TENANT_ID"), + ) + + client = StorageManagementClient(credentials, params["subscription_id"]) + return self.remediate( + client, params["resource_group_name"], params["account_name"], + ) + + +if __name__ == "__main__": + sys.exit(StorageAccountDefaultActionDeny().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_storage_default_network_access_deny/constraints.txt b/remediation_worker/jobs/azure_storage_default_network_access_deny/constraints.txt new file mode 100644 index 0000000..479ee62 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_default_network_access_deny/constraints.txt @@ -0,0 +1,133 @@ +adal==1.2.5 \ + --hash=sha256:7492aff8f0ba7dd4e1c477303295c645141540fff34c3ca6de0a0b0e6c1c122a \ + --hash=sha256:8003ba03ef04170195b3eddda8a5ab43649ef2c5f0287023d515affb1ccfcfc3 +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.2 \ + --hash=sha256:621b53271f7988b766f8a7d7f7a2c44241e3d2c1d8db13e68089d6da6241748e \ + --hash=sha256:be23d411e19874f375c2ef0327c452be10b1e9a1023ac6afe334598f2920136b +azure-mgmt-core==1.2.1 \ + --hash=sha256:a3906fa77edfedfcc3229dc3b69489d5ed63b107c7eacbc50092e6cbfbfd83f0 \ + --hash=sha256:bd4503a2d81b86780f15936af2e4244c1345062f4c2422f0b377b56cb80d7796 +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-network==16.0.0 \ + --hash=sha256:6159a8c44590cc58841690c27c7d4acb0cd9ad0a1e5178c1d35e0f48e3c3c0e9 \ + --hash=sha256:c0e8358e9d530790dbf3efef6b31bce26e664de5096cbd84c62845067da815d1 +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.3 \ + --hash=sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d \ + --hash=sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b \ + --hash=sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4 \ + --hash=sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f \ + --hash=sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3 \ + --hash=sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579 \ + --hash=sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537 \ + --hash=sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e \ + --hash=sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05 \ + --hash=sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171 \ + --hash=sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca \ + --hash=sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522 \ + --hash=sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c \ + --hash=sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc \ + --hash=sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d \ + --hash=sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808 \ + --hash=sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828 \ + --hash=sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869 \ + --hash=sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d \ + --hash=sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9 \ + --hash=sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0 \ + --hash=sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc \ + --hash=sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15 \ + --hash=sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c \ + --hash=sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a \ + --hash=sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3 \ + --hash=sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1 \ + --hash=sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768 \ + --hash=sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d \ + --hash=sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b \ + --hash=sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e \ + --hash=sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d \ + --hash=sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730 \ + --hash=sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394 \ + --hash=sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1 \ + --hash=sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.2.1 \ + --hash=sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538 \ + --hash=sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f \ + --hash=sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77 \ + --hash=sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b \ + --hash=sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33 \ + --hash=sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e \ + --hash=sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb \ + --hash=sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e \ + --hash=sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7 \ + --hash=sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297 \ + --hash=sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d \ + --hash=sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7 \ + --hash=sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b \ + --hash=sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7 \ + --hash=sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4 \ + --hash=sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8 \ + --hash=sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b \ + --hash=sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851 \ + --hash=sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13 \ + --hash=sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b \ + --hash=sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3 \ + --hash=sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal==1.5.1 \ + --hash=sha256:7efb0256c96a7b2eadab49ce29ecdb91352a91440c12a40bed44303724b62fda \ + --hash=sha256:d84074a997e6fb2a47e22815dce376adcc8790838b6b1bf1fcea29378e2bf3eb +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msrest==0.6.19 \ + --hash=sha256:55f8c3940bc5dc609f8cf9fcd639444716cc212a943606756272e0d0017bbb5b \ + --hash=sha256:87aa64948c3ef3dbf6f6956d2240493e68d714e4621b92b65b3c4d5808297929 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +PyJWT==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +urllib3==1.25.11 \ + --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ + --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e diff --git a/remediation_worker/jobs/azure_storage_default_network_access_deny/minimum_permissions.json b/remediation_worker/jobs/azure_storage_default_network_access_deny/minimum_permissions.json new file mode 100644 index 0000000..a38d78e --- /dev/null +++ b/remediation_worker/jobs/azure_storage_default_network_access_deny/minimum_permissions.json @@ -0,0 +1,19 @@ +{ + "properties": { + "roleName": "remediate_set_default_network_access_deny", + "description": "This role has required permissions to make changes to the storage account", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Storage/storageAccounts/read", + "Microsoft.Storage/storageAccounts/write", + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } +} diff --git a/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements-dev.txt b/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements-dev.txt new file mode 100644 index 0000000..759f2ca --- /dev/null +++ b/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.1.2 \ + --hash=sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe \ + --hash=sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +zipp==3.4.0 \ + --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ + --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb diff --git a/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements.txt b/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements.txt new file mode 100644 index 0000000..1170c49 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_default_network_access_deny/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 diff --git a/test/unit/test_azure_storage_default_network_access_deny.py b/test/unit/test_azure_storage_default_network_access_deny.py new file mode 100644 index 0000000..c60066a --- /dev/null +++ b/test/unit/test_azure_storage_default_network_access_deny.py @@ -0,0 +1,63 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock +from remediation_worker.jobs.azure_storage_default_network_access_deny.azure_storage_default_network_access_deny import ( + StorageAccountDefaultActionDeny, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "99d645b8-aa87-11ea-bb37-0242ac130002", + "Service": "Storage", + "FindingInfo": { + "FindingId": "9b2da5e9-bb96-4298-b2c1-e6c341b44c5f", + "ObjectId": "storage_account_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Storage.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.StorageAccount.testingresourcename\\",\\"entityName\\":\\"testingresourcename\\",\\"entityType\\":\\"Azure.Storage.StorageAccount\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Storage\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestDefaultActionDeny(object): + def test_parse_payload(self, valid_payload): + params = StorageAccountDefaultActionDeny().parse(valid_payload) + assert params["account_name"] == "storage_account_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + action = StorageAccountDefaultActionDeny() + assert action.remediate(client, "resource_group", "account_name") == 0 + assert client.storage_accounts.update.call_count == 1 + + call_args = client.storage_accounts.update.call_args + updated_storage_account = call_args[1]["parameters"] + assert updated_storage_account.network_rule_set.default_action == "Deny" + + def test_remediate_with_exception(self): + client = Mock() + client.storage_accounts.update.side_effect = Exception + action = StorageAccountDefaultActionDeny() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index a19d93c..ccad1bf 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ envlist = unit-azure-vm-close-port-22 unit-azure-blob-remove-public-access unit-azure-storage-allow-only-https + unit-azure-storage-default-network-access-deny unit-azure-security-center-enable-ddos-protection [testenv] @@ -127,6 +128,12 @@ changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_storage_account_allow_https_traffic_only.py deps = -r remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/requirements-dev.txt +[testenv:unit-azure-storage-default-network-access-deny] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_storage_default_network_access_deny.py +deps = -r remediation_worker/jobs/azure_storage_default_network_access_deny/requirements-dev.txt + [testenv:unit-azure-security-center-enable-ddos-protection] description = Unit test the project changedir = test From e36619524812fdbb62559a40d8b7a1d565273dfd Mon Sep 17 00:00:00 2001 From: Zuber Date: Tue, 17 Nov 2020 10:54:43 -0800 Subject: [PATCH 17/34] Pla 19713: remove unused logcall (#31) * remove unused logcall function Co-authored-by: Mohammad Zuber Khan --- .../azure_blob_remove_public_access.py | 9 --------- .../azure_network_security_group_close_port_22.py | 9 --------- .../azure_network_security_group_close_port_3389.py | 9 --------- .../azure_storage_account_allow_https_traffic_only.py | 9 --------- .../azure_vm_close_port_22/azure_vm_close_port_22.py | 9 --------- 5 files changed, 45 deletions(-) diff --git a/remediation_worker/jobs/azure_blob_remove_public_access/azure_blob_remove_public_access.py b/remediation_worker/jobs/azure_blob_remove_public_access/azure_blob_remove_public_access.py index 804f0f9..45b17d8 100644 --- a/remediation_worker/jobs/azure_blob_remove_public_access/azure_blob_remove_public_access.py +++ b/remediation_worker/jobs/azure_blob_remove_public_access/azure_blob_remove_public_access.py @@ -24,15 +24,6 @@ logging.basicConfig(level=logging.INFO) -def logcall(f, *args, **kwargs): - logging.info( - "%s(%s)", - f.__name__, - ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), - ) - logging.info(f(*args, **kwargs)) - - class StorageBlobRemovePublicAccess(object): def parse(self, payload): """Parse payload received from Remediation Service. diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py index f4c98cc..a79a83e 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py +++ b/remediation_worker/jobs/azure_network_security_group_close_port_22/azure_network_security_group_close_port_22.py @@ -23,15 +23,6 @@ logging.basicConfig(level=logging.INFO) -def logcall(f, *args, **kwargs): - logging.info( - "%s(%s)", - f.__name__, - ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), - ) - logging.info(f(*args, **kwargs)) - - class NetworkSecurityGroupClosePort22(object): def parse(self, payload): """Parse payload received from Remediation Service. diff --git a/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py index bd0c7ea..671e38a 100644 --- a/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py +++ b/remediation_worker/jobs/azure_network_security_group_close_port_3389/azure_network_security_group_close_port_3389.py @@ -23,15 +23,6 @@ logging.basicConfig(level=logging.INFO) -def logcall(f, *args, **kwargs): - logging.info( - "%s(%s)", - f.__name__, - ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), - ) - logging.info(f(*args, **kwargs)) - - class NetworkSecurityGroupClosePort3389(object): def parse(self, payload): """Parse payload received from Remediation Service. diff --git a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py index 1ece258..c64f69f 100644 --- a/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py +++ b/remediation_worker/jobs/azure_storage_account_allow_https_traffic_only/azure_storage_account_allow_https_traffic_only.py @@ -24,15 +24,6 @@ logging.basicConfig(level=logging.INFO) -def logcall(f, *args, **kwargs): - logging.info( - "%s(%s)", - f.__name__, - ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), - ) - logging.info(f(*args, **kwargs)) - - class StorageAccountAllowHttpsTrafficOnly(object): def parse(self, payload): """Parse payload received from Remediation Service. diff --git a/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py index 88f1d2e..d6c0a0a 100644 --- a/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py +++ b/remediation_worker/jobs/azure_vm_close_port_22/azure_vm_close_port_22.py @@ -24,15 +24,6 @@ logging.basicConfig(level=logging.INFO) -def logcall(f, *args, **kwargs): - logging.info( - "%s(%s)", - f.__name__, - ", ".join(list(args) + [f"{k}={repr(v)}" for k, v in kwargs.items()]), - ) - logging.info(f(*args, **kwargs)) - - class VMSecurityGroupClosePort22(object): def parse(self, payload): """Parse payload received from Remediation Service. From 865b316a22a8c074d1c73e4ad8ca4ac0409934f8 Mon Sep 17 00:00:00 2001 From: Zuber Date: Mon, 23 Nov 2020 09:16:35 -0800 Subject: [PATCH 18/34] Add remediation job to enable ELB access logs (#29) * Add remediation job to enable ELB access logs Co-authored-by: Mohammad Zuber Khan --- .../jobs/elb_enable_access_logs/README.md | 70 +++++++ .../jobs/elb_enable_access_logs/__init__.py | 0 .../elb_enable_access_logs/constraints.txt | 43 +++++ .../elb_enable_access_logs.py | 182 ++++++++++++++++++ .../minimum_policy.json | 16 ++ .../requirements-dev.txt | 6 + .../elb_enable_access_logs/requirements.txt | 5 + .../elb_enable_access_logs/test_payload.json | 9 + test/unit/test_elb_enable_access_logs.py | 131 +++++++++++++ tox.ini | 7 + 10 files changed, 469 insertions(+) create mode 100644 remediation_worker/jobs/elb_enable_access_logs/README.md create mode 100644 remediation_worker/jobs/elb_enable_access_logs/__init__.py create mode 100644 remediation_worker/jobs/elb_enable_access_logs/constraints.txt create mode 100644 remediation_worker/jobs/elb_enable_access_logs/elb_enable_access_logs.py create mode 100644 remediation_worker/jobs/elb_enable_access_logs/minimum_policy.json create mode 100644 remediation_worker/jobs/elb_enable_access_logs/requirements-dev.txt create mode 100644 remediation_worker/jobs/elb_enable_access_logs/requirements.txt create mode 100644 remediation_worker/jobs/elb_enable_access_logs/test_payload.json create mode 100644 test/unit/test_elb_enable_access_logs.py diff --git a/remediation_worker/jobs/elb_enable_access_logs/README.md b/remediation_worker/jobs/elb_enable_access_logs/README.md new file mode 100644 index 0000000..5b37a1f --- /dev/null +++ b/remediation_worker/jobs/elb_enable_access_logs/README.md @@ -0,0 +1,70 @@ +# Enable Elastic Load Balancer access logs + +This job enables access logs for a classic Elastic Load Balancer (if they are not already enabled). +It will create a new S3 bucket with the required permissions and configure it to receive access logs from the ELB. + +### Applicable Rule + +##### Rule ID: +657c46b7-1cd0-4cce-80bb-9d195f49c987 + +##### Rule Name: +Elastic Load Balancer access logs are not enabled + +## Getting Started + +### Prerequisites + +The provided AWS credential must have access to `elb:DescribeLoadBalancerAttributes`, `s3:CreateBucket`, `s3:PutBucketPolicy`, and `elb:ModifyLoadBalancerAttributes`. + +You may find the latest example policy file [here](minimum_policy.json). + +### Running the script + +You may run this script using following commands: +```shell script + pip install -r ../../requirements.txt + python3 elb_enable_access_logs.py +``` + +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +1. Provision a Virtual Machine +Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +2. Setup Docker +Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +3. Deploy the worker image +SSH into the EC2 instance and run the command below to deploy the worker image: +```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python +``` + + +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/vmware-samples/secure-state-remediation-jobs/tags). + +## Authors + +* **VMware Secure State** - *Initial work* + +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/contributors) who + participated in this project. + +## License + +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details \ No newline at end of file diff --git a/remediation_worker/jobs/elb_enable_access_logs/__init__.py b/remediation_worker/jobs/elb_enable_access_logs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/elb_enable_access_logs/constraints.txt b/remediation_worker/jobs/elb_enable_access_logs/constraints.txt new file mode 100644 index 0000000..6b211d2 --- /dev/null +++ b/remediation_worker/jobs/elb_enable_access_logs/constraints.txt @@ -0,0 +1,43 @@ +docutils==0.15.2 \ + --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \ + --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \ + --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99 +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +s3transfer==0.3.3 \ + --hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \ + --hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db +urllib3==1.25.9 \ + --hash=sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527 \ + --hash=sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115 +six==1.15.0 \ + --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \ + --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +attrs==19.3.0 \ + --hash=sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c \ + --hash=sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72 +more-itertools==8.4.0 \ + --hash=sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5 \ + --hash=sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +iniconfig==1.0.1 \ + --hash=sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437 \ + --hash=sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b diff --git a/remediation_worker/jobs/elb_enable_access_logs/elb_enable_access_logs.py b/remediation_worker/jobs/elb_enable_access_logs/elb_enable_access_logs.py new file mode 100644 index 0000000..50c12d3 --- /dev/null +++ b/remediation_worker/jobs/elb_enable_access_logs/elb_enable_access_logs.py @@ -0,0 +1,182 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import boto3 +import json +import sys +import logging +logging.basicConfig(level=logging.INFO) + +from botocore.exceptions import ClientError + +def logcall(f, *args, **kwargs): + logging.info('%s(%s)', f.__name__, ', '.join(list(args) + [f'{k}={repr(v)}' for k, v in kwargs.items()])) + res = f(*args, **kwargs) + logging.info(res) + return res + +# taken from https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-access-logs.html +ELB_ACCOUNT_IDS = { + 'us-east-1': '127311923021', + 'us-east-2': '033677994240', + 'us-west-1': '027434742980', + 'us-west-2': '797873946194', + 'af-south-1': '098369216593', + 'ca-central-1': '985666609251', + 'eu-central-1': '054676820928', + 'eu-west-1': '156460612806', + 'eu-west-2': '652711504416', + 'eu-south-1': '635631232127', + 'eu-west-3': '009996457667', + 'eu-north-1': '897822967062', + 'ap-east-1': '754344448648', + 'ap-northeast-1': '582318560864', + 'ap-northeast-2': '600734575887', + 'ap-northeast-3': '383597477331', + 'ap-southeast-1': '114774131450', + 'ap-southeast-2': '783225319266', + 'ap-south-1': '718504428378', + 'me-south-1': '076674570225', + 'sa-east-1': '507241528517', + 'us-gov-west-1': '048591011584', + 'us-gov-east-1': '190560391635', + 'cn-north-1': '638102146993', + 'cn-northwest-1': '037604701340' +} + +def create_or_update_bucket_policy(s3_client, bucket_name, bucket_prefix, account_id, region): + elb_account_id = ELB_ACCOUNT_IDS[region] + statement = { + 'Effect': 'Allow', + 'Principal': { + 'AWS': f'arn:aws:iam::{elb_account_id}:root' + }, + 'Action': 's3:PutObject', + 'Resource': f'arn:aws:s3:::{bucket_name}/{bucket_prefix}/AWSLogs/{account_id}/*' + } + try: + policy = json.loads(logcall(s3_client.get_bucket_policy, Bucket=bucket_name)['Policy']) + if statement not in policy['Statement']: + policy['Statement'].append(statement) + logcall( + s3_client.put_bucket_policy, + Bucket=bucket_name, + Policy=json.dumps(policy) + ) + except ClientError as e: + if e.response['Error']['Code'] == 'NoSuchBucketPolicy': + policy = { + 'Version': '2012-10-17', + 'Statement': [ + statement + ] + } + logcall( + s3_client.put_bucket_policy, + Bucket=bucket_name, + Policy=json.dumps(policy) + ) + else: + raise e + +class ELBEnableAccessLogs(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + payload_dict = json.loads(payload) + return { + 'elb_name': payload_dict['notificationInfo']['FindingInfo']['ObjectId'], + 'region': payload_dict['notificationInfo']['FindingInfo']['Region'], + 'cloud_account_id': payload_dict['notificationInfo']['CloudAccountId'] + } + + def ensure_log_target_bucket(self, s3_client, target_bucket, region): + try: + logcall(s3_client.head_bucket, + Bucket=target_bucket) + except ClientError as e: + if e.response["Error"]["Code"] == "404": + # The bucket does not exist + if region == "us-east-1": + logcall(s3_client.create_bucket, + Bucket=target_bucket) + else: + logcall(s3_client.create_bucket, + Bucket=target_bucket, + CreateBucketConfiguration={"LocationConstraint": region} + ) + elif e.response["Error"]["Code"] == "403": + # The assumed role does not have the permission + logging.error("Not enough permissions to list buckets") + raise e + else: + raise e + + def remediate(self, elb_client, s3_client, elb_name, cloud_account_id, region): + """Enables access logs for the given ELB. + + :param elb_client: AWS ELB boto3 client + :param s3_client: AWS S3 boto3 client + :param elb_name: Name of elastic load balancer + :param cloud_account_id: Customer cloud account id + :returns: Integer signaling success or failure + :rtype: int + :raises: botocore.exceptions.ClientError + """ + + logs_enabled = logcall(elb_client.describe_load_balancer_attributes, LoadBalancerName=elb_name)['LoadBalancerAttributes']['AccessLog']['Enabled'] + + if logs_enabled: + logging.info('access logs already enabled') + else: + logging.info('enabling access logs') + bucket_name = f'vss-logging-target-{cloud_account_id}-{region}' + bucket_prefix = elb_name + self.ensure_log_target_bucket(s3_client, bucket_name, region) + create_or_update_bucket_policy(s3_client, bucket_name, bucket_prefix, cloud_account_id, region) + logcall( + elb_client.modify_load_balancer_attributes, + LoadBalancerName=elb_name, + LoadBalancerAttributes={ + 'AccessLog': { + 'Enabled': True, + 'S3BucketName': bucket_name, + 'S3BucketPrefix': bucket_prefix + } + } + ) + + return 0 + + def run(self, args): + """Run the remediation job. + + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + elb_client = boto3.client('elb', region_name=params['region']) + s3_client = boto3.client('s3', region_name=params['region']) + return self.remediate(elb_client, s3_client, **params) + + +if __name__ == '__main__': + sys.exit(ELBEnableAccessLogs().run(sys.argv)) diff --git a/remediation_worker/jobs/elb_enable_access_logs/minimum_policy.json b/remediation_worker/jobs/elb_enable_access_logs/minimum_policy.json new file mode 100644 index 0000000..caebe41 --- /dev/null +++ b/remediation_worker/jobs/elb_enable_access_logs/minimum_policy.json @@ -0,0 +1,16 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "ELBEnableAccessLogs", + "Effect": "Allow", + "Action": [ + "elb:DescribeLoadBalancerAttributes", + "s3:CreateBucket", + "s3:PutBucketPolicy", + "elb:ModifyLoadBalancerAttributes" + ], + "Resource": "*" + } + ] +} diff --git a/remediation_worker/jobs/elb_enable_access_logs/requirements-dev.txt b/remediation_worker/jobs/elb_enable_access_logs/requirements-dev.txt new file mode 100644 index 0000000..9412e93 --- /dev/null +++ b/remediation_worker/jobs/elb_enable_access_logs/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt +-c constraints.txt + +pytest==6.0.1 \ + --hash=sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4 \ + --hash=sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad diff --git a/remediation_worker/jobs/elb_enable_access_logs/requirements.txt b/remediation_worker/jobs/elb_enable_access_logs/requirements.txt new file mode 100644 index 0000000..d2dd560 --- /dev/null +++ b/remediation_worker/jobs/elb_enable_access_logs/requirements.txt @@ -0,0 +1,5 @@ +boto3==1.14.9 \ + --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ + --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 +botocore==1.17.9 \ + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ No newline at end of file diff --git a/remediation_worker/jobs/elb_enable_access_logs/test_payload.json b/remediation_worker/jobs/elb_enable_access_logs/test_payload.json new file mode 100644 index 0000000..330d856 --- /dev/null +++ b/remediation_worker/jobs/elb_enable_access_logs/test_payload.json @@ -0,0 +1,9 @@ +{ + "notificationInfo": { + "CloudAccountId": "650397460025", + "FindingInfo": { + "ObjectId": "jackson-test-classic-elb", + "Region": "us-east-1" + } + } +} diff --git a/test/unit/test_elb_enable_access_logs.py b/test/unit/test_elb_enable_access_logs.py new file mode 100644 index 0000000..024b0a1 --- /dev/null +++ b/test/unit/test_elb_enable_access_logs.py @@ -0,0 +1,131 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +import json +from botocore.exceptions import ClientError + +from remediation_worker.jobs.elb_enable_access_logs.elb_enable_access_logs import create_or_update_bucket_policy + +def ordered(o): + """deeply order o""" + if isinstance(o, dict): + return frozenset((k, ordered(v)) for k, v in o.items()) + if isinstance(o, list): + return frozenset(ordered(x) for x in o) + else: + return o + +def policies_equal(a, b): + return ordered(json.loads(a)) == ordered(json.loads(b)) + +class ExampleClient: + def __init__(self, expected_policy): + self.expected_policy = expected_policy + + def put_bucket_policy(self, *args, **kwargs): + assert policies_equal(kwargs['Policy'], self.expected_policy) + +@pytest.fixture +def test_data(): + bucket_name = 'vss-logging-target-650397460025-us-east-1' + bucket_prefix = 'jackson-test-classic-elb' + account_id = '650397460025' + region = 'us-east-1' + + policy = """ + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::127311923021:root" + }, + "Action": "s3:PutObject", + "Resource": "arn:aws:s3:::vss-logging-target-650397460025-us-east-1/jackson-test-classic-elb/AWSLogs/650397460025/*" + } + ] + } + """ + + valid_policy = """ + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::127311923021:root" + }, + "Action": "s3:PutObject", + "Resource": "arn:aws:s3:::vss-logging-target-650397460025-us-east-1/jackson-test-classic-elb/AWSLogs/650397460025/*" + }, + { + "Sid": "AddCannedAcl", + "Effect": "Allow", + "Principal": {"AWS": ["arn:aws:iam::111122223333:root", "arn:aws:iam::444455556666:root"]}, + "Action": ["s3:PutObject", "s3:PutObjectAcl"], + "Resource": "arn:aws:s3:::awsexamplebucket1/*", + "Condition": {"StringEquals": {"s3:x-amz-acl": ["public-read"]}} + } + ] + } + """ + + invalid_policy = """ + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AddCannedAcl", + "Effect": "Allow", + "Principal": {"AWS": ["arn:aws:iam::111122223333:root", "arn:aws:iam::444455556666:root"]}, + "Action": ["s3:PutObject", "s3:PutObjectAcl"], + "Resource": "arn:aws:s3:::awsexamplebucket1/*", + "Condition": {"StringEquals": {"s3:x-amz-acl": ["public-read"]}} + } + ] + } + """ + + return locals() + +class TestCreateOrUpdateBucketPolicy: + def test_no_policy(self, test_data): + class NoPolicyClient(ExampleClient): + def get_bucket_policy(self, *args, **kwargs): + raise ClientError({'Error': {'Code': 'NoSuchBucketPolicy'}}, None) + + client = NoPolicyClient(test_data['policy']) + create_or_update_bucket_policy(client, test_data['bucket_name'], test_data['bucket_prefix'], test_data['account_id'], test_data['region']) + + def test_valid_policy(self, test_data): + class ValidPolicyClient(ExampleClient): + def get_bucket_policy(self, *args, **kwargs): + return {'Policy': self.expected_policy} + + def put_bucket_policy(self, *args, **kwargs): + raise Exception('should not call put_bucket_policy if the policy is already valid') + + client = ValidPolicyClient(test_data['valid_policy']) + create_or_update_bucket_policy(client, test_data['bucket_name'], test_data['bucket_prefix'], test_data['account_id'], test_data['region']) + + def test_invalid_policy(self, test_data): + class InvalidPolicyClient(ExampleClient): + def get_bucket_policy(self, *args, **kwargs): + return {'Policy': test_data['invalid_policy']} + + client = InvalidPolicyClient(test_data['valid_policy']) + create_or_update_bucket_policy(client, test_data['bucket_name'], test_data['bucket_prefix'], test_data['account_id'], test_data['region']) diff --git a/tox.ini b/tox.ini index ccad1bf..bd645ce 100644 --- a/tox.ini +++ b/tox.ini @@ -11,6 +11,7 @@ envlist = unit-s3-list-buckets unit-security-group-close-port-3389 unit-rds-backup-retention-30-days + unit-elb-enable-access-logs unit-security-group-close-port-22 unit-azure-network-security-group-close-port-22 unit-azure-network-security-group-close-port-3389 @@ -98,6 +99,12 @@ changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_security_group_close_port_22.py deps = -r remediation_worker/jobs/security_group_close_port_22/requirements-dev.txt +[testenv:unit-elb-enable-access-logs] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_elb_enable_access_logs.py +deps = -r remediation_worker/jobs/elb_enable_access_logs/requirements-dev.txt + [testenv:unit-azure-network-security-group-close-port-22] description = Unit test the project changedir = test From 9b31d8ed4fc32c1bb8f9c0925af6a245e5b72b50 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni Date: Thu, 26 Nov 2020 23:31:22 +0530 Subject: [PATCH 19/34] PLA-20589: Enable Azure SQL Server Auditing remediation job --- .../azure_sql_auditing_on_server/README.md | 66 ++++++ .../azure_sql_auditing_on_server/__init__.py | 0 .../azure_sql_auditing_on_server.py | 223 ++++++++++++++++++ .../constraints.txt | 139 +++++++++++ .../minimum_permissions.json | 24 ++ .../requirements-dev.txt | 33 +++ .../requirements.txt | 12 + .../unit/test_azure_sql_auditing_on_server.py | 145 ++++++++++++ tox.ini | 9 +- 9 files changed, 650 insertions(+), 1 deletion(-) create mode 100644 remediation_worker/jobs/azure_sql_auditing_on_server/README.md create mode 100644 remediation_worker/jobs/azure_sql_auditing_on_server/__init__.py create mode 100644 remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py create mode 100644 remediation_worker/jobs/azure_sql_auditing_on_server/constraints.txt create mode 100644 remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_sql_auditing_on_server/requirements.txt create mode 100644 test/unit/test_azure_sql_auditing_on_server.py diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/README.md b/remediation_worker/jobs/azure_sql_auditing_on_server/README.md new file mode 100644 index 0000000..441c536 --- /dev/null +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/README.md @@ -0,0 +1,66 @@ +# Enable SQL Server Auditing + +This job enables server blob auditing policy for the SQL Database Server by creating a Storage Account and assigning a Storage Blob Data Contributer role to the server. + +### Applicable Rule + +##### Rule ID: +5c8c268a7a550e1fb6560cb9 + +##### Rule Name: +SQL server auditing is not enabled + +## Getting Started +### Prerequisites +The provided Azure service principal must have the following permissions: +`Microsoft.Sql/servers/read` +`Microsoft.Sql/servers/write` +`Microsoft.Sql/servers/auditingSettings/read` +`Microsoft.Sql/servers/auditingSettings/write` +`Microsoft.Storage/storageAccounts/write` +`Microsoft.Storage/storageAccounts/read` +`Microsoft.Authorization/roleAssignments/write` + +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) + +### Running the script +You may run this script using following commands: + +```shell script + pip install -r requirements.txt + python3 azure_sql_auditing_on_server.py +``` +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: + +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: + ```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python + ``` +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). + +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). +## Versioning +We use SemVer for versioning. For the versions available, see the tags on this repository. + +## Authors +* **VMware Secure State** - *Initial work* +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/graphs/contributors) who participated in this project. + +## License +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/__init__.py b/remediation_worker/jobs/azure_sql_auditing_on_server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py new file mode 100644 index 0000000..7db9b65 --- /dev/null +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py @@ -0,0 +1,223 @@ +import json +import os +import sys +import logging +import random +import string +import uuid + +from azure.identity import ClientSecretCredential +from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.authorization import AuthorizationManagementClient +from azure.mgmt.sql import SqlManagementClient +from azure.mgmt.storage import StorageManagementClient +from azure.mgmt.sql.models import ( + ServerBlobAuditingPolicy, + BlobAuditingPolicyState, + ResourceIdentity, + IdentityType, + Server, +) +from azure.mgmt.storage.models import ( + StorageAccountCreateParameters, + NetworkRuleSet, + Sku, + SkuName, + SkuTier, + DefaultAction, +) +from azure.mgmt.authorization.models import ( + RoleAssignmentCreateParameters, + PrincipalType, +) + +logging.basicConfig(level=logging.INFO) + + +def get_random_string(length): + letters = string.ascii_lowercase + random_str = "".join(random.choice(letters) for i in range(length)) + result_str = "sqlauditing" + random_str + return result_str + + +class SqlServerEnableBlobAuditingPolicy(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + + object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" sql_server_name: {object_id}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + return { + "resource_group_name": resource_group_name, + "sql_server_name": object_id, + "subscription_id": subscription_id, + "region": region, + } + + def remediate( + self, + client, + client_stg, + client_auth, + resource_group_name, + sql_server_name, + region, + subscription_id, + ): + """Enable Server blob auditing policy for Azure SQL Server + :param client: Instance of the Azure SqlManagementClient. + :param resource_group_name: The name of the resource group to which the SQL Server belongs. + :param sql_server_name: The name of the SQL Server. + :type resource_group_name: str. + :type sql_server_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + try: + server = client.servers.get( + resource_group_name=resource_group_name, server_name=sql_server_name, + ) + if server.identity is None: + logging.info( + f"Assigning Azure Active Directory Identity to the SQL Database Server {sql_server_name}" + ) + updated_server = client.servers.update( + resource_group_name=resource_group_name, + server_name=sql_server_name, + parameters=Server( + location=region, + identity=ResourceIdentity(type=IdentityType.system_assigned), + ), + ).result() + principalId = updated_server.identity.principal_id + else: + principalId = server.identity.principal_id + + create_params = StorageAccountCreateParameters( + location=region, + sku=Sku(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), + kind="StorageV2", + enable_https_traffic_only=True, + network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), + ) + stg_account_name = get_random_string(6) + logging.info(f"Creating a storage account with name {stg_account_name}") + + client_stg.storage_accounts.begin_create( + resource_group_name=resource_group_name, + account_name=stg_account_name, + parameters=create_params, + ) + + logging.info( + f"Creating a Role Assignment for Storage Account {stg_account_name} and assigning Storage Blob Data Contributer Role to the SQL Database Server {sql_server_name}" + ) + guid = uuid.uuid4() + Scope = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{resourceName}".format( + subscriptionId=subscription_id, + resourceGroupName=resource_group_name, + resourceName=stg_account_name, + ) + client_auth.role_assignments.create( + scope=Scope, + role_assignment_name=guid, + parameters=RoleAssignmentCreateParameters( + role_definition_id="/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe".format( + subscriptionId=subscription_id + ), + principal_id=principalId, + principal_type=PrincipalType.service_principal, + ), + ) + + logging.info("Enabling Server blob auditing policy for Azure SQL Server") + logging.info( + " executing client.server_blob_auditing_policies.create_or_update" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" server_name={sql_server_name}") + + client.server_blob_auditing_policies.create_or_update( + resource_group_name=resource_group_name, + server_name=sql_server_name, + parameters=ServerBlobAuditingPolicy( + state=BlobAuditingPolicyState.enabled, + storage_endpoint="https://{storage_account_name}.blob.core.windows.net/".format( + storage_account_name=stg_account_name + ), + ), + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + return 0 + + def run(self, args): + """Run the remediation job. + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ServicePrincipalCredentials( + client_id=os.environ.get("AZURE_CLIENT_ID"), + secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant=os.environ.get("AZURE_TENANT_ID"), + ) + credentials1 = ClientSecretCredential( + client_id=os.environ.get("AZURE_CLIENT_ID"), + client_secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant_id=os.environ.get("AZURE_TENANT_ID"), + ) + + client = SqlManagementClient( + credentials, params["subscription_id"], base_url=None + ) + client_stg = StorageManagementClient(credentials1, params["subscription_id"]) + client_auth = AuthorizationManagementClient( + credentials, params["subscription_id"] + ) + return self.remediate( + client, + client_stg, + client_auth, + params["resource_group_name"], + params["sql_server_name"], + params["region"], + params["subscription_id"], + ) + + +if __name__ == "__main__": + sys.exit(SqlServerEnableBlobAuditingPolicy().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/constraints.txt b/remediation_worker/jobs/azure_sql_auditing_on_server/constraints.txt new file mode 100644 index 0000000..7a72ec4 --- /dev/null +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/constraints.txt @@ -0,0 +1,139 @@ +adal==1.2.5 \ + --hash=sha256:7492aff8f0ba7dd4e1c477303295c645141540fff34c3ca6de0a0b0e6c1c122a \ + --hash=sha256:8003ba03ef04170195b3eddda8a5ab43649ef2c5f0287023d515affb1ccfcfc3 +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.2 \ + --hash=sha256:621b53271f7988b766f8a7d7f7a2c44241e3d2c1d8db13e68089d6da6241748e \ + --hash=sha256:be23d411e19874f375c2ef0327c452be10b1e9a1023ac6afe334598f2920136b +azure-mgmt-core==1.2.1 \ + --hash=sha256:a3906fa77edfedfcc3229dc3b69489d5ed63b107c7eacbc50092e6cbfbfd83f0 \ + --hash=sha256:bd4503a2d81b86780f15936af2e4244c1345062f4c2422f0b377b56cb80d7796 +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-sql==0.24.0 \ + --hash=sha256:347f6b7d5e252d48331658fd5febefdacbc341641918df3111c735f4a6906e5e \ + --hash=sha256:da391ed00d82cd8e20ca50affdc43b99fd9a7919b54a3a0d53c73cb41eea09d3 +azure-mgmt-network==16.0.0 \ + --hash=sha256:6159a8c44590cc58841690c27c7d4acb0cd9ad0a1e5178c1d35e0f48e3c3c0e9 \ + --hash=sha256:c0e8358e9d530790dbf3efef6b31bce26e664de5096cbd84c62845067da815d1 +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +azure-mgmt-authorization==0.61.0 \ + --hash=sha256:38f8afcd5c0065e598305de15dbb6b81521ac1e05216049f10ca32a435fd5817 \ + --hash=sha256:f5cceea3add04e9445ea88492f15eecf6c126f0406d967c95f6e48b79be8db75 +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.3 \ + --hash=sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d \ + --hash=sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b \ + --hash=sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4 \ + --hash=sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f \ + --hash=sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3 \ + --hash=sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579 \ + --hash=sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537 \ + --hash=sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e \ + --hash=sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05 \ + --hash=sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171 \ + --hash=sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca \ + --hash=sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522 \ + --hash=sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c \ + --hash=sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc \ + --hash=sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d \ + --hash=sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808 \ + --hash=sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828 \ + --hash=sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869 \ + --hash=sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d \ + --hash=sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9 \ + --hash=sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0 \ + --hash=sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc \ + --hash=sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15 \ + --hash=sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c \ + --hash=sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a \ + --hash=sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3 \ + --hash=sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1 \ + --hash=sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768 \ + --hash=sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d \ + --hash=sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b \ + --hash=sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e \ + --hash=sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d \ + --hash=sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730 \ + --hash=sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394 \ + --hash=sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1 \ + --hash=sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.2.1 \ + --hash=sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538 \ + --hash=sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f \ + --hash=sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77 \ + --hash=sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b \ + --hash=sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33 \ + --hash=sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e \ + --hash=sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb \ + --hash=sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e \ + --hash=sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7 \ + --hash=sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297 \ + --hash=sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d \ + --hash=sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7 \ + --hash=sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b \ + --hash=sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7 \ + --hash=sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4 \ + --hash=sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8 \ + --hash=sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b \ + --hash=sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851 \ + --hash=sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13 \ + --hash=sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b \ + --hash=sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3 \ + --hash=sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal==1.5.1 \ + --hash=sha256:7efb0256c96a7b2eadab49ce29ecdb91352a91440c12a40bed44303724b62fda \ + --hash=sha256:d84074a997e6fb2a47e22815dce376adcc8790838b6b1bf1fcea29378e2bf3eb +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msrest==0.6.19 \ + --hash=sha256:55f8c3940bc5dc609f8cf9fcd639444716cc212a943606756272e0d0017bbb5b \ + --hash=sha256:87aa64948c3ef3dbf6f6956d2240493e68d714e4621b92b65b3c4d5808297929 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +PyJWT==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +urllib3==1.25.11 \ + --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ + --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json b/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json new file mode 100644 index 0000000..1d78586 --- /dev/null +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json @@ -0,0 +1,24 @@ +{ + "properties": { + "roleName": "remediate_enable_server_blob_auditing_policies", + "description": "This role has required permissions to make changes to the SQL Server", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Sql/servers/read", + "Microsoft.Sql/servers/write", + "Microsoft.Sql/servers/auditingSettings/read", + "Microsoft.Sql/servers/auditingSettings/write", + "Microsoft.Storage/storageAccounts/write", + "Microsoft.Storage/storageAccounts/read", + "Microsoft.Authorization/roleAssignments/write" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } + } diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev.txt b/remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev.txt new file mode 100644 index 0000000..759f2ca --- /dev/null +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.1.2 \ + --hash=sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe \ + --hash=sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +zipp==3.4.0 \ + --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ + --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/requirements.txt b/remediation_worker/jobs/azure_sql_auditing_on_server/requirements.txt new file mode 100644 index 0000000..0d2469a --- /dev/null +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/requirements.txt @@ -0,0 +1,12 @@ +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-sql==0.24.0 \ + --hash=sha256:347f6b7d5e252d48331658fd5febefdacbc341641918df3111c735f4a6906e5e \ + --hash=sha256:da391ed00d82cd8e20ca50affdc43b99fd9a7919b54a3a0d53c73cb41eea09d3 +azure-mgmt-authorization==0.61.0 \ + --hash=sha256:38f8afcd5c0065e598305de15dbb6b81521ac1e05216049f10ca32a435fd5817 \ + --hash=sha256:f5cceea3add04e9445ea88492f15eecf6c126f0406d967c95f6e48b79be8db75 +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 diff --git a/test/unit/test_azure_sql_auditing_on_server.py b/test/unit/test_azure_sql_auditing_on_server.py new file mode 100644 index 0000000..a8d1f1d --- /dev/null +++ b/test/unit/test_azure_sql_auditing_on_server.py @@ -0,0 +1,145 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock +from remediation_worker.jobs.azure_sql_auditing_on_server.azure_sql_auditing_on_server import ( + SqlServerEnableBlobAuditingPolicy, +) +from azure.mgmt.sql.models import Server, ResourceIdentity, BlobAuditingPolicyState + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c8c268a7a550e1fb6560cb9", + "Service": "Sql", + "FindingInfo": { + "FindingId": "d0431afd-b82e-4021-8aa6-ba3cf5c60ef7", + "ObjectId": "sql_server_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Sql.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.Server.sql_server_name\\",\\"entityName\\":\\"sql_server_name\\",\\"entityType\\":\\"Azure.Sql.Server\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Sql\\",\\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestEnableDdosProtection(object): + def test_parse_payload(self, valid_payload): + params = SqlServerEnableBlobAuditingPolicy().parse(valid_payload) + assert params["sql_server_name"] == "sql_server_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success_without_server_identity(self): + client = Mock() + client_stg = Mock() + client_auth = Mock() + client.servers.get.return_value = Server( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", + name="remserver5", + type="Microsoft.Sql/servers", + location="eastus", + identity=None, + administrator_login="accelerators", + administrator_login_password=None, + state="Ready", + fully_qualified_domain_name="remserver5.database.windows.net", + public_network_access="Enabled", + ) + resource_identity = ResourceIdentity( + principal_id="139bcf82-e14e-4773-bcf4-1da136674792", + type="SystemAssigned", + tenant_id="b39138ca-3cee-4b4a-a4d6-cd83d9dd62f0", + ) + client.servers.update.result.return_value = Server( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", + name="remserver5", + type="Microsoft.Sql/servers", + location="eastus", + identity=resource_identity, + administrator_login="accelerators", + administrator_login_password=None, + state="Ready", + fully_qualified_domain_name="remserver5.database.windows.net", + public_network_access="Enabled", + ) + + action = SqlServerEnableBlobAuditingPolicy() + assert ( + action.remediate( + client, + client_stg, + client_auth, + "resource_group", + "sql_server_name", + "subscription_id", + "region", + ) + == 0 + ) + assert client.server_blob_auditing_policies.create_or_update.call_count == 1 + call_args = client.server_blob_auditing_policies.create_or_update.call_args + updated_auditing_policy = call_args[1]["parameters"] + assert updated_auditing_policy.state == BlobAuditingPolicyState.enabled + + def test_remediate_success_with_server_identity(self): + client = Mock() + client_stg = Mock() + client_auth = Mock() + resource_identity = ResourceIdentity( + principal_id="139bcf82-e14e-4773-bcf4-1da136674792", + type="SystemAssigned", + tenant_id="b39138ca-3cee-4b4a-a4d6-cd83d9dd62f0", + ) + client.servers.get.return_value = Server( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", + name="remserver5", + type="Microsoft.Sql/servers", + location="eastus", + identity=resource_identity, + administrator_login="accelerators", + administrator_login_password=None, + state="Ready", + fully_qualified_domain_name="remserver5.database.windows.net", + public_network_access="Enabled", + ) + action = SqlServerEnableBlobAuditingPolicy() + assert ( + action.remediate( + client, + client_stg, + client_auth, + "resource_group", + "sql_server_name", + "subscription_id", + "region", + ) + == 0 + ) + assert client.server_blob_auditing_policies.create_or_update.call_count == 1 + call_args = client.server_blob_auditing_policies.create_or_update.call_args + updated_auditing_policy = call_args[1]["parameters"] + assert updated_auditing_policy.state == BlobAuditingPolicyState.enabled + + def test_remediate_with_exception(self): + client = Mock() + client.virtual_networks.begin_create_or_update.side_effect = Exception + action = SqlServerEnableBlobAuditingPolicy() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index ccad1bf..0637500 100644 --- a/tox.ini +++ b/tox.ini @@ -19,6 +19,7 @@ envlist = unit-azure-storage-allow-only-https unit-azure-storage-default-network-access-deny unit-azure-security-center-enable-ddos-protection + unit-azure-sql-auditing-on-server [testenv] passenv = @@ -138,4 +139,10 @@ deps = -r remediation_worker/jobs/azure_storage_default_network_access_deny/requ description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_security_center_enable_ddos_protection.py -deps = -r remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt \ No newline at end of file +deps = -r remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt + +[testenv:unit-azure-sql-auditing-on-server] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_sql_auditing_on_server.py +deps = -r remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev.txt From 3aaa0affd34d9d49d88f754ef5e990fe258da650 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni Date: Tue, 1 Dec 2020 19:12:19 +0530 Subject: [PATCH 20/34] PLA-20589: Fixed unit-test file --- .../azure_sql_auditing_on_server.py | 16 ++++----- .../unit/test_azure_sql_auditing_on_server.py | 33 +++++++++++++------ 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py index 7db9b65..e4c7791 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py @@ -85,8 +85,8 @@ def parse(self, payload): def remediate( self, client, - client_stg, - client_auth, + client_storage, + client_authorization, resource_group_name, sql_server_name, region, @@ -133,7 +133,7 @@ def remediate( stg_account_name = get_random_string(6) logging.info(f"Creating a storage account with name {stg_account_name}") - client_stg.storage_accounts.begin_create( + client_storage.storage_accounts.begin_create( resource_group_name=resource_group_name, account_name=stg_account_name, parameters=create_params, @@ -148,7 +148,7 @@ def remediate( resourceGroupName=resource_group_name, resourceName=stg_account_name, ) - client_auth.role_assignments.create( + client_authorization.role_assignments.create( scope=Scope, role_assignment_name=guid, parameters=RoleAssignmentCreateParameters( @@ -204,14 +204,14 @@ def run(self, args): client = SqlManagementClient( credentials, params["subscription_id"], base_url=None ) - client_stg = StorageManagementClient(credentials1, params["subscription_id"]) - client_auth = AuthorizationManagementClient( + client_storage = StorageManagementClient(credentials1, params["subscription_id"]) + client_authorization = AuthorizationManagementClient( credentials, params["subscription_id"] ) return self.remediate( client, - client_stg, - client_auth, + client_storage, + client_authorization, params["resource_group_name"], params["sql_server_name"], params["region"], diff --git a/test/unit/test_azure_sql_auditing_on_server.py b/test/unit/test_azure_sql_auditing_on_server.py index a8d1f1d..e964520 100644 --- a/test/unit/test_azure_sql_auditing_on_server.py +++ b/test/unit/test_azure_sql_auditing_on_server.py @@ -48,8 +48,8 @@ def test_parse_payload(self, valid_payload): def test_remediate_success_without_server_identity(self): client = Mock() - client_stg = Mock() - client_auth = Mock() + client_storage = Mock() + client_authorization = Mock() client.servers.get.return_value = Server( id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", name="remserver5", @@ -84,8 +84,8 @@ def test_remediate_success_without_server_identity(self): assert ( action.remediate( client, - client_stg, - client_auth, + client_storage, + client_authorization, "resource_group", "sql_server_name", "subscription_id", @@ -100,8 +100,8 @@ def test_remediate_success_without_server_identity(self): def test_remediate_success_with_server_identity(self): client = Mock() - client_stg = Mock() - client_auth = Mock() + client_storage = Mock() + client_authorization = Mock() resource_identity = ResourceIdentity( principal_id="139bcf82-e14e-4773-bcf4-1da136674792", type="SystemAssigned", @@ -123,8 +123,8 @@ def test_remediate_success_with_server_identity(self): assert ( action.remediate( client, - client_stg, - client_auth, + client_storage, + client_authorization, "resource_group", "sql_server_name", "subscription_id", @@ -139,7 +139,20 @@ def test_remediate_success_with_server_identity(self): def test_remediate_with_exception(self): client = Mock() - client.virtual_networks.begin_create_or_update.side_effect = Exception + client_storage = Mock() + client_authorization = Mock() + client.server_blob_auditing_policies.create_or_update.side_effect = Exception action = SqlServerEnableBlobAuditingPolicy() with pytest.raises(Exception): - assert action.remediate(client, "security_group_id", "resource_group") + assert ( + action.remediate( + client, + client_storage, + client_authorization, + "resource_group", + "security_group", + "subscription_id", + "region", + ) + == 0 + ) From 670a40dd1edf0928f8886d7280fbaa908e2a963d Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Fri, 4 Dec 2020 23:03:41 +0530 Subject: [PATCH 21/34] PLA-21409: Azure SQL Server Threat Detection enable (#36) --- .../README.md | 61 ++++++++ .../__init__.py | 0 .../azure_sql_threat_detection_on_server.py | 109 ++++++++++++++ .../constraints.txt | 136 ++++++++++++++++++ .../minimum_permissions.json | 19 +++ .../requirements-dev.txt | 33 +++++ .../requirements.txt | 6 + ...st_azure_sql_threat_detection_on_server.py | 63 ++++++++ tox.ini | 9 +- 9 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 remediation_worker/jobs/azure_sql_threat_detection_on_server/README.md create mode 100644 remediation_worker/jobs/azure_sql_threat_detection_on_server/__init__.py create mode 100644 remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py create mode 100644 remediation_worker/jobs/azure_sql_threat_detection_on_server/constraints.txt create mode 100644 remediation_worker/jobs/azure_sql_threat_detection_on_server/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements.txt create mode 100644 test/unit/test_azure_sql_threat_detection_on_server.py diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/README.md b/remediation_worker/jobs/azure_sql_threat_detection_on_server/README.md new file mode 100644 index 0000000..3349ef2 --- /dev/null +++ b/remediation_worker/jobs/azure_sql_threat_detection_on_server/README.md @@ -0,0 +1,61 @@ +# Enable Threat Detection for SQL Database Server + +This job enables SQL Database Server threat detection. + +### Applicable Rule + +##### Rule ID: +5c8c26947a550e1fb6560cce + +##### Rule Name: +SQL server threat detection is not enabled + +## Getting Started +### Prerequisites +The provided Azure service principal must have the following permissions: +`Microsoft.Sql/servers/securityAlertPolicies/write` +`Microsoft.Sql/servers/securityAlertPolicies/read` + +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) + +### Running the script +You may run this script using following commands: + +```shell script + pip install -r requirements.txt + python3 azure_sql_threat_detection_on_server.py +``` +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: + +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: + ```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python + ``` +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). + +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). +## Versioning +We use SemVer for versioning. For the versions available, see the tags on this repository. + +## Authors +* **VMware Secure State** - *Initial work* +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/graphs/contributors) who participated in this project. + +## License +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/__init__.py b/remediation_worker/jobs/azure_sql_threat_detection_on_server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py b/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py new file mode 100644 index 0000000..58742e4 --- /dev/null +++ b/remediation_worker/jobs/azure_sql_threat_detection_on_server/azure_sql_threat_detection_on_server.py @@ -0,0 +1,109 @@ +import json +import os +import sys +import logging + +from azure.mgmt.sql import SqlManagementClient +from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.sql.models import ServerSecurityAlertPolicy, SecurityAlertPolicyState + +logging.basicConfig(level=logging.INFO) + + +class EnableSqlServerThreatProtection(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" sql_server_name: {object_id}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + return { + "resource_group_name": resource_group_name, + "sql_server_name": object_id, + "subscription_id": subscription_id, + "region": region, + } + + def remediate(self, client, resource_group_name, sql_server_name): + """Enable Transparent Data Encryption for SQL Database Server + :param client: Instance of the Azure SqlManagementClient. + :param resource_group_name: The name of the resource group to which the SQL Server belongs. + :param sql_server_name: The name of the SQL Server. + :type resource_group_name: str. + :type sql_server_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + logging.info("Enabling Threat Detection for SQL Database Server") + try: + logging.info( + " executing client.server_security_alert_policies.create_or_update" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" server_name={sql_server_name}") + + client.server_security_alert_policies.create_or_update( + resource_group_name=resource_group_name, + server_name=sql_server_name, + parameters=ServerSecurityAlertPolicy( + state=SecurityAlertPolicyState.enabled + ), + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def run(self, args): + """Run the remediation job. + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ServicePrincipalCredentials( + client_id=os.environ.get("AZURE_CLIENT_ID"), + secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant=os.environ.get("AZURE_TENANT_ID"), + ) + client = SqlManagementClient( + credentials, params["subscription_id"], base_url=None + ) + + return self.remediate( + client, params["resource_group_name"], params["sql_server_name"], + ) + + +if __name__ == "__main__": + sys.exit(EnableSqlServerThreatProtection().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/constraints.txt b/remediation_worker/jobs/azure_sql_threat_detection_on_server/constraints.txt new file mode 100644 index 0000000..e7f696c --- /dev/null +++ b/remediation_worker/jobs/azure_sql_threat_detection_on_server/constraints.txt @@ -0,0 +1,136 @@ +adal==1.2.5 \ + --hash=sha256:7492aff8f0ba7dd4e1c477303295c645141540fff34c3ca6de0a0b0e6c1c122a \ + --hash=sha256:8003ba03ef04170195b3eddda8a5ab43649ef2c5f0287023d515affb1ccfcfc3 +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.2 \ + --hash=sha256:621b53271f7988b766f8a7d7f7a2c44241e3d2c1d8db13e68089d6da6241748e \ + --hash=sha256:be23d411e19874f375c2ef0327c452be10b1e9a1023ac6afe334598f2920136b +azure-mgmt-core==1.2.1 \ + --hash=sha256:a3906fa77edfedfcc3229dc3b69489d5ed63b107c7eacbc50092e6cbfbfd83f0 \ + --hash=sha256:bd4503a2d81b86780f15936af2e4244c1345062f4c2422f0b377b56cb80d7796 +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-sql==0.24.0 \ + --hash=sha256:347f6b7d5e252d48331658fd5febefdacbc341641918df3111c735f4a6906e5e \ + --hash=sha256:da391ed00d82cd8e20ca50affdc43b99fd9a7919b54a3a0d53c73cb41eea09d3 +azure-mgmt-network==16.0.0 \ + --hash=sha256:6159a8c44590cc58841690c27c7d4acb0cd9ad0a1e5178c1d35e0f48e3c3c0e9 \ + --hash=sha256:c0e8358e9d530790dbf3efef6b31bce26e664de5096cbd84c62845067da815d1 +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.3 \ + --hash=sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d \ + --hash=sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b \ + --hash=sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4 \ + --hash=sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f \ + --hash=sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3 \ + --hash=sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579 \ + --hash=sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537 \ + --hash=sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e \ + --hash=sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05 \ + --hash=sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171 \ + --hash=sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca \ + --hash=sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522 \ + --hash=sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c \ + --hash=sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc \ + --hash=sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d \ + --hash=sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808 \ + --hash=sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828 \ + --hash=sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869 \ + --hash=sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d \ + --hash=sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9 \ + --hash=sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0 \ + --hash=sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc \ + --hash=sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15 \ + --hash=sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c \ + --hash=sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a \ + --hash=sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3 \ + --hash=sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1 \ + --hash=sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768 \ + --hash=sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d \ + --hash=sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b \ + --hash=sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e \ + --hash=sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d \ + --hash=sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730 \ + --hash=sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394 \ + --hash=sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1 \ + --hash=sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.2.1 \ + --hash=sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538 \ + --hash=sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f \ + --hash=sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77 \ + --hash=sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b \ + --hash=sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33 \ + --hash=sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e \ + --hash=sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb \ + --hash=sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e \ + --hash=sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7 \ + --hash=sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297 \ + --hash=sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d \ + --hash=sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7 \ + --hash=sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b \ + --hash=sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7 \ + --hash=sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4 \ + --hash=sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8 \ + --hash=sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b \ + --hash=sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851 \ + --hash=sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13 \ + --hash=sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b \ + --hash=sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3 \ + --hash=sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal==1.5.1 \ + --hash=sha256:7efb0256c96a7b2eadab49ce29ecdb91352a91440c12a40bed44303724b62fda \ + --hash=sha256:d84074a997e6fb2a47e22815dce376adcc8790838b6b1bf1fcea29378e2bf3eb +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msrest==0.6.19 \ + --hash=sha256:55f8c3940bc5dc609f8cf9fcd639444716cc212a943606756272e0d0017bbb5b \ + --hash=sha256:87aa64948c3ef3dbf6f6956d2240493e68d714e4621b92b65b3c4d5808297929 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +PyJWT==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +urllib3==1.25.11 \ + --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ + --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/minimum_permissions.json b/remediation_worker/jobs/azure_sql_threat_detection_on_server/minimum_permissions.json new file mode 100644 index 0000000..1d40655 --- /dev/null +++ b/remediation_worker/jobs/azure_sql_threat_detection_on_server/minimum_permissions.json @@ -0,0 +1,19 @@ +{ + "properties": { + "roleName": "remediate_enable_sql_threat_detection_on_server", + "description": "This role has required permissions to make changes to the Azure SQL Server", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Sql/servers/securityAlertPolicies/write", + "Microsoft.Sql/servers/securityAlertPolicies/read" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } + } diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt b/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt new file mode 100644 index 0000000..759f2ca --- /dev/null +++ b/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.1.2 \ + --hash=sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe \ + --hash=sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +zipp==3.4.0 \ + --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ + --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb diff --git a/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements.txt b/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements.txt new file mode 100644 index 0000000..074579f --- /dev/null +++ b/remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-sql==0.24.0 \ + --hash=sha256:347f6b7d5e252d48331658fd5febefdacbc341641918df3111c735f4a6906e5e \ + --hash=sha256:da391ed00d82cd8e20ca50affdc43b99fd9a7919b54a3a0d53c73cb41eea09d3 diff --git a/test/unit/test_azure_sql_threat_detection_on_server.py b/test/unit/test_azure_sql_threat_detection_on_server.py new file mode 100644 index 0000000..5a376bc --- /dev/null +++ b/test/unit/test_azure_sql_threat_detection_on_server.py @@ -0,0 +1,63 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock +from remediation_worker.jobs.azure_sql_threat_detection_on_server.azure_sql_threat_detection_on_server import ( + EnableSqlServerThreatProtection, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c8c268d7a550e1fb6560cc0", + "Service": "Sql", + "FindingInfo": { + "FindingId": "86c63989-4193-4785-b010-3fafb64e9d83", + "ObjectId": "sql_server_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Sql.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.Server.sql_server_name\\",\\"entityName\\":\\"sql_server_name\\",\\"entityType\\":\\"Azure.Sql.Server\\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Sql\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestEnableSqlServerThreatDetection(object): + def test_parse_payload(self, valid_payload): + params = EnableSqlServerThreatProtection().parse(valid_payload) + assert params["sql_server_name"] == "sql_server_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + action = EnableSqlServerThreatProtection() + assert action.remediate(client, "resource_group", "sql_server_name") == 0 + assert client.server_security_alert_policies.create_or_update.call_count == 1 + + call_args = client.server_security_alert_policies.create_or_update.call_args + updated_sql_server_parameters = call_args[1]["parameters"] + assert updated_sql_server_parameters.state == "Enabled" + + def test_remediate_with_exception(self): + client = Mock() + client.server_security_alert_policies.create_or_update.side_effect = Exception + action = EnableSqlServerThreatProtection() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index bd645ce..2705cd6 100644 --- a/tox.ini +++ b/tox.ini @@ -20,6 +20,7 @@ envlist = unit-azure-storage-allow-only-https unit-azure-storage-default-network-access-deny unit-azure-security-center-enable-ddos-protection + unit-azure-sql-threat-detection-on-server [testenv] passenv = @@ -145,4 +146,10 @@ deps = -r remediation_worker/jobs/azure_storage_default_network_access_deny/requ description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_security_center_enable_ddos_protection.py -deps = -r remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt \ No newline at end of file +deps = -r remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt + +[testenv:unit-azure-sql-threat-detection-on-server] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_sql_threat_detection_on_server.py +deps = -r remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt From e9a6b8d90387d70227233626a9e867c05b42db87 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni Date: Tue, 8 Dec 2020 01:20:24 +0530 Subject: [PATCH 22/34] Addressed the review comments --- .../azure_sql_auditing_on_server.py | 89 ++++++++++++------- .../minimum_permissions.json | 44 ++++----- 2 files changed, 80 insertions(+), 53 deletions(-) diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py index e4c7791..3c1b296 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py @@ -41,6 +41,38 @@ def get_random_string(length): return result_str +def create_storage_account( + resource_group_name, name, region, client_storage, +): + create_params = StorageAccountCreateParameters( + location=region, + sku=Sku(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), + kind="StorageV2", + enable_https_traffic_only=True, + network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), + ) + poller = client_storage.storage_accounts.begin_create( + resource_group_name=resource_group_name, + account_name=name, + parameters=create_params, + ) + return poller.result + + +def create_role_assignment( + stg_account_name, subscription_id, client_authorization, guid, Scope, principalId, +): + client_authorization.role_assignments.create( + scope=Scope, + role_assignment_name=guid, + parameters=RoleAssignmentCreateParameters( + role_definition_id=f"/subscriptions/{subscription_id}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe", + principal_id=principalId, + principal_type=PrincipalType.service_principal, + ), + ) + + class SqlServerEnableBlobAuditingPolicy(object): def parse(self, payload): """Parse payload received from Remediation Service. @@ -111,6 +143,9 @@ def remediate( logging.info( f"Assigning Azure Active Directory Identity to the SQL Database Server {sql_server_name}" ) + logging.info("executing client.servers.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" server_name={sql_server_name}") updated_server = client.servers.update( resource_group_name=resource_group_name, server_name=sql_server_name, @@ -123,41 +158,33 @@ def remediate( else: principalId = server.identity.principal_id - create_params = StorageAccountCreateParameters( - location=region, - sku=Sku(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), - kind="StorageV2", - enable_https_traffic_only=True, - network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), - ) stg_account_name = get_random_string(6) logging.info(f"Creating a storage account with name {stg_account_name}") + logging.info("executing client_storage.storage_accounts.begin_create") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" storage_account_name={stg_account_name}") - client_storage.storage_accounts.begin_create( - resource_group_name=resource_group_name, - account_name=stg_account_name, - parameters=create_params, + create_storage_account( + resource_group_name, stg_account_name, region, client_storage ) + guid = uuid.uuid4() + Scope = f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Storage/storageAccounts/{stg_account_name}" + logging.info( f"Creating a Role Assignment for Storage Account {stg_account_name} and assigning Storage Blob Data Contributer Role to the SQL Database Server {sql_server_name}" ) - guid = uuid.uuid4() - Scope = "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{resourceName}".format( - subscriptionId=subscription_id, - resourceGroupName=resource_group_name, - resourceName=stg_account_name, - ) - client_authorization.role_assignments.create( - scope=Scope, - role_assignment_name=guid, - parameters=RoleAssignmentCreateParameters( - role_definition_id="/subscriptions/{subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe".format( - subscriptionId=subscription_id - ), - principal_id=principalId, - principal_type=PrincipalType.service_principal, - ), + logging.info("executing client_authorization.role_assignments.create") + logging.info(f" scope={Scope}") + logging.info(f" role_assignment_name={guid}") + + create_role_assignment( + stg_account_name, + subscription_id, + client_authorization, + guid, + Scope, + principalId, ) logging.info("Enabling Server blob auditing policy for Azure SQL Server") @@ -172,9 +199,7 @@ def remediate( server_name=sql_server_name, parameters=ServerBlobAuditingPolicy( state=BlobAuditingPolicyState.enabled, - storage_endpoint="https://{storage_account_name}.blob.core.windows.net/".format( - storage_account_name=stg_account_name - ), + storage_endpoint=f"https://{stg_account_name}.blob.core.windows.net/", ), ) except Exception as e: @@ -204,7 +229,9 @@ def run(self, args): client = SqlManagementClient( credentials, params["subscription_id"], base_url=None ) - client_storage = StorageManagementClient(credentials1, params["subscription_id"]) + client_storage = StorageManagementClient( + credentials1, params["subscription_id"] + ) client_authorization = AuthorizationManagementClient( credentials, params["subscription_id"] ) diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json b/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json index 1d78586..39bf271 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json @@ -1,24 +1,24 @@ { - "properties": { - "roleName": "remediate_enable_server_blob_auditing_policies", - "description": "This role has required permissions to make changes to the SQL Server", - "assignableScopes": [ - ], - "permissions": [ - { - "actions": [ - "Microsoft.Sql/servers/read", - "Microsoft.Sql/servers/write", - "Microsoft.Sql/servers/auditingSettings/read", - "Microsoft.Sql/servers/auditingSettings/write", - "Microsoft.Storage/storageAccounts/write", - "Microsoft.Storage/storageAccounts/read", - "Microsoft.Authorization/roleAssignments/write" - ], - "notActions": [], - "dataActions": [], - "notDataActions": [] - } - ] - } + "properties": { + "roleName": "remediate_enable_server_blob_auditing_policies", + "description": "This role has required permissions to make changes to the SQL Server", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Sql/servers/read", + "Microsoft.Sql/servers/write", + "Microsoft.Sql/servers/auditingSettings/read", + "Microsoft.Sql/servers/auditingSettings/write", + "Microsoft.Storage/storageAccounts/write", + "Microsoft.Storage/storageAccounts/read", + "Microsoft.Authorization/roleAssignments/write" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] } +} From 2eb5aed8967299192c4ad730f0f2ed341ae41d40 Mon Sep 17 00:00:00 2001 From: Paul Allen Date: Thu, 10 Dec 2020 10:48:50 -0800 Subject: [PATCH 23/34] PLA-21822 fixing whitespace (#37) --- .../jobs/s3_enable_default_encryption/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt b/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt index 7ca0574..c91d2e0 100644 --- a/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt +++ b/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt @@ -3,4 +3,4 @@ boto3==1.14.9 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 From 58b4d339c11477e9933a6fe77bef1769f2639a69 Mon Sep 17 00:00:00 2001 From: Paul Allen Date: Thu, 10 Dec 2020 11:02:37 -0800 Subject: [PATCH 24/34] Pla 21822 fixing requirements whitespace (#38) * PLA-21822 fixing whitespace --- remediation_worker/jobs/ec2_close_port_3389/requirements.txt | 4 ++-- .../jobs/elb_enable_access_logs/requirements.txt | 3 ++- .../jobs/rds_backup_retention_30_days/requirements.txt | 4 ++-- .../jobs/s3_enable_access_logging/requirements.txt | 4 ++-- .../jobs/s3_enable_default_encryption/requirements.txt | 2 +- remediation_worker/jobs/s3_list_buckets/requirements.txt | 4 ++-- .../jobs/s3_remove_public_access/requirements.txt | 4 ++-- .../jobs/s3_remove_public_admin_acl/requirements.txt | 4 ++-- .../jobs/security_group_close_port_22/requirements.txt | 4 ++-- .../jobs/security_group_close_port_3389/requirements.txt | 4 ++-- .../jobs/security_group_close_port_5432/requirements.txt | 4 ++-- 11 files changed, 21 insertions(+), 20 deletions(-) diff --git a/remediation_worker/jobs/ec2_close_port_3389/requirements.txt b/remediation_worker/jobs/ec2_close_port_3389/requirements.txt index 7ca0574..b239388 100644 --- a/remediation_worker/jobs/ec2_close_port_3389/requirements.txt +++ b/remediation_worker/jobs/ec2_close_port_3389/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/elb_enable_access_logs/requirements.txt b/remediation_worker/jobs/elb_enable_access_logs/requirements.txt index d2dd560..42e124c 100644 --- a/remediation_worker/jobs/elb_enable_access_logs/requirements.txt +++ b/remediation_worker/jobs/elb_enable_access_logs/requirements.txt @@ -2,4 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ No newline at end of file + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 \ No newline at end of file diff --git a/remediation_worker/jobs/rds_backup_retention_30_days/requirements.txt b/remediation_worker/jobs/rds_backup_retention_30_days/requirements.txt index 7ca0574..b239388 100644 --- a/remediation_worker/jobs/rds_backup_retention_30_days/requirements.txt +++ b/remediation_worker/jobs/rds_backup_retention_30_days/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_enable_access_logging/requirements.txt b/remediation_worker/jobs/s3_enable_access_logging/requirements.txt index 7ca0574..b239388 100644 --- a/remediation_worker/jobs/s3_enable_access_logging/requirements.txt +++ b/remediation_worker/jobs/s3_enable_access_logging/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt b/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt index c91d2e0..b239388 100644 --- a/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt +++ b/remediation_worker/jobs/s3_enable_default_encryption/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_list_buckets/requirements.txt b/remediation_worker/jobs/s3_list_buckets/requirements.txt index 7ca0574..b239388 100644 --- a/remediation_worker/jobs/s3_list_buckets/requirements.txt +++ b/remediation_worker/jobs/s3_list_buckets/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_remove_public_access/requirements.txt b/remediation_worker/jobs/s3_remove_public_access/requirements.txt index 7ca0574..b239388 100644 --- a/remediation_worker/jobs/s3_remove_public_access/requirements.txt +++ b/remediation_worker/jobs/s3_remove_public_access/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/s3_remove_public_admin_acl/requirements.txt b/remediation_worker/jobs/s3_remove_public_admin_acl/requirements.txt index 7ca0574..b239388 100644 --- a/remediation_worker/jobs/s3_remove_public_admin_acl/requirements.txt +++ b/remediation_worker/jobs/s3_remove_public_admin_acl/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/security_group_close_port_22/requirements.txt b/remediation_worker/jobs/security_group_close_port_22/requirements.txt index 7ca0574..b239388 100644 --- a/remediation_worker/jobs/security_group_close_port_22/requirements.txt +++ b/remediation_worker/jobs/security_group_close_port_22/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/security_group_close_port_3389/requirements.txt b/remediation_worker/jobs/security_group_close_port_3389/requirements.txt index 7ca0574..b239388 100644 --- a/remediation_worker/jobs/security_group_close_port_3389/requirements.txt +++ b/remediation_worker/jobs/security_group_close_port_3389/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 diff --git a/remediation_worker/jobs/security_group_close_port_5432/requirements.txt b/remediation_worker/jobs/security_group_close_port_5432/requirements.txt index 7ca0574..b239388 100644 --- a/remediation_worker/jobs/security_group_close_port_5432/requirements.txt +++ b/remediation_worker/jobs/security_group_close_port_5432/requirements.txt @@ -2,5 +2,5 @@ boto3==1.14.9 \ --hash=sha256:185f7b36c16f76e501d8dfc5cd209113426e078e4968dd13cc355c916bc99597 \ --hash=sha256:51243ba0e976343ca0b98bb4a15fc3d588526220f6ba45bfed7ea45472b1e033 botocore==1.17.9 \ - --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 - --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 + --hash=sha256:7dd59bc766d567ca83bc6113aa139d92ba447738ccdfcd40788848553d329a52 \ + --hash=sha256:cd4bb2d96ff2ec6bf4fbcdb2f241d0fb6ba1e7955b4721cf1d81f13db02768b6 From 282fbb1af0d8c3ac760b8121d00f6b013d8f4a60 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni Date: Tue, 22 Dec 2020 01:05:24 +0530 Subject: [PATCH 25/34] PLA-20589: Prefixed Server nameto Storage Account name --- .../azure_sql_auditing_on_server.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py index 3c1b296..eba257a 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py @@ -34,10 +34,11 @@ logging.basicConfig(level=logging.INFO) -def get_random_string(length): +def get_random_string(length, prefix): letters = string.ascii_lowercase random_str = "".join(random.choice(letters) for i in range(length)) - result_str = "sqlauditing" + random_str + prefix = "".join(i for i in prefix if i != "-") + result_str = prefix + random_str return result_str @@ -158,7 +159,7 @@ def remediate( else: principalId = server.identity.principal_id - stg_account_name = get_random_string(6) + stg_account_name = get_random_string(6, sql_server_name) logging.info(f"Creating a storage account with name {stg_account_name}") logging.info("executing client_storage.storage_accounts.begin_create") logging.info(f" resource_group_name={resource_group_name}") From 6322dbe9f670d1739c6a59d7c5c36ae5534e451f Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Wed, 23 Dec 2020 23:11:34 +0530 Subject: [PATCH 26/34] PLA-21408: Azure Sql Database encryption enable (#33) --- .../azure_sql_data_encryption_on/README.md | 61 ++++++++ .../azure_sql_data_encryption_on/__init__.py | 0 .../azure_sql_data_encryption_on.py | 119 +++++++++++++++ .../constraints.txt | 136 ++++++++++++++++++ .../minimum_permissions.json | 19 +++ .../requirements-dev.txt | 33 +++++ .../requirements.txt | 6 + .../unit/test_azure_sql_data_encryption_on.py | 69 +++++++++ tox.ini | 9 +- 9 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 remediation_worker/jobs/azure_sql_data_encryption_on/README.md create mode 100644 remediation_worker/jobs/azure_sql_data_encryption_on/__init__.py create mode 100644 remediation_worker/jobs/azure_sql_data_encryption_on/azure_sql_data_encryption_on.py create mode 100644 remediation_worker/jobs/azure_sql_data_encryption_on/constraints.txt create mode 100644 remediation_worker/jobs/azure_sql_data_encryption_on/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_sql_data_encryption_on/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_sql_data_encryption_on/requirements.txt create mode 100644 test/unit/test_azure_sql_data_encryption_on.py diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/README.md b/remediation_worker/jobs/azure_sql_data_encryption_on/README.md new file mode 100644 index 0000000..cf41263 --- /dev/null +++ b/remediation_worker/jobs/azure_sql_data_encryption_on/README.md @@ -0,0 +1,61 @@ +# Enable Transparent Data Encryption for SQL Database + +This job enables Azure SQL Database Transparent Data Encryption. + +### Applicable Rule + +##### Rule ID: +5c8c268d7a550e1fb6560cc0 + +##### Rule Name: +SQL data encryption is disabled + +## Getting Started +### Prerequisites +The provided Azure service principal must have the following permissions: +`Microsoft.Sql/servers/databases/transparentDataEncryption/read` +`Microsoft.Sql/servers/databases/transparentDataEncryption/write` + +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) + +### Running the script +You may run this script using following commands: + +```shell script + pip install -r requirements.txt + python3 azure_sql_data_encryption_on.py +``` +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: + +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: + ```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python + ``` +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). + +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). +## Versioning +We use SemVer for versioning. For the versions available, see the tags on this repository. + +## Authors +* **VMware Secure State** - *Initial work* +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/graphs/contributors) who participated in this project. + +## License +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/__init__.py b/remediation_worker/jobs/azure_sql_data_encryption_on/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/azure_sql_data_encryption_on.py b/remediation_worker/jobs/azure_sql_data_encryption_on/azure_sql_data_encryption_on.py new file mode 100644 index 0000000..4db78f1 --- /dev/null +++ b/remediation_worker/jobs/azure_sql_data_encryption_on/azure_sql_data_encryption_on.py @@ -0,0 +1,119 @@ +import json +import os +import sys +import logging + +from azure.mgmt.sql import SqlManagementClient +from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.sql.models import TransparentDataEncryptionStatus + +logging.basicConfig(level=logging.INFO) + + +class EnableSqlDataEncryption(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + object_components = object_id.split(".") + server_name = object_components[0] + database_name = object_components[-1] + + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" sql_server_name: {server_name}") + logging.info(f" sql_database_name: {database_name}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + return { + "resource_group_name": resource_group_name, + "sql_server_name": server_name, + "subscription_id": subscription_id, + "sql_database_name": database_name, + "region": region, + } + + def remediate( + self, client, resource_group_name, sql_server_name, sql_database_name + ): + """Enable Transparent Data Encryption for SQL Dtabase + :param client: Instance of the Azure SqlManagementClient. + :param resource_group_name: The name of the resource group to which the SQL Server belongs. + :param sql_server_name: The name of the SQL Server. + :param sql_database_name: The name of the SQL Database. + :type resource_group_name: str. + :type sql_server_name: str. + :type sql_database_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + + logging.info("Enabling Transparent Data Encryption for SQL Dtabase") + + try: + logging.info( + " executing client.transparent_data_encryptions.create_or_update" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" server_name={sql_server_name}") + logging.info(f" database_name={sql_database_name}") + + client.transparent_data_encryptions.create_or_update( + resource_group_name=resource_group_name, + server_name=sql_server_name, + database_name=sql_database_name, + status=TransparentDataEncryptionStatus.enabled, + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + return 0 + + def run(self, args): + """Run the remediation job. + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ServicePrincipalCredentials( + client_id=os.environ.get("AZURE_CLIENT_ID"), + secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant=os.environ.get("AZURE_TENANT_ID"), + ) + + client = SqlManagementClient(credentials, params["subscription_id"]) + return self.remediate( + client, + params["resource_group_name"], + params["sql_server_name"], + params["sql_database_name"], + ) + + +if __name__ == "__main__": + sys.exit(EnableSqlDataEncryption().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/constraints.txt b/remediation_worker/jobs/azure_sql_data_encryption_on/constraints.txt new file mode 100644 index 0000000..e7f696c --- /dev/null +++ b/remediation_worker/jobs/azure_sql_data_encryption_on/constraints.txt @@ -0,0 +1,136 @@ +adal==1.2.5 \ + --hash=sha256:7492aff8f0ba7dd4e1c477303295c645141540fff34c3ca6de0a0b0e6c1c122a \ + --hash=sha256:8003ba03ef04170195b3eddda8a5ab43649ef2c5f0287023d515affb1ccfcfc3 +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.2 \ + --hash=sha256:621b53271f7988b766f8a7d7f7a2c44241e3d2c1d8db13e68089d6da6241748e \ + --hash=sha256:be23d411e19874f375c2ef0327c452be10b1e9a1023ac6afe334598f2920136b +azure-mgmt-core==1.2.1 \ + --hash=sha256:a3906fa77edfedfcc3229dc3b69489d5ed63b107c7eacbc50092e6cbfbfd83f0 \ + --hash=sha256:bd4503a2d81b86780f15936af2e4244c1345062f4c2422f0b377b56cb80d7796 +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-sql==0.24.0 \ + --hash=sha256:347f6b7d5e252d48331658fd5febefdacbc341641918df3111c735f4a6906e5e \ + --hash=sha256:da391ed00d82cd8e20ca50affdc43b99fd9a7919b54a3a0d53c73cb41eea09d3 +azure-mgmt-network==16.0.0 \ + --hash=sha256:6159a8c44590cc58841690c27c7d4acb0cd9ad0a1e5178c1d35e0f48e3c3c0e9 \ + --hash=sha256:c0e8358e9d530790dbf3efef6b31bce26e664de5096cbd84c62845067da815d1 +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.3 \ + --hash=sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d \ + --hash=sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b \ + --hash=sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4 \ + --hash=sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f \ + --hash=sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3 \ + --hash=sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579 \ + --hash=sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537 \ + --hash=sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e \ + --hash=sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05 \ + --hash=sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171 \ + --hash=sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca \ + --hash=sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522 \ + --hash=sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c \ + --hash=sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc \ + --hash=sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d \ + --hash=sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808 \ + --hash=sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828 \ + --hash=sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869 \ + --hash=sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d \ + --hash=sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9 \ + --hash=sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0 \ + --hash=sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc \ + --hash=sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15 \ + --hash=sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c \ + --hash=sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a \ + --hash=sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3 \ + --hash=sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1 \ + --hash=sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768 \ + --hash=sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d \ + --hash=sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b \ + --hash=sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e \ + --hash=sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d \ + --hash=sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730 \ + --hash=sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394 \ + --hash=sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1 \ + --hash=sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.2.1 \ + --hash=sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538 \ + --hash=sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f \ + --hash=sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77 \ + --hash=sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b \ + --hash=sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33 \ + --hash=sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e \ + --hash=sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb \ + --hash=sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e \ + --hash=sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7 \ + --hash=sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297 \ + --hash=sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d \ + --hash=sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7 \ + --hash=sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b \ + --hash=sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7 \ + --hash=sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4 \ + --hash=sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8 \ + --hash=sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b \ + --hash=sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851 \ + --hash=sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13 \ + --hash=sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b \ + --hash=sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3 \ + --hash=sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal==1.5.1 \ + --hash=sha256:7efb0256c96a7b2eadab49ce29ecdb91352a91440c12a40bed44303724b62fda \ + --hash=sha256:d84074a997e6fb2a47e22815dce376adcc8790838b6b1bf1fcea29378e2bf3eb +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msrest==0.6.19 \ + --hash=sha256:55f8c3940bc5dc609f8cf9fcd639444716cc212a943606756272e0d0017bbb5b \ + --hash=sha256:87aa64948c3ef3dbf6f6956d2240493e68d714e4621b92b65b3c4d5808297929 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +PyJWT==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +urllib3==1.25.11 \ + --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ + --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/minimum_permissions.json b/remediation_worker/jobs/azure_sql_data_encryption_on/minimum_permissions.json new file mode 100644 index 0000000..3e3d15c --- /dev/null +++ b/remediation_worker/jobs/azure_sql_data_encryption_on/minimum_permissions.json @@ -0,0 +1,19 @@ +{ + "properties": { + "roleName": "remediate_enable_sql_data_encrytion", + "description": "This role has required permissions to make changes to the Azure SQL Database", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Sql/servers/databases/transparentDataEncryption/read", + "Microsoft.Sql/servers/databases/transparentDataEncryption/write" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } +} diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/requirements-dev.txt b/remediation_worker/jobs/azure_sql_data_encryption_on/requirements-dev.txt new file mode 100644 index 0000000..759f2ca --- /dev/null +++ b/remediation_worker/jobs/azure_sql_data_encryption_on/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.1.2 \ + --hash=sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe \ + --hash=sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +zipp==3.4.0 \ + --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ + --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/requirements.txt b/remediation_worker/jobs/azure_sql_data_encryption_on/requirements.txt new file mode 100644 index 0000000..074579f --- /dev/null +++ b/remediation_worker/jobs/azure_sql_data_encryption_on/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-sql==0.24.0 \ + --hash=sha256:347f6b7d5e252d48331658fd5febefdacbc341641918df3111c735f4a6906e5e \ + --hash=sha256:da391ed00d82cd8e20ca50affdc43b99fd9a7919b54a3a0d53c73cb41eea09d3 diff --git a/test/unit/test_azure_sql_data_encryption_on.py b/test/unit/test_azure_sql_data_encryption_on.py new file mode 100644 index 0000000..e9976ef --- /dev/null +++ b/test/unit/test_azure_sql_data_encryption_on.py @@ -0,0 +1,69 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock +from remediation_worker.jobs.azure_sql_data_encryption_on.azure_sql_data_encryption_on import ( + EnableSqlDataEncryption, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c8c268d7a550e1fb6560cc0", + "Service": "Sql", + "FindingInfo": { + "FindingId": "86c63989-4193-4785-b010-3fafb64e9d83", + "ObjectId": "sql_server_name.sql_database_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Sql.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.Database.sql_server_name.sql_database_name\\",\\"entityName\\":\\"sql_server_name.sql_database_name\\",\\"entityType\\":\\"Azure.Sql.Database\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Sql\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestEnableSqlDataEncryption(object): + def test_parse_payload(self, valid_payload): + params = EnableSqlDataEncryption().parse(valid_payload) + assert params["sql_server_name"] == "sql_server_name" + assert params["sql_database_name"] == "sql_database_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + action = EnableSqlDataEncryption() + assert ( + action.remediate( + client, "resource_group", "sql_server_name", "sql_database_name" + ) + == 0 + ) + assert client.transparent_data_encryptions.create_or_update.call_count == 1 + + call_args = client.transparent_data_encryptions.create_or_update.call_args + updated_sql_data_encryption_status = call_args[1]["status"] + assert updated_sql_data_encryption_status == "Enabled" + + def test_remediate_with_exception(self): + client = Mock() + client.transparent_data_encryptions.create_or_update.side_effect = Exception + action = EnableSqlDataEncryption() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index 7d031f2..871765e 100644 --- a/tox.ini +++ b/tox.ini @@ -20,6 +20,7 @@ envlist = unit-azure-storage-allow-only-https unit-azure-storage-default-network-access-deny unit-azure-security-center-enable-ddos-protection + unit-azure-sql-data-encryption-on unit-azure-sql-auditing-on-server unit-azure-sql-threat-detection-on-server @@ -149,6 +150,12 @@ changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_security_center_enable_ddos_protection.py deps = -r remediation_worker/jobs/azure_security_center_enable_ddos_protection/requirements-dev.txt +[testenv:unit-azure-sql-data-encryption-on] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_sql_data_encryption_on.py +deps = -r remediation_worker/jobs/azure_sql_data_encryption_on/requirements-dev.txt + [testenv:unit-azure-sql-auditing-on-server] description = Unit test the project changedir = test @@ -159,4 +166,4 @@ deps = -r remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev. description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_sql_threat_detection_on_server.py -deps = -r remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt +deps = -r remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt \ No newline at end of file From 6cbdef480c432c56b8489e5619fbfdd8e882470a Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Tue, 12 Jan 2021 01:29:20 +0530 Subject: [PATCH 27/34] Changed Storage account name (#41) * Changed Storage account name * PLA-22317 - Changed the function name --- .../azure_sql_auditing_on_server.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py index eba257a..8146bf7 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py @@ -34,11 +34,11 @@ logging.basicConfig(level=logging.INFO) -def get_random_string(length, prefix): - letters = string.ascii_lowercase - random_str = "".join(random.choice(letters) for i in range(length)) - prefix = "".join(i for i in prefix if i != "-") - result_str = prefix + random_str +def generate_storage_account_name(prefix): + prefix = "".join(i for i in prefix if i.islower() or i.isdigit()) + if len(prefix) >= 15: + prefix = str(prefix[:14]) + result_str = prefix + "auditlogs" return result_str @@ -57,7 +57,7 @@ def create_storage_account( account_name=name, parameters=create_params, ) - return poller.result + return poller.result() def create_role_assignment( @@ -127,10 +127,12 @@ def remediate( ): """Enable Server blob auditing policy for Azure SQL Server :param client: Instance of the Azure SqlManagementClient. + :param client_storage: Instance of the Azure StorageManagementClient. :param resource_group_name: The name of the resource group to which the SQL Server belongs. :param sql_server_name: The name of the SQL Server. :type resource_group_name: str. :type sql_server_name: str. + :type region: str. :returns: Integer signaling success or failure :rtype: int :raises: msrestazure.azure_exceptions.CloudError @@ -159,7 +161,7 @@ def remediate( else: principalId = server.identity.principal_id - stg_account_name = get_random_string(6, sql_server_name) + stg_account_name = generate_storage_account_name(sql_server_name) logging.info(f"Creating a storage account with name {stg_account_name}") logging.info("executing client_storage.storage_accounts.begin_create") logging.info(f" resource_group_name={resource_group_name}") @@ -221,7 +223,7 @@ def run(self, args): secret=os.environ.get("AZURE_CLIENT_SECRET"), tenant=os.environ.get("AZURE_TENANT_ID"), ) - credentials1 = ClientSecretCredential( + credentials_stg = ClientSecretCredential( client_id=os.environ.get("AZURE_CLIENT_ID"), client_secret=os.environ.get("AZURE_CLIENT_SECRET"), tenant_id=os.environ.get("AZURE_TENANT_ID"), @@ -231,7 +233,7 @@ def run(self, args): credentials, params["subscription_id"], base_url=None ) client_storage = StorageManagementClient( - credentials1, params["subscription_id"] + credentials_stg, params["subscription_id"] ) client_authorization = AuthorizationManagementClient( credentials, params["subscription_id"] From c5aae322a9e0fcf5a93127f0b6a11008fa02c522 Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Tue, 12 Jan 2021 01:32:18 +0530 Subject: [PATCH 28/34] PLA-21661 - Remediation job to enable logging for Key Vault (#42) --- .../README.md | 62 ++++++ .../__init__.py | 0 ..._key_vault_logging_for_keyvault_enabled.py | 206 ++++++++++++++++++ .../constraints.txt | 139 ++++++++++++ .../minimum_permissions.json | 22 ++ .../requirements-dev.txt | 33 +++ .../requirements.txt | 12 + ..._key_vault_logging_for_keyvault_enabled.py | 72 ++++++ tox.ini | 9 +- 9 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md create mode 100644 remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/__init__.py create mode 100644 remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py create mode 100644 remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt create mode 100644 remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt create mode 100644 test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md new file mode 100644 index 0000000..73e3e1e --- /dev/null +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md @@ -0,0 +1,62 @@ +# Enable Logging For Keyvault + +This job enables Key Vault Logging. + +### Applicable Rule + +##### Rule ID: +5c8c26687a550e1fb6560c72 + +##### Rule Name: +Logging For Keyvault Enabled + +## Getting Started +### Prerequisites +The provided Azure service principal must have the following permissions: +`Microsoft.Storage/storageAccounts/read` +`Microsoft.Storage/storageAccounts/write` +`Microsoft.Insights/DiagnosticSettings/Write` + +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) + +### Running the script +You may run this script using following commands: + +```shell script + pip install -r requirements.txt + python3 azure_key_vault_logging_for_keyvault_enabled.py +``` +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: + +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: + ```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python + ``` +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). + +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). +## Versioning +We use SemVer for versioning. For the versions available, see the tags on this repository. + +## Authors +* **VMware Secure State** - *Initial work* +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/graphs/contributors) who participated in this project. + +## License +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/__init__.py b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py new file mode 100644 index 0000000..01e2fff --- /dev/null +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py @@ -0,0 +1,206 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging + +from azure.identity import ClientSecretCredential +from azure.mgmt.keyvault import KeyVaultManagementClient +from azure.mgmt.monitor import MonitorClient +from azure.mgmt.storage import StorageManagementClient +from azure.mgmt.storage.models import ( + StorageAccountCreateParameters, + NetworkRuleSet, + Sku, + SkuName, + SkuTier, + DefaultAction, +) +from azure.mgmt.monitor.models import ( + DiagnosticSettingsResource, + LogSettings, + RetentionPolicy, +) + +logging.basicConfig(level=logging.INFO) + + +def generate_name(prefix): + prefix = "".join(i for i in prefix if i.islower() or i.isdigit()) + if len(prefix) >= 12: + prefix = str(prefix[:11]) + result_str = prefix + "keyvaultlogs" + return result_str + + +def create_storage_account( + resource_group_name, name, region, storage_client, +): + create_params = StorageAccountCreateParameters( + location=region, + sku=Sku(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), + kind="StorageV2", + enable_https_traffic_only=True, + network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), + ) + poller = storage_client.storage_accounts.begin_create( + resource_group_name=resource_group_name, + account_name=name, + parameters=create_params, + ) + return poller.result() + + +class EnableKeyVaultLogging(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + + object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" account_name: {object_id}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + + return { + "resource_group_name": resource_group_name, + "key_vault_name": object_id, + "subscription_id": subscription_id, + "region": region, + } + + def remediate( + self, + keyvault_client, + monitor_client, + storage_client, + resource_group_name, + key_vault_name, + region, + ): + """Enable key vault logging + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param storage_client: Instance of the Azure StorageManagementClient. + :param monitor_client: Instance of the Azure MonitorClient. + :param resource_group_name: The name of the resource group to which the storage account belongs. + :param key_vault_name: The name of the key vault. + :param region: The region in which the key vault is present. + :type resource_group_name: str. + :type key_vault_name: str. + :type region: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + key_vault = keyvault_client.vaults.get( + resource_group_name=resource_group_name, vault_name=key_vault_name, + ) + try: + stg_name = generate_name(key_vault_name) + logging.info(" Creating a Storage Account") + logging.info(" executing client_storage.storage_accounts.begin_create") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={stg_name}") + stg_account = create_storage_account( + resource_group_name, stg_name, region, storage_client + ) + log = LogSettings( + category="AuditEvent", + enabled=True, + retention_policy=RetentionPolicy(enabled=True, days=180), + ) + + logging.info(" Creating a Diagnostic setting for key vault logs") + logging.info( + " executing monitor_client.diagnostic_settings.create_or_update" + ) + logging.info(f" resource_uri={key_vault.id}") + logging.info(f" name={key_vault_name}") + + monitor_client.diagnostic_settings.create_or_update( + resource_uri=key_vault.id, + name=key_vault_name, + parameters=DiagnosticSettingsResource( + storage_account_id=stg_account.id, logs=[log], + ), + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def run(self, args): + """Run the remediation job. + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ClientSecretCredential( + client_id=os.environ.get("AZURE_CLIENT_ID"), + client_secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant_id=os.environ.get("AZURE_TENANT_ID"), + ) + credentials_stg = ClientSecretCredential( + client_id=os.environ.get("AZURE_CLIENT_ID"), + client_secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant_id=os.environ.get("AZURE_TENANT_ID"), + ) + storage_client = StorageManagementClient( + credentials_stg, params["subscription_id"] + ) + keyvault_client = KeyVaultManagementClient( + credentials, params["subscription_id"] + ) + monitor_client = MonitorClient(credentials, params["subscription_id"]) + return self.remediate( + keyvault_client, + monitor_client, + storage_client, + params["resource_group_name"], + params["key_vault_name"], + params["region"], + ) + + +if __name__ == "__main__": + sys.exit(EnableKeyVaultLogging().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt new file mode 100644 index 0000000..b184eeb --- /dev/null +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt @@ -0,0 +1,139 @@ +adal==1.2.5 \ + --hash=sha256:7492aff8f0ba7dd4e1c477303295c645141540fff34c3ca6de0a0b0e6c1c122a \ + --hash=sha256:8003ba03ef04170195b3eddda8a5ab43649ef2c5f0287023d515affb1ccfcfc3 +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.2 \ + --hash=sha256:621b53271f7988b766f8a7d7f7a2c44241e3d2c1d8db13e68089d6da6241748e \ + --hash=sha256:be23d411e19874f375c2ef0327c452be10b1e9a1023ac6afe334598f2920136b +azure-mgmt-core==1.2.1 \ + --hash=sha256:a3906fa77edfedfcc3229dc3b69489d5ed63b107c7eacbc50092e6cbfbfd83f0 \ + --hash=sha256:bd4503a2d81b86780f15936af2e4244c1345062f4c2422f0b377b56cb80d7796 +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-network==16.0.0 \ + --hash=sha256:6159a8c44590cc58841690c27c7d4acb0cd9ad0a1e5178c1d35e0f48e3c3c0e9 \ + --hash=sha256:c0e8358e9d530790dbf3efef6b31bce26e664de5096cbd84c62845067da815d1 +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +azure-mgmt-monitor==1.0.1 \ + --hash=sha256:04bd89d74fe47f966b09e3256ffefcfa5c1a51057a6b33c092afe5ae17a1a7b7 \ + --hash=sha256:92db4d133f58be900e5a2a9e3f44f4f0c068717bf534bf49c97e070c0280d203 +azure-mgmt-keyvault==8.0.0 \ + --hash=sha256:2c974c6114d8d27152642c82a975812790a5e86ccf609bf370a476d9ea0d2e7d \ + --hash=sha256:99da24e9455f195d1e45a605af04d663e15cb5d5eb005b0ef60638474bbb2b3f +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.3 \ + --hash=sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d \ + --hash=sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b \ + --hash=sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4 \ + --hash=sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f \ + --hash=sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3 \ + --hash=sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579 \ + --hash=sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537 \ + --hash=sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e \ + --hash=sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05 \ + --hash=sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171 \ + --hash=sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca \ + --hash=sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522 \ + --hash=sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c \ + --hash=sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc \ + --hash=sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d \ + --hash=sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808 \ + --hash=sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828 \ + --hash=sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869 \ + --hash=sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d \ + --hash=sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9 \ + --hash=sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0 \ + --hash=sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc \ + --hash=sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15 \ + --hash=sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c \ + --hash=sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a \ + --hash=sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3 \ + --hash=sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1 \ + --hash=sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768 \ + --hash=sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d \ + --hash=sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b \ + --hash=sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e \ + --hash=sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d \ + --hash=sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730 \ + --hash=sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394 \ + --hash=sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1 \ + --hash=sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.2.1 \ + --hash=sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538 \ + --hash=sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f \ + --hash=sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77 \ + --hash=sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b \ + --hash=sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33 \ + --hash=sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e \ + --hash=sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb \ + --hash=sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e \ + --hash=sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7 \ + --hash=sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297 \ + --hash=sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d \ + --hash=sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7 \ + --hash=sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b \ + --hash=sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7 \ + --hash=sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4 \ + --hash=sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8 \ + --hash=sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b \ + --hash=sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851 \ + --hash=sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13 \ + --hash=sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b \ + --hash=sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3 \ + --hash=sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal==1.5.1 \ + --hash=sha256:7efb0256c96a7b2eadab49ce29ecdb91352a91440c12a40bed44303724b62fda \ + --hash=sha256:d84074a997e6fb2a47e22815dce376adcc8790838b6b1bf1fcea29378e2bf3eb +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msrest==0.6.19 \ + --hash=sha256:55f8c3940bc5dc609f8cf9fcd639444716cc212a943606756272e0d0017bbb5b \ + --hash=sha256:87aa64948c3ef3dbf6f6956d2240493e68d714e4621b92b65b3c4d5808297929 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +PyJWT==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +urllib3==1.25.11 \ + --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ + --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json new file mode 100644 index 0000000..e59e8be --- /dev/null +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json @@ -0,0 +1,22 @@ +{ + "properties": { + "roleName": "remediate_set_default_network_access_deny", + "description": "This role has required permissions to make changes to the storage account", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Storage/storageAccounts/read", + "Microsoft.Storage/storageAccounts/write", + "Microsoft.Insights/DiagnosticSettings/Write", + "Microsoft.KeyVault/vaults/read", + "Microsoft.KeyVault/vaults/write" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } + } diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt new file mode 100644 index 0000000..759f2ca --- /dev/null +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.1.2 \ + --hash=sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe \ + --hash=sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +zipp==3.4.0 \ + --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ + --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt new file mode 100644 index 0000000..fbfcae4 --- /dev/null +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt @@ -0,0 +1,12 @@ +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +azure-mgmt-monitor==1.0.1 \ + --hash=sha256:04bd89d74fe47f966b09e3256ffefcfa5c1a51057a6b33c092afe5ae17a1a7b7 \ + --hash=sha256:92db4d133f58be900e5a2a9e3f44f4f0c068717bf534bf49c97e070c0280d203 +azure-mgmt-keyvault==8.0.0 \ + --hash=sha256:2c974c6114d8d27152642c82a975812790a5e86ccf609bf370a476d9ea0d2e7d \ + --hash=sha256:99da24e9455f195d1e45a605af04d663e15cb5d5eb005b0ef60638474bbb2b3f diff --git a/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py b/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py new file mode 100644 index 0000000..73901ef --- /dev/null +++ b/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py @@ -0,0 +1,72 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock +from remediation_worker.jobs.azure_key_vault_logging_for_keyvault_enabled.azure_key_vault_logging_for_keyvault_enabled import ( + EnableKeyVaultLogging, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "5c8c26687a550e1fb6560c72", + "Service": "KeyVault", + "FindingInfo": { + "FindingId": "9b2da5e9-bb96-4298-b2c1-e6c341b44c5f", + "ObjectId": "key_vault_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.KeyVault.d687b1a3-9b78-43b1-a17b-7de297fd1fce.accelerators-team-resources.Vault.key_vault_name\\",\\"entityName\\":\\"key_vault_name\\",\\"entityType\\":\\"Azure.KeyVault.Vault\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"KeyVault\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestKeyVaultLoggingEnabled(object): + def test_parse_payload(self, valid_payload): + params = EnableKeyVaultLogging().parse(valid_payload) + assert params["key_vault_name"] == "key_vault_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + storage_client = Mock() + keyvault_client = Mock() + monitor_client = Mock() + action = EnableKeyVaultLogging() + assert ( + action.remediate( + keyvault_client, + monitor_client, + storage_client, + "resource_group", + "key_vault_name", + "region", + ) + == 0 + ) + assert storage_client.storage_accounts.begin_create.call_count == 1 + assert monitor_client.diagnostic_settings.create_or_update.call_count == 1 + + def test_remediate_with_exception(self): + client = Mock() + client.storage_accounts.update.side_effect = Exception + action = EnableKeyVaultLogging() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index 871765e..ea914af 100644 --- a/tox.ini +++ b/tox.ini @@ -23,6 +23,7 @@ envlist = unit-azure-sql-data-encryption-on unit-azure-sql-auditing-on-server unit-azure-sql-threat-detection-on-server + unit-azure-key-vault-logging-for-keyvault-enabled [testenv] passenv = @@ -166,4 +167,10 @@ deps = -r remediation_worker/jobs/azure_sql_auditing_on_server/requirements-dev. description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_sql_threat_detection_on_server.py -deps = -r remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt \ No newline at end of file +deps = -r remediation_worker/jobs/azure_sql_threat_detection_on_server/requirements-dev.txt + +[testenv:unit-azure-key-vault-logging-for-keyvault-enabled] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_key_vault_logging_for_keyvault_enabled.py +deps = -r remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt From 7495c162414dbb04bcc73c0c451ca0acbce07056 Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Thu, 14 Jan 2021 06:27:28 +0530 Subject: [PATCH 29/34] PLA-22299 - Remediation job to enable soft delete for Storage Account Blob Service (#43) --- .../README.md | 2 + .../README.md | 61 ++++++++ .../__init__.py | 0 .../azure_storage_soft_delete_not_enabled.py | 120 +++++++++++++++ .../constraints.txt | 139 ++++++++++++++++++ .../minimum_permissions.json | 19 +++ .../requirements-dev.txt | 33 +++++ .../requirements.txt | 6 + ...t_azure_storage_soft_delete_not_enabled.py | 65 ++++++++ tox.ini | 7 + 10 files changed, 452 insertions(+) create mode 100644 remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md create mode 100644 remediation_worker/jobs/azure_storage_soft_delete_not_enabled/__init__.py create mode 100644 remediation_worker/jobs/azure_storage_soft_delete_not_enabled/azure_storage_soft_delete_not_enabled.py create mode 100644 remediation_worker/jobs/azure_storage_soft_delete_not_enabled/constraints.txt create mode 100644 remediation_worker/jobs/azure_storage_soft_delete_not_enabled/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements.txt create mode 100644 test/unit/test_azure_storage_soft_delete_not_enabled.py diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md index 73e3e1e..0232689 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md @@ -16,6 +16,8 @@ The provided Azure service principal must have the following permissions: `Microsoft.Storage/storageAccounts/read` `Microsoft.Storage/storageAccounts/write` `Microsoft.Insights/DiagnosticSettings/Write` +`Microsoft.KeyVault/vaults/read` +`Microsoft.KeyVault/vaults/write` A sample role with requisite permissions can be found [here](minimum_permissions.json) diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md new file mode 100644 index 0000000..7e84da9 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md @@ -0,0 +1,61 @@ +# Enable Soft Delete for Storage Account BlobService + +This job enables Soft Delete for Storage Account BlobService. + +### Applicable Rule + +##### Rule ID: +643eb5fc-7747-4df4-b217-41c4e97e0c07 + +##### Rule Name: +Storage account blob service is not configured with soft delete + +## Getting Started +### Prerequisites +The provided Azure service principal must have the following permissions: +`Microsoft.Storage/storageAccounts/blobServices/read` +`Microsoft.Storage/storageAccounts/blobServices/write` + +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) + +### Running the script +You may run this script using following commands: + +```shell script + pip install -r requirements.txt + python3 azure_storage_soft_delete_not_enabled.py +``` +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: + +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: + ```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python + ``` +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). + +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). +## Versioning +We use SemVer for versioning. For the versions available, see the tags on this repository. + +## Authors +* **VMware Secure State** - *Initial work* +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/graphs/contributors) who participated in this project. + +## License +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/__init__.py b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/azure_storage_soft_delete_not_enabled.py b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/azure_storage_soft_delete_not_enabled.py new file mode 100644 index 0000000..992e225 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/azure_storage_soft_delete_not_enabled.py @@ -0,0 +1,120 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging + +from azure.mgmt.storage import StorageManagementClient +from azure.identity import ClientSecretCredential +from azure.mgmt.storage.models import BlobServiceProperties, DeleteRetentionPolicy + +logging.basicConfig(level=logging.INFO) + + +class EnableBlobServiceSoftDelete(object): + def parse(self, payload): + """Parse payload received from Remediation Service. + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + object_components = object_id.split(".") + account_name = object_components[0] + + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" account_name: {account_name}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + return { + "resource_group_name": resource_group_name, + "account_name": account_name, + "subscription_id": subscription_id, + "region": region, + } + + def remediate(self, client, resource_group_name, account_name): + """Enable Soft Delete for Storage Account Blob Service + :param client: Instance of the Azure StorageManagementClient. + :param resource_group_name: The name of the resource group to which the storage account belongs + :param account_name: The name of the storage account. + :type resource_group_name: str. + :type account_name: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + logging.info("Enabling Soft Delete for Storage Account Blob Service") + try: + logging.info(" executing client.blob_services.set_service_properties") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={account_name}") + + client.blob_services.set_service_properties( + resource_group_name=resource_group_name, + account_name=account_name, + parameters=BlobServiceProperties( + delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7) + ), + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def run(self, args): + """Run the remediation job. + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + + credentials = ClientSecretCredential( + client_id=os.environ.get("AZURE_CLIENT_ID"), + client_secret=os.environ.get("AZURE_CLIENT_SECRET"), + tenant_id=os.environ.get("AZURE_TENANT_ID"), + ) + + client = StorageManagementClient(credentials, params["subscription_id"]) + return self.remediate( + client, params["resource_group_name"], params["account_name"], + ) + + +if __name__ == "__main__": + sys.exit(EnableBlobServiceSoftDelete().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/constraints.txt b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/constraints.txt new file mode 100644 index 0000000..7a72ec4 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/constraints.txt @@ -0,0 +1,139 @@ +adal==1.2.5 \ + --hash=sha256:7492aff8f0ba7dd4e1c477303295c645141540fff34c3ca6de0a0b0e6c1c122a \ + --hash=sha256:8003ba03ef04170195b3eddda8a5ab43649ef2c5f0287023d515affb1ccfcfc3 +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.2 \ + --hash=sha256:621b53271f7988b766f8a7d7f7a2c44241e3d2c1d8db13e68089d6da6241748e \ + --hash=sha256:be23d411e19874f375c2ef0327c452be10b1e9a1023ac6afe334598f2920136b +azure-mgmt-core==1.2.1 \ + --hash=sha256:a3906fa77edfedfcc3229dc3b69489d5ed63b107c7eacbc50092e6cbfbfd83f0 \ + --hash=sha256:bd4503a2d81b86780f15936af2e4244c1345062f4c2422f0b377b56cb80d7796 +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-sql==0.24.0 \ + --hash=sha256:347f6b7d5e252d48331658fd5febefdacbc341641918df3111c735f4a6906e5e \ + --hash=sha256:da391ed00d82cd8e20ca50affdc43b99fd9a7919b54a3a0d53c73cb41eea09d3 +azure-mgmt-network==16.0.0 \ + --hash=sha256:6159a8c44590cc58841690c27c7d4acb0cd9ad0a1e5178c1d35e0f48e3c3c0e9 \ + --hash=sha256:c0e8358e9d530790dbf3efef6b31bce26e664de5096cbd84c62845067da815d1 +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +azure-mgmt-authorization==0.61.0 \ + --hash=sha256:38f8afcd5c0065e598305de15dbb6b81521ac1e05216049f10ca32a435fd5817 \ + --hash=sha256:f5cceea3add04e9445ea88492f15eecf6c126f0406d967c95f6e48b79be8db75 +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.3 \ + --hash=sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d \ + --hash=sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b \ + --hash=sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4 \ + --hash=sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f \ + --hash=sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3 \ + --hash=sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579 \ + --hash=sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537 \ + --hash=sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e \ + --hash=sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05 \ + --hash=sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171 \ + --hash=sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca \ + --hash=sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522 \ + --hash=sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c \ + --hash=sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc \ + --hash=sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d \ + --hash=sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808 \ + --hash=sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828 \ + --hash=sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869 \ + --hash=sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d \ + --hash=sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9 \ + --hash=sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0 \ + --hash=sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc \ + --hash=sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15 \ + --hash=sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c \ + --hash=sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a \ + --hash=sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3 \ + --hash=sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1 \ + --hash=sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768 \ + --hash=sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d \ + --hash=sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b \ + --hash=sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e \ + --hash=sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d \ + --hash=sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730 \ + --hash=sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394 \ + --hash=sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1 \ + --hash=sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.2.1 \ + --hash=sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538 \ + --hash=sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f \ + --hash=sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77 \ + --hash=sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b \ + --hash=sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33 \ + --hash=sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e \ + --hash=sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb \ + --hash=sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e \ + --hash=sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7 \ + --hash=sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297 \ + --hash=sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d \ + --hash=sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7 \ + --hash=sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b \ + --hash=sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7 \ + --hash=sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4 \ + --hash=sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8 \ + --hash=sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b \ + --hash=sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851 \ + --hash=sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13 \ + --hash=sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b \ + --hash=sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3 \ + --hash=sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal==1.5.1 \ + --hash=sha256:7efb0256c96a7b2eadab49ce29ecdb91352a91440c12a40bed44303724b62fda \ + --hash=sha256:d84074a997e6fb2a47e22815dce376adcc8790838b6b1bf1fcea29378e2bf3eb +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msrest==0.6.19 \ + --hash=sha256:55f8c3940bc5dc609f8cf9fcd639444716cc212a943606756272e0d0017bbb5b \ + --hash=sha256:87aa64948c3ef3dbf6f6956d2240493e68d714e4621b92b65b3c4d5808297929 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +PyJWT==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +urllib3==1.25.11 \ + --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ + --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/minimum_permissions.json b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/minimum_permissions.json new file mode 100644 index 0000000..6a6b52f --- /dev/null +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/minimum_permissions.json @@ -0,0 +1,19 @@ +{ + "properties": { + "roleName": "remediate_set_default_network_access_deny", + "description": "This role has required permissions to make changes to the storage account", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Storage/storageAccounts/blobServices/write", + "Microsoft.Storage/storageAccounts/blobServices/read" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } + } diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt new file mode 100644 index 0000000..8be29aa --- /dev/null +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==17.4.0 \ + --hash=sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9 \ + --hash=sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450 +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.1.2 \ + --hash=sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe \ + --hash=sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +zipp==3.4.0 \ + --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ + --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements.txt b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements.txt new file mode 100644 index 0000000..1170c49 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements.txt @@ -0,0 +1,6 @@ +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 diff --git a/test/unit/test_azure_storage_soft_delete_not_enabled.py b/test/unit/test_azure_storage_soft_delete_not_enabled.py new file mode 100644 index 0000000..d82e331 --- /dev/null +++ b/test/unit/test_azure_storage_soft_delete_not_enabled.py @@ -0,0 +1,65 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock +from azure.mgmt.storage.models import DeleteRetentionPolicy +from remediation_worker.jobs.azure_storage_soft_delete_not_enabled.azure_storage_soft_delete_not_enabled import ( + EnableBlobServiceSoftDelete, +) + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "99d645b8-aa87-11ea-bb37-0242ac130002", + "Service": "Storage", + "FindingInfo": { + "FindingId": "9b2da5e9-bb96-4298-b2c1-e6c341b44c5f", + "ObjectId": "storage_account_name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Storage.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.StorageAccount.testingresourcename\\",\\"entityName\\":\\"testingresourcename\\",\\"entityType\\":\\"Azure.Storage.StorageAccount\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Storage\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestDefaultActionDeny(object): + def test_parse_payload(self, valid_payload): + params = EnableBlobServiceSoftDelete().parse(valid_payload) + assert params["account_name"] == "storage_account_name" + assert params["resource_group_name"] == "resource_group_name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success(self): + client = Mock() + action = EnableBlobServiceSoftDelete() + assert action.remediate(client, "resource_group", "account_name") == 0 + assert client.blob_services.set_service_properties.call_count == 1 + + call_args = client.blob_services.set_service_properties.call_args + updated_blob_service = call_args[1]["parameters"] + blob_soft_delete = DeleteRetentionPolicy(enabled=True, days=7) + assert updated_blob_service.delete_retention_policy == blob_soft_delete + + def test_remediate_with_exception(self): + client = Mock() + client.blob_services.set_service_properties.side_effect = Exception + action = EnableBlobServiceSoftDelete() + with pytest.raises(Exception): + assert action.remediate(client, "security_group_id", "resource_group") diff --git a/tox.ini b/tox.ini index ea914af..8272929 100644 --- a/tox.ini +++ b/tox.ini @@ -24,6 +24,7 @@ envlist = unit-azure-sql-auditing-on-server unit-azure-sql-threat-detection-on-server unit-azure-key-vault-logging-for-keyvault-enabled + unit-azure-storage-soft-delete-not-enabled [testenv] passenv = @@ -174,3 +175,9 @@ description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_key_vault_logging_for_keyvault_enabled.py deps = -r remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt + +[testenv:unit-azure-storage-soft-delete-not-enabled] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_storage_soft_delete_not_enabled.py +deps = -r remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt From c8952d850dcc6c77cc534d2f0a424f1d8b5b4346 Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Fri, 22 Jan 2021 10:56:55 +0530 Subject: [PATCH 30/34] PLA-22301 - Remediation job to encrypt storage account with CMK (#44) * PLA-22301 - Remediation job to encrypt storage account with CMK * PLA-22301 - Addressed review comments and added Diagnostic Settings for KeyVault --- ..._key_vault_logging_for_keyvault_enabled.py | 7 +- .../minimum_permissions.json | 4 +- .../README.md | 66 ++++ .../__init__.py | 0 ...ot_configured_with_customer_managed_key.py | 343 ++++++++++++++++++ .../constraints.txt | 130 +++++++ .../minimum_permissions.json | 24 ++ .../requirements-dev.txt | 33 ++ .../requirements.txt | 18 + .../minimum_permissions.json | 2 +- ...ot_configured_with_customer_managed_key.py | 221 +++++++++++ tox.ini | 7 + 12 files changed, 846 insertions(+), 9 deletions(-) create mode 100644 remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/README.md create mode 100644 remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/__init__.py create mode 100644 remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py create mode 100644 remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/constraints.txt create mode 100644 remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/minimum_permissions.json create mode 100644 remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements-dev.txt create mode 100644 remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements.txt create mode 100644 test/unit/test_azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py index 01e2fff..cbd0d87 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py @@ -180,13 +180,8 @@ def run(self, args): client_secret=os.environ.get("AZURE_CLIENT_SECRET"), tenant_id=os.environ.get("AZURE_TENANT_ID"), ) - credentials_stg = ClientSecretCredential( - client_id=os.environ.get("AZURE_CLIENT_ID"), - client_secret=os.environ.get("AZURE_CLIENT_SECRET"), - tenant_id=os.environ.get("AZURE_TENANT_ID"), - ) storage_client = StorageManagementClient( - credentials_stg, params["subscription_id"] + credentials, params["subscription_id"] ) keyvault_client = KeyVaultManagementClient( credentials, params["subscription_id"] diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json index e59e8be..16a9bb0 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json @@ -1,7 +1,7 @@ { "properties": { - "roleName": "remediate_set_default_network_access_deny", - "description": "This role has required permissions to make changes to the storage account", + "roleName": "remediate_enable_key_vault_logging", + "description": "This role has required permissions to make changes to the KeyVault", "assignableScopes": [ ], "permissions": [ diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/README.md b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/README.md new file mode 100644 index 0000000..ae1f045 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/README.md @@ -0,0 +1,66 @@ +# Configure Storage Account Encryption at rest with Customer Managed Keys + +This job configures Storage Account Encryption at rest with Customer Managed Keys. + +### Applicable Rule + +##### Rule ID: +02b672b7-a590-4434-8188-19325b2d1864 + +##### Rule Name: +Storage account encryption at rest is not configured with customer-managed key (CMK) + +## Getting Started +### Prerequisites +The provided Azure service principal must have the following permissions: +`Microsoft.Storage/storageAccounts/read` +`Microsoft.Storage/storageAccounts/write` +`Microsoft.Insights/DiagnosticSettings/Write` +`Microsoft.KeyVault/vaults/read` +`Microsoft.KeyVault/vaults/write` +`Microsoft.KeyVault/vaults/keys/read` +`Microsoft.KeyVault/vaults/keys/write` + +A sample role with requisite permissions can be found [here](minimum_permissions.json) + +More information about already builtin roles and permissions can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles) + +### Running the script +You may run this script using following commands: + +```shell script + pip install -r requirements.txt + python3 azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py +``` +## Running the tests +You may run test using following command under vss-remediation-worker-job-code-python directory: + +```shell script + pip install -r requirements-dev.txt + python3 -m pytest test +``` +## Deployment +Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: + ```shell script + docker run --rm -it --name worker \ + -e VSS_CLIENT_ID={ENTER CLIENT ID} + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + vmware/vss-remediation-worker:latest-python + ``` +## Contributing +The Secure State team welcomes contributions from the community. If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will update the issue when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ](https://cla.vmware.com/faq). + +All contributions to this repository must be signed as described on that page. Your signature certifies that you wrote the patch or have the right to pass it on as an open-source patch. + +For more detailed information, refer to [CONTRIBUTING.md](../../../CONTRIBUTING.md). +## Versioning +We use SemVer for versioning. For the versions available, see the tags on this repository. + +## Authors +* **VMware Secure State** - *Initial work* +See also the list of [contributors](https://github.com/vmware-samples/secure-state-remediation-jobs/graphs/contributors) who participated in this project. + +## License +This project is licensed under the Apache License - see the [LICENSE](https://github.com/vmware-samples/secure-state-remediation-jobs/blob/master/LICENSE.txt) file for details diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/__init__.py b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py new file mode 100644 index 0000000..d796636 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py @@ -0,0 +1,343 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import os +import sys +import logging +import datetime +from dateutil import parser as date_parse + +from azure.mgmt.storage import StorageManagementClient +from azure.mgmt.keyvault import KeyVaultManagementClient +from azure.mgmt.monitor import MonitorClient +from azure.keyvault.keys import KeyClient +from azure.identity import ClientSecretCredential +from azure.graphrbac import GraphRbacManagementClient +from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.storage.models import ( + StorageAccountUpdateParameters, + Encryption, + KeySource, + KeyVaultProperties, + Identity, +) +from azure.mgmt.keyvault.models import ( + VaultCreateOrUpdateParameters, + VaultProperties, + Sku, + AccessPolicyEntry, + Permissions, + KeyPermissions, + SecretPermissions, +) +from azure.mgmt.monitor.models import ( + DiagnosticSettingsResource, + LogSettings, + RetentionPolicy, +) + +logging.basicConfig(level=logging.INFO) + + +def generate_key_vault_name(prefix): + if len(prefix) >= 15: + prefix = str(prefix[:14]) + result_str = prefix + "-keyvault" + return result_str + + +class StorageAccountNotEncryptedWithCmk(object): + def create_key_vault( + self, + keyvault_client, + resource_group_name, + key_vault_name, + region, + tenant_id, + app_object_id, + stg_principal_id, + ): + access_policy_storage_account = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=stg_principal_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.UNWRAP_KEY, + KeyPermissions.WRAP_KEY, + ], + ), + ) + access_policy_app = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=app_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.LIST, + KeyPermissions.CREATE, + KeyPermissions.UPDATE, + KeyPermissions.DELETE, + KeyPermissions.BACKUP, + KeyPermissions.RESTORE, + KeyPermissions.RECOVER, + ], + ), + ) + key_vault_properties = VaultCreateOrUpdateParameters( + location=region, + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard",), + access_policies=[access_policy_storage_account, access_policy_app], + soft_delete_retention_in_days=90, + enabled_for_disk_encryption=False, + enabled_for_deployment=False, + enabled_for_template_deployment=False, + enable_soft_delete=True, + enable_purge_protection=True, + ), + ) + vault = keyvault_client.vaults.begin_create_or_update( + resource_group_name=resource_group_name, + vault_name=key_vault_name, + parameters=key_vault_properties, + ).result() + return vault + + def create_key(self, credential, key_vault_name): + d = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() + date = datetime.datetime.strptime( + d[0:19], "%Y-%m-%dT%H:%M:%S" + ) + datetime.timedelta(days=180) + expires_on = date_parse.parse( + date.replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() + ) + key_client = KeyClient( + vault_url=f"https://{key_vault_name}.vault.azure.net/", + credential=credential, + ) + rsa_key_name = key_vault_name + "-key" + rsa_key = key_client.create_rsa_key( + rsa_key_name, size=2048, expires_on=expires_on, enabled=True + ) + return rsa_key + + def parse(self, payload): + """Parse payload received from Remediation Service. + :param payload: JSON string containing parameters received from the remediation service. + :type payload: str. + :returns: Dictionary of parsed parameters + :rtype: dict + :raises: KeyError, JSONDecodeError + """ + remediation_entry = json.loads(payload) + + object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] + + region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] + + object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ + "ObjectChain" + ] + object_chain_dict = json.loads(object_chain) + subscription_id = object_chain_dict["cloudAccountId"] + + properties = object_chain_dict["properties"] + resource_group_name = "" + for property in properties: + if property["name"] == "ResourceGroup" and property["type"] == "string": + resource_group_name = property["stringV"] + break + + logging.info("parsed params") + logging.info(f" resource_group_name: {resource_group_name}") + logging.info(f" account_name: {object_id}") + logging.info(f" subscription_id: {subscription_id}") + logging.info(f" region: {region}") + + return { + "resource_group_name": resource_group_name, + "account_name": object_id, + "subscription_id": subscription_id, + "region": region, + } + + def remediate( + self, + monitor_client, + graph_client, + storage_client, + keyvault_client, + client_id, + tenant_id, + credentials, + resource_group_name, + account_name, + region, + ): + """Enable Soft Delete for Storage Account Blob Service + :param storage_client: Instance of the Azure StorageManagementClient. + :param graph_client: Instance of the AzureGraphRbacManagementClient. + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param client_id: Azure Client ID. + :param tenant_id: Azure Tenant ID. + :param resource_group_name: The name of the resource group to which the storage account belongs. + :param account_name: The name of the storage account. + :param region: Region in which the storage account belongs. + :type resource_group_name: str. + :type account_name: str. + :type region: str. + :returns: Integer signaling success or failure + :rtype: int + :raises: msrestazure.azure_exceptions.CloudError + """ + try: + app_details = graph_client.applications.get_service_principals_id_by_app_id( + application_id=client_id + ) + app_object_id = app_details.value + + stg_acc = storage_client.storage_accounts.get_properties( + resource_group_name=resource_group_name, account_name=account_name, + ) + if stg_acc.identity is None: + + logging.info( + f"Assigning Identity to the Storage Account {account_name}" + ) + logging.info("executing storage_client.storage_accounts.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={account_name}") + + updated_stg_acc = storage_client.storage_accounts.update( + resource_group_name=resource_group_name, + account_name=account_name, + parameters=StorageAccountUpdateParameters( + identity=Identity(type="SystemAssigned") + ), + ) + + stg_principal_id = updated_stg_acc.identity.principal_id + else: + stg_principal_id = stg_acc.identity.principal_id + + key_vault_name = generate_key_vault_name(account_name) + + logging.info("creating a key vault") + logging.info("executing keyvault_client.vaults.begin_create_or_update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" vault_name={key_vault_name}") + + key_vault = self.create_key_vault( + keyvault_client, + resource_group_name, + key_vault_name, + region, + tenant_id, + app_object_id, + stg_principal_id, + ) + logging.info("creating a key") + key = self.create_key(credentials, key_vault_name) + + log = LogSettings( + category="AuditEvent", + enabled=True, + retention_policy=RetentionPolicy(enabled=True, days=180), + ) + + logging.info(" Creating a Diagnostic setting for key vault logs") + logging.info( + " executing monitor_client.diagnostic_settings.create_or_update" + ) + logging.info(f" resource_uri={key_vault.id}") + logging.info(f" name={key_vault_name}") + + monitor_client.diagnostic_settings.create_or_update( + resource_uri=key_vault.id, + name=key_vault_name, + parameters=DiagnosticSettingsResource( + storage_account_id=stg_acc.id, logs=[log], + ), + ) + + logging.info("executing storage_client.storage_accounts.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={account_name}") + + storage_client.storage_accounts.update( + resource_group_name=resource_group_name, + account_name=account_name, + parameters=StorageAccountUpdateParameters( + encryption=Encryption( + key_source=KeySource.MICROSOFT_KEYVAULT, + key_vault_properties=KeyVaultProperties( + key_name=key.name, + key_vault_uri=key_vault.properties.vault_uri, + ), + ), + ), + ) + except Exception as e: + logging.error(f"{str(e)}") + raise + + return 0 + + def run(self, args): + """Run the remediation job. + :param args: List of arguments provided to the job. + :type args: list. + :returns: int + """ + params = self.parse(args[1]) + client_id = os.environ.get("AZURE_CLIENT_ID") + client_secret = os.environ.get("AZURE_CLIENT_SECRET") + tenant_id = os.environ.get("AZURE_TENANT_ID") + + credential = ClientSecretCredential( + client_id=client_id, client_secret=client_secret, tenant_id=tenant_id, + ) + + credentials = ServicePrincipalCredentials( + client_id=client_id, + secret=client_secret, + tenant=tenant_id, + resource="https://graph.windows.net", + ) + + storage_client = StorageManagementClient(credential, params["subscription_id"]) + keyvault_client = KeyVaultManagementClient( + credential, params["subscription_id"] + ) + graph_client = GraphRbacManagementClient(credentials, tenant_id, base_url=None) + monitor_client = MonitorClient(credential, params["subscription_id"]) + return self.remediate( + monitor_client, + graph_client, + storage_client, + keyvault_client, + client_id, + tenant_id, + credential, + params["resource_group_name"], + params["account_name"], + params["region"], + ) + + +if __name__ == "__main__": + sys.exit(StorageAccountNotEncryptedWithCmk().run(sys.argv)) diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/constraints.txt b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/constraints.txt new file mode 100644 index 0000000..d870aba --- /dev/null +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/constraints.txt @@ -0,0 +1,130 @@ +adal==1.2.5 \ + --hash=sha256:7492aff8f0ba7dd4e1c477303295c645141540fff34c3ca6de0a0b0e6c1c122a \ + --hash=sha256:8003ba03ef04170195b3eddda8a5ab43649ef2c5f0287023d515affb1ccfcfc3 +azure-common==1.1.25 \ + --hash=sha256:ce0f1013e6d0e9faebaf3188cc069f4892fc60a6ec552e3f817c1a2f92835054 \ + --hash=sha256:fd02e4256dc9cdd2d4422bc795bdca2ef302f7a86148b154fbf4ea1f09da400a +azure-core==1.8.2 \ + --hash=sha256:621b53271f7988b766f8a7d7f7a2c44241e3d2c1d8db13e68089d6da6241748e \ + --hash=sha256:be23d411e19874f375c2ef0327c452be10b1e9a1023ac6afe334598f2920136b +azure-mgmt-core==1.2.1 \ + --hash=sha256:a3906fa77edfedfcc3229dc3b69489d5ed63b107c7eacbc50092e6cbfbfd83f0 \ + --hash=sha256:bd4503a2d81b86780f15936af2e4244c1345062f4c2422f0b377b56cb80d7796 +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +certifi==2020.6.20 \ + --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ + --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 +cffi==1.14.3 \ + --hash=sha256:005f2bfe11b6745d726dbb07ace4d53f057de66e336ff92d61b8c7e9c8f4777d \ + --hash=sha256:09e96138280241bd355cd585148dec04dbbedb4f46128f340d696eaafc82dd7b \ + --hash=sha256:0b1ad452cc824665ddc682400b62c9e4f5b64736a2ba99110712fdee5f2505c4 \ + --hash=sha256:0ef488305fdce2580c8b2708f22d7785ae222d9825d3094ab073e22e93dfe51f \ + --hash=sha256:15f351bed09897fbda218e4db5a3d5c06328862f6198d4fb385f3e14e19decb3 \ + --hash=sha256:22399ff4870fb4c7ef19fff6eeb20a8bbf15571913c181c78cb361024d574579 \ + --hash=sha256:23e5d2040367322824605bc29ae8ee9175200b92cb5483ac7d466927a9b3d537 \ + --hash=sha256:2791f68edc5749024b4722500e86303a10d342527e1e3bcac47f35fbd25b764e \ + --hash=sha256:2f9674623ca39c9ebe38afa3da402e9326c245f0f5ceff0623dccdac15023e05 \ + --hash=sha256:3363e77a6176afb8823b6e06db78c46dbc4c7813b00a41300a4873b6ba63b171 \ + --hash=sha256:33c6cdc071ba5cd6d96769c8969a0531be2d08c2628a0143a10a7dcffa9719ca \ + --hash=sha256:3b8eaf915ddc0709779889c472e553f0d3e8b7bdf62dab764c8921b09bf94522 \ + --hash=sha256:3cb3e1b9ec43256c4e0f8d2837267a70b0e1ca8c4f456685508ae6106b1f504c \ + --hash=sha256:3eeeb0405fd145e714f7633a5173318bd88d8bbfc3dd0a5751f8c4f70ae629bc \ + --hash=sha256:44f60519595eaca110f248e5017363d751b12782a6f2bd6a7041cba275215f5d \ + --hash=sha256:4d7c26bfc1ea9f92084a1d75e11999e97b62d63128bcc90c3624d07813c52808 \ + --hash=sha256:529c4ed2e10437c205f38f3691a68be66c39197d01062618c55f74294a4a4828 \ + --hash=sha256:6642f15ad963b5092d65aed022d033c77763515fdc07095208f15d3563003869 \ + --hash=sha256:85ba797e1de5b48aa5a8427b6ba62cf69607c18c5d4eb747604b7302f1ec382d \ + --hash=sha256:8f0f1e499e4000c4c347a124fa6a27d37608ced4fe9f7d45070563b7c4c370c9 \ + --hash=sha256:a624fae282e81ad2e4871bdb767e2c914d0539708c0f078b5b355258293c98b0 \ + --hash=sha256:b0358e6fefc74a16f745afa366acc89f979040e0cbc4eec55ab26ad1f6a9bfbc \ + --hash=sha256:bbd2f4dfee1079f76943767fce837ade3087b578aeb9f69aec7857d5bf25db15 \ + --hash=sha256:bf39a9e19ce7298f1bd6a9758fa99707e9e5b1ebe5e90f2c3913a47bc548747c \ + --hash=sha256:c11579638288e53fc94ad60022ff1b67865363e730ee41ad5e6f0a17188b327a \ + --hash=sha256:c150eaa3dadbb2b5339675b88d4573c1be3cb6f2c33a6c83387e10cc0bf05bd3 \ + --hash=sha256:c53af463f4a40de78c58b8b2710ade243c81cbca641e34debf3396a9640d6ec1 \ + --hash=sha256:cb763ceceae04803adcc4e2d80d611ef201c73da32d8f2722e9d0ab0c7f10768 \ + --hash=sha256:cc75f58cdaf043fe6a7a6c04b3b5a0e694c6a9e24050967747251fb80d7bce0d \ + --hash=sha256:d80998ed59176e8cba74028762fbd9b9153b9afc71ea118e63bbf5d4d0f9552b \ + --hash=sha256:de31b5164d44ef4943db155b3e8e17929707cac1e5bd2f363e67a56e3af4af6e \ + --hash=sha256:e66399cf0fc07de4dce4f588fc25bfe84a6d1285cc544e67987d22663393926d \ + --hash=sha256:f0620511387790860b249b9241c2f13c3a80e21a73e0b861a2df24e9d6f56730 \ + --hash=sha256:f4eae045e6ab2bb54ca279733fe4eb85f1effda392666308250714e01907f394 \ + --hash=sha256:f92cdecb618e5fa4658aeb97d5eb3d2f47aa94ac6477c6daf0f306c5a3b9e6b1 \ + --hash=sha256:f92f789e4f9241cd262ad7a555ca2c648a98178a953af117ef7fad46aa1d5591 +chardet==3.0.4 \ + --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ + --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 +cryptography==3.2.1 \ + --hash=sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538 \ + --hash=sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f \ + --hash=sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77 \ + --hash=sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b \ + --hash=sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33 \ + --hash=sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e \ + --hash=sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb \ + --hash=sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e \ + --hash=sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7 \ + --hash=sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297 \ + --hash=sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d \ + --hash=sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7 \ + --hash=sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b \ + --hash=sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7 \ + --hash=sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4 \ + --hash=sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8 \ + --hash=sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b \ + --hash=sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851 \ + --hash=sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13 \ + --hash=sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b \ + --hash=sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3 \ + --hash=sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df +idna==2.10 \ + --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \ + --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 +isodate==0.6.0 \ + --hash=sha256:2e364a3d5759479cdb2d37cce6b9376ea504db2ff90252a2e5b7cc89cc9ff2d8 \ + --hash=sha256:aa4d33c06640f5352aca96e4b81afd8ab3b47337cc12089822d6f322ac772c81 +msal==1.5.1 \ + --hash=sha256:7efb0256c96a7b2eadab49ce29ecdb91352a91440c12a40bed44303724b62fda \ + --hash=sha256:d84074a997e6fb2a47e22815dce376adcc8790838b6b1bf1fcea29378e2bf3eb +msal-extensions==0.2.2 \ + --hash=sha256:31414753c484679bb3b6c6401623eb4c3ccab630af215f2f78c1d5c4f8e1d1a9 \ + --hash=sha256:f092246787145ec96d6c3c9f7bedfb837830fe8a79b56180e531fbf28b8de532 +msrest==0.6.19 \ + --hash=sha256:55f8c3940bc5dc609f8cf9fcd639444716cc212a943606756272e0d0017bbb5b \ + --hash=sha256:87aa64948c3ef3dbf6f6956d2240493e68d714e4621b92b65b3c4d5808297929 +msrestazure==0.6.4 \ + --hash=sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9 \ + --hash=sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189 +oauthlib==3.1.0 \ + --hash=sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889 \ + --hash=sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea +portalocker==1.7.1 \ + --hash=sha256:34cb36c618d88bcd9079beb36dcdc1848a3e3d92ac4eac59055bdeafc39f9d4a \ + --hash=sha256:6d6f5de5a3e68c4dd65a98ec1babb26d28ccc5e770e07b672d65d5a35e4b2d8a +pycparser==2.20 \ + --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 \ + --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 +PyJWT==1.7.1 \ + --hash=sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e \ + --hash=sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96 +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a +requests==2.24.0 \ + --hash=sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b \ + --hash=sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898 +requests-oauthlib==1.3.0 \ + --hash=sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d \ + --hash=sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a \ + --hash=sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc +six==1.12.0 \ + --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ + --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 +urllib3==1.25.11 \ + --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \ + --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/minimum_permissions.json b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/minimum_permissions.json new file mode 100644 index 0000000..0f83e55 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/minimum_permissions.json @@ -0,0 +1,24 @@ +{ + "properties": { + "roleName": "remediate_encrypt_storage_account_with_cmk", + "description": "This role has required permissions to make changes to the storage account", + "assignableScopes": [ + ], + "permissions": [ + { + "actions": [ + "Microsoft.Storage/storageAccounts/read", + "Microsoft.Storage/storageAccounts/write", + "Microsoft.Insights/DiagnosticSettings/Write", + "Microsoft.KeyVault/vaults/read", + "Microsoft.KeyVault/vaults/write", + "Microsoft.KeyVault/vaults/keys/read", + "Microsoft.KeyVault/vaults/keys/write" + ], + "notActions": [], + "dataActions": [], + "notDataActions": [] + } + ] + } + } diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements-dev.txt b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements-dev.txt new file mode 100644 index 0000000..759f2ca --- /dev/null +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements-dev.txt @@ -0,0 +1,33 @@ +-r requirements.txt +-c constraints.txt + +attrs==20.1.0 \ + --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ + --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff +iniconfig==1.1.1 \ + --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ + --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 +mock==4.0.2 \ + --hash=sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0 \ + --hash=sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72 +packaging==20.4 \ + --hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \ + --hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 +pluggy==0.13.1 \ + --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \ + --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d +py==1.9.0 \ + --hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \ + --hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 +pyparsing==2.4.7 \ + --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \ + --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b +pytest==6.1.2 \ + --hash=sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe \ + --hash=sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e +toml==0.10.1 \ + --hash=sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f \ + --hash=sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88 +zipp==3.4.0 \ + --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ + --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements.txt b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements.txt new file mode 100644 index 0000000..e7a86a5 --- /dev/null +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements.txt @@ -0,0 +1,18 @@ +azure-identity==1.4.1 \ + --hash=sha256:6f95b3505fc134ad16bd16da053456e1933188ac43161704d48ddb4edebf72c9 \ + --hash=sha256:7b071089faf0789059ac24052e311e2b096a002c173d42b96896db09c6e2ba5d +azure-mgmt-storage==16.0.0 \ + --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ + --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +azure-mgmt-keyvault==8.0.0 \ + --hash=sha256:2c974c6114d8d27152642c82a975812790a5e86ccf609bf370a476d9ea0d2e7d \ + --hash=sha256:99da24e9455f195d1e45a605af04d663e15cb5d5eb005b0ef60638474bbb2b3f +azure-graphrbac==0.61.1 \ + --hash=sha256:53e98ae2ca7c19b349e9e9bb1b6a824aeae8dcfcbe17190d20fe69c0f185b2e2 \ + --hash=sha256:7b4e0f05676acc912f2b33c71c328d9fb2e4dc8e70ebadc9d3de8ab08bf0b175 +azure-keyvault-keys==4.3.1 \ + --hash=sha256:25cf889bbcafd8dee0e068fd48d84e24e11143bf85e35c68d997a222e4a0f7fb \ + --hash=sha256:fbf67bca913ebf68b9075ee9d2e2b899dc3c7892cc40abfe1b08220a382f6ed9 +azure-mgmt-monitor==1.0.1 \ + --hash=sha256:04bd89d74fe47f966b09e3256ffefcfa5c1a51057a6b33c092afe5ae17a1a7b7 \ + --hash=sha256:92db4d133f58be900e5a2a9e3f44f4f0c068717bf534bf49c97e070c0280d203 \ No newline at end of file diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/minimum_permissions.json b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/minimum_permissions.json index 6a6b52f..f748349 100644 --- a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/minimum_permissions.json +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/minimum_permissions.json @@ -1,6 +1,6 @@ { "properties": { - "roleName": "remediate_set_default_network_access_deny", + "roleName": "remediate_soft_delete_for_blob_service", "description": "This role has required permissions to make changes to the storage account", "assignableScopes": [ ], diff --git a/test/unit/test_azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py b/test/unit/test_azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py new file mode 100644 index 0000000..0e474d9 --- /dev/null +++ b/test/unit/test_azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py @@ -0,0 +1,221 @@ +# Copyright (c) 2020 VMware Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from mock import Mock +from remediation_worker.jobs.azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.azure_storage_encryption_at_rest_not_configured_with_customer_managed_key import ( + StorageAccountNotEncryptedWithCmk, +) +from azure.mgmt.storage.models import ( + StorageAccount, + Identity, +) +from azure.mgmt.keyvault.models import ( + VaultProperties, + Vault, + Sku, +) +from azure.keyvault.keys import KeyVaultKey + + +@pytest.fixture +def valid_payload(): + return """ +{ + "notificationInfo": { + "RuleId": "99d645b8-aa87-11ea-bb37-0242ac130002", + "Service": "Storage", + "FindingInfo": { + "FindingId": "9b2da5e9-bb96-4298-b2c1-e6c341b44c5f", + "ObjectId": "account-name", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Storage.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.StorageAccount.testingresourcename\\",\\"entityName\\":\\"testingresourcename\\",\\"entityType\\":\\"Azure.Storage.StorageAccount\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Storage\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "Region": "region" + } + } +} +""" + + +class TestestEncryptStorageAccountWithCMK(object): + def test_parse_payload(self, valid_payload): + params = StorageAccountNotEncryptedWithCmk().parse(valid_payload) + assert params["resource_group_name"] == "resource_group_name" + assert params["account_name"] == "account-name" + assert params["subscription_id"] == "subscription_id" + assert params["region"] == "region" + + def test_remediate_success_without_storage_identity(self): + monitor_client = Mock() + graph_client = Mock() + storage_client = Mock() + keyvault_client = Mock() + credentials = Mock() + client_id = Mock() + tenant_id = Mock() + + storage_client.storage_accounts.get_properties.return_value = StorageAccount( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.Storage/storageAccounts/kshrutikagfigz", + name="kshrutikagfigz5", + type="Microsoft.Storage/storageAccounts", + location="eastus", + identity=None, + ) + identity = Identity( + principal_id="139bcf82-e14e-4773-bcf4-1da136674792", + type="SystemAssigned", + tenant_id="b39138ca-3cee-4b4a-a4d6-cd83d9dd62f0", + ) + storage_client.storage_accounts.update.return_value = StorageAccount( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", + name="remserver5", + type="Microsoft.Sql/servers", + location="eastus", + identity=identity, + ) + action = StorageAccountNotEncryptedWithCmk() + action.create_key = Mock() + action.create_key_vault = Mock() + action.create_key_vault.return_value = Vault( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", + name="stg-keyvault-rem", + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard"), + vault_uri="https://stg-keyvault-rem.vault.azure.net", + ), + ) + action.create_key.return_value = KeyVaultKey( + key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", + name="rem-key1", + ) + assert ( + action.remediate( + monitor_client, + graph_client, + storage_client, + keyvault_client, + client_id, + tenant_id, + credentials, + "resource_group_name", + "account-name", + "region", + ) + == 0 + ) + assert storage_client.storage_accounts.update.call_count == 2 + call_args = storage_client.storage_accounts.update.call_args + updated_storage_account = call_args[1]["parameters"] + assert updated_storage_account.encryption.key_source == "Microsoft.Keyvault" + assert ( + updated_storage_account.encryption.key_vault_properties.key_name + == "rem-key1" + ) + assert ( + updated_storage_account.encryption.key_vault_properties.key_vault_uri + == "https://stg-keyvault-rem.vault.azure.net" + ) + + def test_remediate_success_with_storage_identity(self): + monitor_client = Mock() + graph_client = Mock() + storage_client = Mock() + keyvault_client = Mock() + credentials = Mock() + client_id = Mock() + tenant_id = Mock() + + identity = Identity( + principal_id="139bcf82-e14e-4773-bcf4-1da136674792", + type="SystemAssigned", + tenant_id="b39138ca-3cee-4b4a-a4d6-cd83d9dd62f0", + ) + storage_client.storage_accounts.get_properties.return_value = StorageAccount( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.Storage/storageAccounts/kshrutikagfigz", + name="kshrutikagfigz5", + type="Microsoft.Storage/storageAccounts", + location="eastus", + identity=identity, + ) + action = StorageAccountNotEncryptedWithCmk() + action.create_key = Mock() + action.create_key_vault = Mock() + action.create_key_vault.return_value = Vault( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", + name="stg-keyvault-rem", + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard"), + vault_uri="https://stg-keyvault-rem.vault.azure.net", + ), + ) + action.create_key.return_value = KeyVaultKey( + key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", + name="rem-key1", + ) + + assert ( + action.remediate( + monitor_client, + graph_client, + storage_client, + keyvault_client, + client_id, + tenant_id, + credentials, + "resource_group_name", + "account-name", + "region", + ) + == 0 + ) + assert storage_client.storage_accounts.update.call_count == 1 + call_args = storage_client.storage_accounts.update.call_args + updated_storage_account = call_args[1]["parameters"] + assert updated_storage_account.encryption.key_source == "Microsoft.Keyvault" + assert ( + updated_storage_account.encryption.key_vault_properties.key_name + == "rem-key1" + ) + assert ( + updated_storage_account.encryption.key_vault_properties.key_vault_uri + == "https://stg-keyvault-rem.vault.azure.net" + ) + + def test_remediate_with_exception(self): + monitor_client = Mock() + graph_client = Mock() + storage_client = Mock() + keyvault_client = Mock() + credentials = Mock() + client_id = Mock() + tenant_id = Mock() + storage_client.storage_accounts.update.side_effect = Exception + action = StorageAccountNotEncryptedWithCmk() + with pytest.raises(Exception): + assert ( + action.remediate( + monitor_client, + graph_client, + storage_client, + keyvault_client, + client_id, + tenant_id, + credentials, + "resource_group", + "security_group", + "region", + ) + == 0 + ) diff --git a/tox.ini b/tox.ini index 8272929..1606668 100644 --- a/tox.ini +++ b/tox.ini @@ -25,6 +25,7 @@ envlist = unit-azure-sql-threat-detection-on-server unit-azure-key-vault-logging-for-keyvault-enabled unit-azure-storage-soft-delete-not-enabled + unit-azure-storage-encryption-at-rest-not-configured-with-customer-managed-key [testenv] passenv = @@ -181,3 +182,9 @@ description = Unit test the project changedir = test commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_storage_soft_delete_not_enabled.py deps = -r remediation_worker/jobs/azure_storage_soft_delete_not_enabled/requirements-dev.txt + +[testenv:unit-azure-storage-encryption-at-rest-not-configured-with-customer-managed-key] +description = Unit test the project +changedir = test +commands = pytest --capture=no --basetemp="{envtmpdir}" unit/test_azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py +deps = -r remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/requirements-dev.txt From b8052ae282fec7ed6867764184cf301dd022d6f5 Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Fri, 12 Feb 2021 16:25:06 +0530 Subject: [PATCH 31/34] PLA-23159 - Updated Remediation Job for Storage Account encryption (#48) * PLA-23159 - Updated Remediation Job for Storage Account encryption * PLA-23159 - Added Comments * PLA-23159 - Updated the Storage Account job * PLA-23159 - Updated Storage encryption job --- .../README.md | 22 +- ...ot_configured_with_customer_managed_key.py | 529 +++++++++++++++--- .../minimum_permissions.json | 5 +- ...ot_configured_with_customer_managed_key.py | 191 ++++--- 4 files changed, 585 insertions(+), 162 deletions(-) diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/README.md b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/README.md index ae1f045..60b5bdb 100644 --- a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/README.md +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/README.md @@ -1,6 +1,8 @@ # Configure Storage Account Encryption at rest with Customer Managed Keys -This job configures Storage Account Encryption at rest with Customer Managed Keys. +This job configures Storage Account Encryption at rest with Customer Managed Keys. It checks for the existence of the Key Vault created by CHSS in a give resource group and region, if the Key Vault exists then it creates a new Key in it and then encrypts the Storage Account. If the Key Vault does not exists then it creates one. + +The Key Vault created by CHSS is prefixed with "chss" and is tagged by `{"Created By" : "CHSS"}`. ### Applicable Rule @@ -15,11 +17,14 @@ Storage account encryption at rest is not configured with customer-managed key ( The provided Azure service principal must have the following permissions: `Microsoft.Storage/storageAccounts/read` `Microsoft.Storage/storageAccounts/write` +`"Microsoft.Storage/storageAccounts/blobServices/write` +`Microsoft.Storage/storageAccounts/blobServices/read` `Microsoft.Insights/DiagnosticSettings/Write` `Microsoft.KeyVault/vaults/read` `Microsoft.KeyVault/vaults/write` `Microsoft.KeyVault/vaults/keys/read` `Microsoft.KeyVault/vaults/keys/write` +`Microsoft.KeyVault/vaults/accessPolicies/write` A sample role with requisite permissions can be found [here](minimum_permissions.json) @@ -40,13 +45,16 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` ## Deployment -Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. -Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. -Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: +Provision an instance by creating an Azure Virtual Machine to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker on newly provisioned Azure Virtual Machine instance. You can refer to the [docs here](https://docs.microsoft.com/en-us/previous-versions/azure/virtual-machines/linux/docker-machine) for more information. +Deploy the worker docker image by SSH into the Azure Virtual Machine instance and run the following commands: ```shell script - docker run --rm -it --name worker \ - -e VSS_CLIENT_ID={ENTER CLIENT ID} - -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + docker run --rm -it --name {worker_name}\ + -e VSS_CLIENT_ID={ENTER CLIENT ID}\ + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET}\ + -e AZURE_CLIENT_ID={ENTER AZURE_CLIENT_ID} \ + -e AZURE_CLIENT_SECRET={ENTER AZURE_CLIENT_SECRET} \ + -e AZURE_TENANT_ID={ENTER AZURE_TENANT_ID} \ vmware/vss-remediation-worker:latest-python ``` ## Contributing diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py index d796636..c392a3c 100644 --- a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py @@ -19,6 +19,8 @@ import datetime from dateutil import parser as date_parse +from typing import List +from azure.core.paging import ItemPaged from azure.mgmt.storage import StorageManagementClient from azure.mgmt.keyvault import KeyVaultManagementClient from azure.mgmt.monitor import MonitorClient @@ -26,12 +28,21 @@ from azure.identity import ClientSecretCredential from azure.graphrbac import GraphRbacManagementClient from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.storage.models import Sku as sku_storage from azure.mgmt.storage.models import ( StorageAccountUpdateParameters, + StorageAccountCreateParameters, Encryption, KeySource, KeyVaultProperties, Identity, + DefaultAction, + SkuName, + SkuTier, + NetworkRuleSet, + BlobServiceProperties, + DeleteRetentionPolicy, + StorageAccountListResult, ) from azure.mgmt.keyvault.models import ( VaultCreateOrUpdateParameters, @@ -40,7 +51,10 @@ AccessPolicyEntry, Permissions, KeyPermissions, - SecretPermissions, + VaultListResult, + AccessPolicyUpdateKind, + VaultAccessPolicyParameters, + VaultAccessPolicyProperties, ) from azure.mgmt.monitor.models import ( DiagnosticSettingsResource, @@ -50,15 +64,174 @@ logging.basicConfig(level=logging.INFO) - -def generate_key_vault_name(prefix): - if len(prefix) >= 15: - prefix = str(prefix[:14]) - result_str = prefix + "-keyvault" +MAX_COUNT_SUBSCRIPTION = 5 +MAX_COUNT_RESOURCE_NAME = 5 +MAX_COUNT_REGION = 6 +MAX_COUNT_COMPONENT = 4 + + +def generate_name(region, subscription_id, resource_group_name): + """Generates a name for the resource + :param region: location in which the resource exists + :param subscription_id: Azure Subscription Id + :param resource_group_name: Resource group name in which the resource exists + :type region: str + :type subscription_id: str + :type resource_group_name: str + :returns: resource name + :rtype: str + """ + random_str = "".join(i for i in subscription_id if i.islower() or i.isdigit()) + subscription_id = random_str[:MAX_COUNT_SUBSCRIPTION] + random_str = "".join(i for i in region if i.islower() or i.isdigit()) + region = random_str[-MAX_COUNT_REGION:] + random_str = "".join(i for i in resource_group_name if i.islower() or i.isdigit()) + resource_group_name = random_str[-MAX_COUNT_RESOURCE_NAME:] + result_str = "chss" + subscription_id + resource_group_name + region + "logs" return result_str class StorageAccountNotEncryptedWithCmk(object): + def check_stg_account(self, storage_client, region, name, resource_group_name): + """Checks For the existence of the Storage Account created by CHSS + :param storage_client: Instance of the Azure StorageManagementClient. + :param region: The location in which the storage account exists. + :param name: The Storage Account name. + :param resource_group_name: The name of the resource group. + :type storage_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: StorageAccount object + :rtype: object + """ + storage_accounts_paged: ItemPaged[ + StorageAccountListResult + ] = storage_client.storage_accounts.list() + storage_accounts_list: List[dict] = list(storage_accounts_paged) + storage_account = None + for stg_account in storage_accounts_list: + stg_id = stg_account.id + stg_components = stg_id.split("/") + if len(stg_components) > MAX_COUNT_COMPONENT: + resource_grp = stg_components[MAX_COUNT_COMPONENT] + if ( + stg_account.name == name + and stg_account.location == region + and resource_grp == resource_group_name + ): + storage_account = stg_account + break + return storage_account + + def check_key_vault(self, keyvault_client, region, name, resource_group_name): + """Checks for the existence of the Key Vault created by CHSS. + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param region: The location in which the Key Vault exists. + :param name: Key Vault name. + :param resource_group_name: The name of the resource group. + :type keyvault_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: Vault object + :rtype: object + """ + key_vault_paged: ItemPaged[ + VaultListResult + ] = keyvault_client.vaults.list_by_subscription() + key_vault_list: List[dict] = list(key_vault_paged) + chss_key_vault = None + for key_vault in key_vault_list: + key_vault_id = key_vault.id + key_vault_components = key_vault_id.split("/") + if len(key_vault_components) > MAX_COUNT_COMPONENT: + resource_grp = key_vault_components[MAX_COUNT_COMPONENT] + if ( + key_vault.name == name + and key_vault.location == region + and resource_grp == resource_group_name + ): + chss_key_vault = key_vault + break + return chss_key_vault + + def create_storage_account( + self, resource_group_name, name, region, storage_client, + ): + """Creates a Storage Account + :param storage_client: Instance of the Azure StorageManagementClient. + :param region: The location in which the storage account exists. + :param name: The Storage Account name. + :param resource_group_name: The name of the resource group. + :type storage_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: StorageAccount object + :rtype: object + """ + + create_params = StorageAccountCreateParameters( + location=region, + sku=sku_storage(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), + identity=Identity(type="SystemAssigned"), + kind="StorageV2", + enable_https_traffic_only=True, + network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), + tags={"Created By": "CHSS"}, + ) + stg_account = storage_client.storage_accounts.begin_create( + resource_group_name=resource_group_name, + account_name=name, + parameters=create_params, + ).result() + + storage_client.blob_services.set_service_properties( + resource_group_name=resource_group_name, + account_name=name, + parameters=BlobServiceProperties( + delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7) + ), + ) + return stg_account + + def update_storage_account_encryption( + self, storage_client, resource_group_name, stg_name, key_name, vault_uri + ): + """Updates Storage Account Encryption for a Storage Account. + :param storage_client: Instance of the Azure StorageManagementClient. + :param resource_group_name: The name of the resource group. + :param stg_name: The Storage Account name. + :param key_name: Name of the Key to encrypt the Storage Account with. + :param vault_uri: Key Vault uri in which the Key exists. + :type storage_client: object + :type resource_group_name: str + :type stg_name: str + :type key_name: str + :type vault_uri: str + :returns: None + :rtype: None + """ + logging.info(" Encrypting Storage Account with Customer Managed Key") + logging.info(" executing storage_client.storage_accounts.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={stg_name}") + logging.info(f" key_vault_uri={vault_uri}") + logging.info(f" key_name={key_name}") + storage_client.storage_accounts.update( + resource_group_name=resource_group_name, + account_name=stg_name, + parameters=StorageAccountUpdateParameters( + encryption=Encryption( + key_source=KeySource.MICROSOFT_KEYVAULT, + key_vault_properties=KeyVaultProperties( + key_name=key_name, key_vault_uri=vault_uri, + ), + ), + ), + ) + def create_key_vault( self, keyvault_client, @@ -69,6 +242,24 @@ def create_key_vault( app_object_id, stg_principal_id, ): + """Creates a Key Vault + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param resource_group_name: The name of the resource group. + :param key_vault_name: Name of the Key Vault. + :param region: location of the Key Vault. + :param tenant_id: Azure tenant Id + :param app_object_id: Object Id of the application + :param stg_principal_id: Principal Id of the Storage Account + :type keyvault_client: object + :type resource_group_name: str + :type key_vault_name: str + :type region: str + :type tenant_id: str + :type app_object_id: str + :type stg_principal_id: str + :returns: Vault object + :rtype: object + """ access_policy_storage_account = AccessPolicyEntry( tenant_id=tenant_id, object_id=stg_principal_id, @@ -98,6 +289,7 @@ def create_key_vault( ) key_vault_properties = VaultCreateOrUpdateParameters( location=region, + tags={"Created By": "CHSS"}, properties=VaultProperties( tenant_id=tenant_id, sku=Sku(family="A", name="standard",), @@ -110,6 +302,10 @@ def create_key_vault( enable_purge_protection=True, ), ) + logging.info("creating a key vault") + logging.info("executing keyvault_client.vaults.begin_create_or_update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" vault_name={key_vault_name}") vault = keyvault_client.vaults.begin_create_or_update( resource_group_name=resource_group_name, vault_name=key_vault_name, @@ -117,7 +313,16 @@ def create_key_vault( ).result() return vault - def create_key(self, credential, key_vault_name): + def create_key(self, credential, key_vault_name, suffix): + """Creates a Key within the given Key Vault + :param credential: Azure Credentials + :param key_vault_name: Name of the Key Vault. + :param suffix: suffix for Key name + :type key_vault_name: str + :type suffix: str + :returns: Azure Key object which was created + :rtype: object + """ d = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() date = datetime.datetime.strptime( d[0:19], "%Y-%m-%dT%H:%M:%S" @@ -129,12 +334,149 @@ def create_key(self, credential, key_vault_name): vault_url=f"https://{key_vault_name}.vault.azure.net/", credential=credential, ) - rsa_key_name = key_vault_name + "-key" + rsa_key_name = key_vault_name + "-" + suffix + logging.info("creating a key") rsa_key = key_client.create_rsa_key( rsa_key_name, size=2048, expires_on=expires_on, enabled=True ) return rsa_key + def update_key_vault_access_policy( + self, + keyvault_client, + resource_group_name, + key_vault_name, + tenant_id, + app_object_id, + stg_object_id, + ): + """Updates Key Vault Access Policy + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param resource_group_name: The name of the resource group. + :param key_vault_name: Name of the Key Vault. + :param region: The location in which the Key Vault exists. + :param tenant_id: Azure tenant Id + :param app_object_id: Object Id of the application + :param stg_principal_id: Principal Id of the Storage Account + :type keyvault_client: object + :type resource_group_name: str + :type key_vault_name: str + :type region: str + :type tenant_id: str + :type app_object_id: str + :type stg_principal_id: str + :returns: None + :rtype: None + """ + access_policy_storage = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=stg_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.UNWRAP_KEY, + KeyPermissions.WRAP_KEY, + ], + ), + ) + access_policy_app = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=app_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.LIST, + KeyPermissions.CREATE, + KeyPermissions.UPDATE, + KeyPermissions.DELETE, + KeyPermissions.BACKUP, + KeyPermissions.RESTORE, + KeyPermissions.RECOVER, + ], + ), + ) + access_policy = [access_policy_app, access_policy_storage] + + logging.info("Updating Key Vault Access Policy") + logging.info("executing keyvault_client.vaults.update_access_policy") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" vault_name={key_vault_name}") + keyvault_client.vaults.update_access_policy( + resource_group_name=resource_group_name, + vault_name=key_vault_name, + operation_kind=AccessPolicyUpdateKind.ADD, + parameters=VaultAccessPolicyParameters( + properties=VaultAccessPolicyProperties(access_policies=access_policy), + ), + ) + + def create_diagnostic_setting( + self, monitor_client, key_vault_id, key_vault_name, stg_account_id, log + ): + """Creates a diagnostic setting + :param monitor_client: Instance of the Azure StorageManagementClient. + :param key_vault_id: The resource Id of the Key Vault. + :param key_vault_name: Name of the Key Vault. + :param stg_account_id: The Storage Account resource Id. + :param log: Instance of Azure Monitor LogSettings + :type monitor_client: object + :type log: object + :type key_vault_id: str + :type key_vault_name: str + :type stg_account_id: str + :returns: None + :rtype: None + """ + logging.info(" Creating a Diagnostic setting for key vault logs") + logging.info( + " executing monitor_client.diagnostic_settings.create_or_update" + ) + logging.info(f" resource_uri={key_vault_id}") + logging.info(f" name={key_vault_name}") + monitor_client.diagnostic_settings.create_or_update( + resource_uri=key_vault_id, + name=key_vault_name, + parameters=DiagnosticSettingsResource( + storage_account_id=stg_account_id, logs=[log], + ), + ) + + def ensure_identity_assigned( + self, resource_group_name, account_name, region, storage_client + ): + """Checks if the Identity is assigned to the Storage. If not then it assigns the identity + :param storage_client: Instance of the Azure StorageManagementClient. + :param resource_group_name: Resource group name + :param account_name: Storage Account name + :param region: location in which the Storage Account exists + :type storage_client: object + :type resource_group_name: str + :type account_name: str + :type region: str + :returns: Principal Id of the Storage Account + :rtype: str + """ + stg_acc = storage_client.storage_accounts.get_properties( + resource_group_name=resource_group_name, account_name=account_name, + ) + if stg_acc.identity is None: + + logging.info(f"Assigning Identity to the Storage Account {account_name}") + logging.info("executing storage_client.storage_accounts.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={account_name}") + + updated_stg_acc = storage_client.storage_accounts.update( + resource_group_name=resource_group_name, + account_name=account_name, + parameters=StorageAccountUpdateParameters( + identity=Identity(type="SystemAssigned") + ), + ) + return updated_stg_acc.identity.principal_id + else: + return stg_acc.identity.principal_id + def parse(self, payload): """Parse payload received from Remediation Service. :param payload: JSON string containing parameters received from the remediation service. @@ -187,19 +529,31 @@ def remediate( resource_group_name, account_name, region, + subscription_id, ): """Enable Soft Delete for Storage Account Blob Service - :param storage_client: Instance of the Azure StorageManagementClient. - :param graph_client: Instance of the AzureGraphRbacManagementClient. - :param keyvault_client: Instance of the Azure KeyVaultManagementClient. :param client_id: Azure Client ID. :param tenant_id: Azure Tenant ID. + :param graph_client: Instance of the AzureGraphRbacManagementClient. + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param storage_client: Instance of the Azure StorageManagementClient. + :param monitor_client: Instance of the Azure MonitorClient. + :param credentials: Azure Credential object. :param resource_group_name: The name of the resource group to which the storage account belongs. - :param account_name: The name of the storage account. - :param region: Region in which the storage account belongs. + :param account_name: The name of the Storage Account. + :param region: The region in which the key vault is present. + :param subscription_id: Azure Subscription Id + :type client_id: str + :type tenant_id: str + :type graph_client: object + :type keyvault_client: object + :type storage_client: object + :type monitor_client: object + :type credentials: object :type resource_group_name: str. :type account_name: str. :type region: str. + :type subscription_id: str :returns: Integer signaling success or failure :rtype: int :raises: msrestazure.azure_exceptions.CloudError @@ -208,89 +562,91 @@ def remediate( app_details = graph_client.applications.get_service_principals_id_by_app_id( application_id=client_id ) - app_object_id = app_details.value - stg_acc = storage_client.storage_accounts.get_properties( - resource_group_name=resource_group_name, account_name=account_name, + # Check if the identity is assigned to the Storage Account. If not then assign the identity. + principal_id = self.ensure_identity_assigned( + resource_group_name, account_name, region, storage_client ) - if stg_acc.identity is None: - logging.info( - f"Assigning Identity to the Storage Account {account_name}" + # Check if the Key Vault created by CHSS exists in the given region and resource group. + key_vault_name = generate_name(region, subscription_id, resource_group_name) + key_vault = self.check_key_vault( + keyvault_client, region, key_vault_name, resource_group_name + ) + if key_vault is None: + # If the Key Vault does not exists then create the Key Vault. + key_vault = self.create_key_vault( + keyvault_client, + resource_group_name, + key_vault_name, + region, + tenant_id, + app_details.value, + principal_id, ) - logging.info("executing storage_client.storage_accounts.update") - logging.info(f" resource_group_name={resource_group_name}") - logging.info(f" account_name={account_name}") - - updated_stg_acc = storage_client.storage_accounts.update( - resource_group_name=resource_group_name, - account_name=account_name, - parameters=StorageAccountUpdateParameters( - identity=Identity(type="SystemAssigned") - ), + # Create a Key to encrypt the Storage Account. + key = self.create_key(credentials, key_vault_name, account_name) + log = LogSettings( + category="AuditEvent", + enabled=True, + retention_policy=RetentionPolicy(enabled=True, days=180), ) - stg_principal_id = updated_stg_acc.identity.principal_id + # Check if the Storage Account created by CHSS exists in the given region and resource group. + stg_name = generate_name(region, subscription_id, resource_group_name) + stg_account = self.check_stg_account( + storage_client, region, stg_name, resource_group_name + ) + if stg_account is None: + # If the Storage Account does not exists then create the Storage Account. + stg_account = self.create_storage_account( + resource_group_name, stg_name, region, storage_client + ) + + # Create a Key to encrypt the Storage Account. + key = self.create_key(credentials, key_vault.name, stg_account.name) + # Update the Access policy for the Key Vault to give access to the Storage Account which is being created. + self.update_key_vault_access_policy( + keyvault_client, + resource_group_name, + key_vault_name, + tenant_id, + app_details.value, + stg_account.identity.principal_id, + ) + # Encrypt the Storage Account which is being created with the above Key. + self.update_storage_account_encryption( + storage_client, + resource_group_name, + stg_name, + key.name, + key_vault.properties.vault_uri, + ) + # Create Diagnostic Setting to store key vault logs + self.create_diagnostic_setting( + monitor_client, key_vault.id, key_vault.name, stg_account.id, log, + ) else: - stg_principal_id = stg_acc.identity.principal_id - - key_vault_name = generate_key_vault_name(account_name) - - logging.info("creating a key vault") - logging.info("executing keyvault_client.vaults.begin_create_or_update") - logging.info(f" resource_group_name={resource_group_name}") - logging.info(f" vault_name={key_vault_name}") - - key_vault = self.create_key_vault( - keyvault_client, + # If the Key Vault exists then update the access policy to give access to app and the Storage Account + self.update_key_vault_access_policy( + keyvault_client, + resource_group_name, + key_vault_name, + tenant_id, + app_details.value, + principal_id, + ) + # Create a Key to encrypt the Storage Account. + key = self.create_key(credentials, key_vault.name, account_name) + # Encrypt the Storage Account with the Key which was created. + self.update_storage_account_encryption( + storage_client, resource_group_name, - key_vault_name, - region, - tenant_id, - app_object_id, - stg_principal_id, - ) - logging.info("creating a key") - key = self.create_key(credentials, key_vault_name) - - log = LogSettings( - category="AuditEvent", - enabled=True, - retention_policy=RetentionPolicy(enabled=True, days=180), - ) - - logging.info(" Creating a Diagnostic setting for key vault logs") - logging.info( - " executing monitor_client.diagnostic_settings.create_or_update" - ) - logging.info(f" resource_uri={key_vault.id}") - logging.info(f" name={key_vault_name}") - - monitor_client.diagnostic_settings.create_or_update( - resource_uri=key_vault.id, - name=key_vault_name, - parameters=DiagnosticSettingsResource( - storage_account_id=stg_acc.id, logs=[log], - ), + account_name, + key.name, + key_vault.properties.vault_uri, ) - logging.info("executing storage_client.storage_accounts.update") - logging.info(f" resource_group_name={resource_group_name}") - logging.info(f" account_name={account_name}") - - storage_client.storage_accounts.update( - resource_group_name=resource_group_name, - account_name=account_name, - parameters=StorageAccountUpdateParameters( - encryption=Encryption( - key_source=KeySource.MICROSOFT_KEYVAULT, - key_vault_properties=KeyVaultProperties( - key_name=key.name, - key_vault_uri=key_vault.properties.vault_uri, - ), - ), - ), - ) except Exception as e: logging.error(f"{str(e)}") raise @@ -336,6 +692,7 @@ def run(self, args): params["resource_group_name"], params["account_name"], params["region"], + params["subscription_id"], ) diff --git a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/minimum_permissions.json b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/minimum_permissions.json index 0f83e55..628a522 100644 --- a/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/minimum_permissions.json +++ b/remediation_worker/jobs/azure_storage_encryption_at_rest_not_configured_with_customer_managed_key/minimum_permissions.json @@ -9,11 +9,14 @@ "actions": [ "Microsoft.Storage/storageAccounts/read", "Microsoft.Storage/storageAccounts/write", + "Microsoft.Storage/storageAccounts/blobServices/write", + "Microsoft.Storage/storageAccounts/blobServices/read", "Microsoft.Insights/DiagnosticSettings/Write", "Microsoft.KeyVault/vaults/read", "Microsoft.KeyVault/vaults/write", "Microsoft.KeyVault/vaults/keys/read", - "Microsoft.KeyVault/vaults/keys/write" + "Microsoft.KeyVault/vaults/keys/write", + "Microsoft.KeyVault/vaults/accessPolicies/write" ], "notActions": [], "dataActions": [], diff --git a/test/unit/test_azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py b/test/unit/test_azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py index 0e474d9..48438f7 100644 --- a/test/unit/test_azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py +++ b/test/unit/test_azure_storage_encryption_at_rest_not_configured_with_customer_managed_key.py @@ -55,37 +55,85 @@ def test_parse_payload(self, valid_payload): assert params["subscription_id"] == "subscription_id" assert params["region"] == "region" - def test_remediate_success_without_storage_identity(self): - monitor_client = Mock() + def test_remediate_with_keyvault(self): graph_client = Mock() storage_client = Mock() keyvault_client = Mock() + monitor_client = Mock() credentials = Mock() client_id = Mock() tenant_id = Mock() - storage_client.storage_accounts.get_properties.return_value = StorageAccount( - id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.Storage/storageAccounts/kshrutikagfigz", - name="kshrutikagfigz5", - type="Microsoft.Storage/storageAccounts", - location="eastus", - identity=None, + action = StorageAccountNotEncryptedWithCmk() + action.create_key = Mock() + action.check_key_vault = Mock() + action.update_key_vault_access_policy = Mock() + action.update_storage_account_encryption = Mock() + action.ensure_identity_assigned = Mock() + action.create_key.return_value = KeyVaultKey( + key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", + name="rem-key1", + ) + action.ensure_identity_assigned.return_value = ( + "139bcf82-e14e-4773-bcf4-1da136674792" + ) + action.check_key_vault.return_value = Vault( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", + name="stg-keyvault-rem", + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard"), + vault_uri="https://stg-keyvault-rem.vault.azure.net", + ), + ) + assert ( + action.remediate( + monitor_client, + graph_client, + storage_client, + keyvault_client, + client_id, + tenant_id, + credentials, + "resource_group_name", + "account-name", + "region", + "subscription_id", + ) + == 0 ) + assert action.ensure_identity_assigned.call_count == 1 + assert action.check_key_vault.call_count == 1 + assert action.create_key.call_count == 1 + assert action.update_key_vault_access_policy.call_count == 1 + assert action.update_storage_account_encryption.call_count == 1 + + def test_remediate_without_keyvault_with_stg(self): + graph_client = Mock() + storage_client = Mock() + keyvault_client = Mock() + monitor_client = Mock() + credentials = Mock() + client_id = Mock() + tenant_id = Mock() + + action = StorageAccountNotEncryptedWithCmk() + action.create_key = Mock() + action.create_key_vault = Mock() + action.check_key_vault = Mock() + action.check_stg_account = Mock() + action.update_storage_account_encryption = Mock() + action.create_diagnostic_setting = Mock() + action.ensure_identity_assigned = Mock() + identity = Identity( principal_id="139bcf82-e14e-4773-bcf4-1da136674792", type="SystemAssigned", tenant_id="b39138ca-3cee-4b4a-a4d6-cd83d9dd62f0", ) - storage_client.storage_accounts.update.return_value = StorageAccount( - id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", - name="remserver5", - type="Microsoft.Sql/servers", - location="eastus", - identity=identity, + action.ensure_identity_assigned.return_value = ( + "139bcf82-e14e-4773-bcf4-1da136674792" ) - action = StorageAccountNotEncryptedWithCmk() - action.create_key = Mock() - action.create_key_vault = Mock() action.create_key_vault.return_value = Vault( id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", name="stg-keyvault-rem", @@ -99,6 +147,14 @@ def test_remediate_success_without_storage_identity(self): key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", name="rem-key1", ) + action.check_stg_account.return_value = StorageAccount( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", + name="remstg5", + type="Microsoft.Storage/storageAccounts", + location="eastus", + identity=identity, + ) + action.check_key_vault.return_value = None assert ( action.remediate( monitor_client, @@ -111,46 +167,45 @@ def test_remediate_success_without_storage_identity(self): "resource_group_name", "account-name", "region", + "subscription_id", ) == 0 ) - assert storage_client.storage_accounts.update.call_count == 2 - call_args = storage_client.storage_accounts.update.call_args - updated_storage_account = call_args[1]["parameters"] - assert updated_storage_account.encryption.key_source == "Microsoft.Keyvault" - assert ( - updated_storage_account.encryption.key_vault_properties.key_name - == "rem-key1" - ) - assert ( - updated_storage_account.encryption.key_vault_properties.key_vault_uri - == "https://stg-keyvault-rem.vault.azure.net" - ) + assert action.ensure_identity_assigned.call_count == 1 + assert action.check_key_vault.call_count == 1 + assert action.check_stg_account.call_count == 1 + assert action.create_key.call_count == 1 + assert action.update_storage_account_encryption.call_count == 1 + assert action.create_diagnostic_setting.call_count == 1 - def test_remediate_success_with_storage_identity(self): - monitor_client = Mock() + def test_remediate_without_keyvault_without_stg(self): graph_client = Mock() storage_client = Mock() keyvault_client = Mock() + monitor_client = Mock() credentials = Mock() client_id = Mock() tenant_id = Mock() + action = StorageAccountNotEncryptedWithCmk() + action.create_key = Mock() + action.create_key_vault = Mock() + action.check_key_vault = Mock() + action.check_stg_account = Mock() + action.update_key_vault_access_policy = Mock() + action.update_storage_account_encryption = Mock() + action.create_diagnostic_setting = Mock() + action.create_storage_account = Mock() + action.ensure_identity_assigned = Mock() + identity = Identity( principal_id="139bcf82-e14e-4773-bcf4-1da136674792", type="SystemAssigned", tenant_id="b39138ca-3cee-4b4a-a4d6-cd83d9dd62f0", ) - storage_client.storage_accounts.get_properties.return_value = StorageAccount( - id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.Storage/storageAccounts/kshrutikagfigz", - name="kshrutikagfigz5", - type="Microsoft.Storage/storageAccounts", - location="eastus", - identity=identity, + action.ensure_identity_assigned.return_value = ( + "139bcf82-e14e-4773-bcf4-1da136674792" ) - action = StorageAccountNotEncryptedWithCmk() - action.create_key = Mock() - action.create_key_vault = Mock() action.create_key_vault.return_value = Vault( id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", name="stg-keyvault-rem", @@ -164,7 +219,15 @@ def test_remediate_success_with_storage_identity(self): key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", name="rem-key1", ) - + action.create_storage_account.return_value = StorageAccount( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", + name="remstg5", + type="Microsoft.Storage/storageAccounts", + location="eastus", + identity=identity, + ) + action.check_stg_account.return_value = None + action.check_key_vault.return_value = None assert ( action.remediate( monitor_client, @@ -177,24 +240,20 @@ def test_remediate_success_with_storage_identity(self): "resource_group_name", "account-name", "region", + "subscription_id", ) == 0 ) - assert storage_client.storage_accounts.update.call_count == 1 - call_args = storage_client.storage_accounts.update.call_args - updated_storage_account = call_args[1]["parameters"] - assert updated_storage_account.encryption.key_source == "Microsoft.Keyvault" - assert ( - updated_storage_account.encryption.key_vault_properties.key_name - == "rem-key1" - ) - assert ( - updated_storage_account.encryption.key_vault_properties.key_vault_uri - == "https://stg-keyvault-rem.vault.azure.net" - ) + assert action.ensure_identity_assigned.call_count == 1 + assert action.check_key_vault.call_count == 1 + assert action.check_stg_account.call_count == 1 + assert action.create_key.call_count == 2 + assert action.update_key_vault_access_policy.call_count == 1 + assert action.update_storage_account_encryption.call_count == 2 + assert action.create_storage_account.call_count == 1 + assert action.create_diagnostic_setting.call_count == 1 def test_remediate_with_exception(self): - monitor_client = Mock() graph_client = Mock() storage_client = Mock() keyvault_client = Mock() @@ -204,18 +263,14 @@ def test_remediate_with_exception(self): storage_client.storage_accounts.update.side_effect = Exception action = StorageAccountNotEncryptedWithCmk() with pytest.raises(Exception): - assert ( - action.remediate( - monitor_client, - graph_client, - storage_client, - keyvault_client, - client_id, - tenant_id, - credentials, - "resource_group", - "security_group", - "region", - ) - == 0 + assert action.remediate( + graph_client, + storage_client, + keyvault_client, + client_id, + tenant_id, + credentials, + "resource_group", + "security_group", + "region", ) From 828b1d184e12d73a72fe27edd84b0fde64a3ec94 Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Fri, 12 Feb 2021 16:25:37 +0530 Subject: [PATCH 32/34] PLA-23081 - Updated Remediation Job to enable SQL Auditing on Server (#47) * PLA-23081 - Updated Remediation Job to enable SQL Auditing on Server * PLA-23081 - Added Comments * PLA-23081 - Updated the SQL Auditing job --- .../azure_sql_auditing_on_server/README.md | 27 +- .../azure_sql_auditing_on_server.py | 837 +++++++++++++++--- .../constraints.txt | 21 +- .../minimum_permissions.json | 11 +- .../requirements.txt | 18 +- .../unit/test_azure_sql_auditing_on_server.py | 207 +++-- 6 files changed, 924 insertions(+), 197 deletions(-) diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/README.md b/remediation_worker/jobs/azure_sql_auditing_on_server/README.md index 441c536..db99ae9 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/README.md +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/README.md @@ -1,6 +1,7 @@ # Enable SQL Server Auditing -This job enables server blob auditing policy for the SQL Database Server by creating a Storage Account and assigning a Storage Blob Data Contributer role to the server. +This job enables server blob auditing policy for the SQL Database Server. It checks for the existence of the Storage Account created by CHSS, if the Storage Account exists then it assigns a Storage Blob Contributer Role to the SQL Server. If the Storage Account Created by CHSS does not exists then it creates one. +The Storage Account created by CHSS is prefixed with "chss" and contains tag `{"Created By" : "CHSS"}`. ### Applicable Rule @@ -19,7 +20,16 @@ The provided Azure service principal must have the following permissions: `Microsoft.Sql/servers/auditingSettings/write` `Microsoft.Storage/storageAccounts/write` `Microsoft.Storage/storageAccounts/read` +`Microsoft.Storage/storageAccounts/blobServices/write` +`Microsoft.Storage/storageAccounts/blobServices/read` `Microsoft.Authorization/roleAssignments/write` +`Microsoft.Authorization/roleAssignments/read` +`Microsoft.Insights/DiagnosticSettings/Write` +`Microsoft.KeyVault/vaults/read` +`Microsoft.KeyVault/vaults/write` +`Microsoft.KeyVault/vaults/keys/read` +`Microsoft.KeyVault/vaults/keys/write` +`Microsoft.KeyVault/vaults/accessPolicies/write` A sample role with requisite permissions can be found [here](minimum_permissions.json) @@ -40,13 +50,16 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` ## Deployment -Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. -Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. -Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: +Provision an instance by creating an Azure Virtual Machine to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker on newly provisioned Azure Virtual Machine instance. You can refer to the [docs here](https://docs.microsoft.com/en-us/previous-versions/azure/virtual-machines/linux/docker-machine) for more information. +Deploy the worker docker image by SSH into the Azure Virtual Machine instance and run the following commands: ```shell script - docker run --rm -it --name worker \ - -e VSS_CLIENT_ID={ENTER CLIENT ID} - -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + docker run --rm -it --name {worker_name}\ + -e VSS_CLIENT_ID={ENTER CLIENT ID}\ + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET}\ + -e AZURE_CLIENT_ID={ENTER AZURE_CLIENT_ID} \ + -e AZURE_CLIENT_SECRET={ENTER AZURE_CLIENT_SECRET} \ + -e AZURE_TENANT_ID={ENTER AZURE_TENANT_ID} \ vmware/vss-remediation-worker:latest-python ``` ## Contributing diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py index 8146bf7..df05e04 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/azure_sql_auditing_on_server.py @@ -2,15 +2,22 @@ import os import sys import logging -import random -import string import uuid +import datetime +from dateutil import parser as date_parse +from typing import List +from azure.core.paging import ItemPaged +from azure.keyvault.keys import KeyClient +from azure.mgmt.monitor import MonitorClient from azure.identity import ClientSecretCredential +from azure.graphrbac import GraphRbacManagementClient +from azure.mgmt.keyvault import KeyVaultManagementClient from azure.common.credentials import ServicePrincipalCredentials from azure.mgmt.authorization import AuthorizationManagementClient from azure.mgmt.sql import SqlManagementClient from azure.mgmt.storage import StorageManagementClient +from azure.mgmt.storage.models import Sku as sku_storage from azure.mgmt.sql.models import ( ServerBlobAuditingPolicy, BlobAuditingPolicyState, @@ -21,57 +28,68 @@ from azure.mgmt.storage.models import ( StorageAccountCreateParameters, NetworkRuleSet, - Sku, SkuName, SkuTier, DefaultAction, + Identity, + Encryption, + KeySource, + KeyVaultProperties, + BlobServiceProperties, + DeleteRetentionPolicy, + StorageAccountListResult, + StorageAccountUpdateParameters, +) +from azure.mgmt.monitor.models import ( + DiagnosticSettingsResource, + LogSettings, + RetentionPolicy, +) +from azure.mgmt.keyvault.models import ( + VaultCreateOrUpdateParameters, + VaultProperties, + Sku, + AccessPolicyEntry, + Permissions, + KeyPermissions, + AccessPolicyUpdateKind, + VaultListResult, + VaultAccessPolicyParameters, + VaultAccessPolicyProperties, ) from azure.mgmt.authorization.models import ( RoleAssignmentCreateParameters, - PrincipalType, + RoleAssignmentListResult, + RoleAssignmentProperties, ) logging.basicConfig(level=logging.INFO) - -def generate_storage_account_name(prefix): - prefix = "".join(i for i in prefix if i.islower() or i.isdigit()) - if len(prefix) >= 15: - prefix = str(prefix[:14]) - result_str = prefix + "auditlogs" - return result_str +MAX_COUNT_SUBSCRIPTION = 5 +MAX_COUNT_RESOURCE_NAME = 5 +MAX_COUNT_REGION = 6 +MAX_COUNT_COMPONENT = 4 -def create_storage_account( - resource_group_name, name, region, client_storage, -): - create_params = StorageAccountCreateParameters( - location=region, - sku=Sku(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), - kind="StorageV2", - enable_https_traffic_only=True, - network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), - ) - poller = client_storage.storage_accounts.begin_create( - resource_group_name=resource_group_name, - account_name=name, - parameters=create_params, - ) - return poller.result() - - -def create_role_assignment( - stg_account_name, subscription_id, client_authorization, guid, Scope, principalId, -): - client_authorization.role_assignments.create( - scope=Scope, - role_assignment_name=guid, - parameters=RoleAssignmentCreateParameters( - role_definition_id=f"/subscriptions/{subscription_id}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe", - principal_id=principalId, - principal_type=PrincipalType.service_principal, - ), - ) +def generate_name(region, subscription_id, resource_group_name): + """Generates a name for the resource + :param region: location in which the resource exists + :param subscription_id: Azure Subscription Id + :param resource_group_name: Resource group name in which the resource exists + :type region: str + :type subscription_id: str + :type resource_group_name: str + :returns: resource name + :rtype: str + """ + random_str = "".join(i for i in subscription_id if i.islower() or i.isdigit()) + subscription_id = random_str[:MAX_COUNT_SUBSCRIPTION] + random_str = "".join(i for i in region if i.islower() or i.isdigit()) + region = random_str[-MAX_COUNT_REGION:] + random_str = "".join(i for i in resource_group_name if i.islower() or i.isdigit()) + resource_group_name = random_str[-MAX_COUNT_RESOURCE_NAME:] + result_str = "chss" + subscription_id + resource_group_name + region + "logs" + return result_str class SqlServerEnableBlobAuditingPolicy(object): @@ -84,18 +102,13 @@ def parse(self, payload): :raises: KeyError, JSONDecodeError """ remediation_entry = json.loads(payload) - object_id = remediation_entry["notificationInfo"]["FindingInfo"]["ObjectId"] - region = remediation_entry["notificationInfo"]["FindingInfo"]["Region"] - object_chain = remediation_entry["notificationInfo"]["FindingInfo"][ "ObjectChain" ] - object_chain_dict = json.loads(object_chain) subscription_id = object_chain_dict["cloudAccountId"] - properties = object_chain_dict["properties"] resource_group_name = "" for property in properties: @@ -115,10 +128,503 @@ def parse(self, payload): "region": region, } + def check_stg_account(self, storage_client, region, name, resource_group_name): + """Checks For the existence of the Storage Account created by CHSS + :param storage_client: Instance of the Azure StorageManagementClient. + :param region: The location in which the storage account exists. + :param name: The Storage Account name. + :param resource_group_name: The name of the resource group. + :type storage_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: StorageAccount object + :rtype: object + """ + storage_accounts_paged: ItemPaged[ + StorageAccountListResult + ] = storage_client.storage_accounts.list() + storage_accounts_list: List[dict] = list(storage_accounts_paged) + storage_account = None + for stg_account in storage_accounts_list: + stg_id = stg_account.id + stg_components = stg_id.split("/") + if len(stg_components) > MAX_COUNT_COMPONENT: + resource_grp = stg_components[MAX_COUNT_COMPONENT] + if ( + stg_account.name == name + and stg_account.location == region + and resource_grp == resource_group_name + ): + storage_account = stg_account + break + return storage_account + + def check_key_vault(self, keyvault_client, region, name, resource_group_name): + """Checks for the existence of the Key Vault created by CHSS. + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param region: The location in which the Key Vault exists. + :param name: Key Vault name. + :param resource_group_name: The name of the resource group. + :type keyvault_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: Vault object + :rtype: object + """ + key_vault_paged: ItemPaged[ + VaultListResult + ] = keyvault_client.vaults.list_by_subscription() + key_vault_list: List[dict] = list(key_vault_paged) + chss_key_vault = None + for key_vault in key_vault_list: + key_vault_id = key_vault.id + key_vault_components = key_vault_id.split("/") + if len(key_vault_components) > MAX_COUNT_COMPONENT: + resource_grp = key_vault_components[MAX_COUNT_COMPONENT] + if ( + key_vault.name == name + and key_vault.location == region + and resource_grp == resource_group_name + ): + chss_key_vault = key_vault + break + return chss_key_vault + + def check_role_assignment( + self, client_authorization, scope, principal_id, role_definition_id + ): + """Checks if the Role Assignment already exists + :param client_authorization: Instance of the Azure AuthorizationManagementClient. + :param scope: Scope for which to check the existence of the Role Assignment. + :param principal_id: Principal Id of the SQL Server. + :param role_definition_id: Role Definition Id. + :type client_authorization: object + :type scope: str + :type principal_id: str + :type role_definition_id: str + :returns: Boolean indicating success or failure + :rtype: bool + """ + role_assignment_paged: ItemPaged[ + RoleAssignmentListResult + ] = client_authorization.role_assignments.list_for_scope(scope=scope) + role_assignment_list: List[dict] = list(role_assignment_paged) + for role in role_assignment_list: + if ( + role.principal_id == principal_id + and role.role_definition_id == role_definition_id + ): + return True + return False + + def create_storage_account( + self, resource_group_name, name, region, storage_client, + ): + """Creates a Storage Account + :param storage_client: Instance of the Azure StorageManagementClient. + :param region: The location in which the storage account exists. + :param name: The Storage Account name. + :param resource_group_name: The name of the resource group. + :type storage_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: StorageAccount object + :rtype: object + """ + + create_params = StorageAccountCreateParameters( + location=region, + sku=sku_storage(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), + identity=Identity(type="SystemAssigned"), + kind="StorageV2", + enable_https_traffic_only=True, + network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), + tags={"Created By": "CHSS"}, + ) + stg_account = storage_client.storage_accounts.begin_create( + resource_group_name=resource_group_name, + account_name=name, + parameters=create_params, + ).result() + + storage_client.blob_services.set_service_properties( + resource_group_name=resource_group_name, + account_name=name, + parameters=BlobServiceProperties( + delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7) + ), + ) + return stg_account + + def update_storage_account_encryption( + self, storage_client, resource_group_name, stg_name, key_name, vault_uri + ): + """Updates Storage Account Encryption for a Storage Account. + :param storage_client: Instance of the Azure StorageManagementClient. + :param resource_group_name: The name of the resource group. + :param stg_name: The Storage Account name. + :param key_name: Name of the Key to encrypt the Storage Account with. + :param vault_uri: Key Vault uri in which the Key exists. + :type storage_client: object + :type resource_group_name: str + :type stg_name: str + :type key_name: str + :type vault_uri: str + :returns: None + :rtype: None + """ + logging.info(" Encrypting Storage Account with Customer Managed Key") + logging.info(" executing storage_client.storage_accounts.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={stg_name}") + logging.info(f" key_vault_uri={vault_uri}") + logging.info(f" key_name={key_name}") + storage_client.storage_accounts.update( + resource_group_name=resource_group_name, + account_name=stg_name, + parameters=StorageAccountUpdateParameters( + encryption=Encryption( + key_source=KeySource.MICROSOFT_KEYVAULT, + key_vault_properties=KeyVaultProperties( + key_name=key_name, key_vault_uri=vault_uri, + ), + ), + ), + ) + + def create_diagnostic_setting( + self, monitor_client, key_vault_id, key_vault_name, stg_account_id, log + ): + """Creates a diagnostic setting + :param monitor_client: Instance of the Azure StorageManagementClient. + :param key_vault_id: The resource Id of the Key Vault. + :param key_vault_name: Name of the Key Vault. + :param stg_account_id: The Storage Account resource Id. + :param log: Instance of Azure Monitor LogSettings + :type monitor_client: object + :type log: object + :type key_vault_id: str + :type key_vault_name: str + :type stg_account_id: str + :returns: None + :rtype: None + """ + logging.info(" Creating a Diagnostic setting for key vault logs") + logging.info( + " executing monitor_client.diagnostic_settings.create_or_update" + ) + logging.info(f" resource_uri={key_vault_id}") + logging.info(f" name={key_vault_name}") + monitor_client.diagnostic_settings.create_or_update( + resource_uri=key_vault_id, + name=key_vault_name, + parameters=DiagnosticSettingsResource( + storage_account_id=stg_account_id, logs=[log], + ), + ) + + def create_key_vault( + self, + keyvault_client, + resource_group_name, + key_vault_name, + region, + tenant_id, + app_object_id, + stg_principal_id, + ): + """Creates a Key Vault + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param resource_group_name: The name of the resource group. + :param key_vault_name: Name of the Key Vault. + :param region: location of the Key Vault. + :param tenant_id: Azure tenant Id + :param app_object_id: Object Id of the application + :param stg_principal_id: Principal Id of the Storage Account + :type keyvault_client: object + :type resource_group_name: str + :type key_vault_name: str + :type region: str + :type tenant_id: str + :type app_object_id: str + :type stg_principal_id: str + :returns: Vault object + :rtype: object + """ + access_policy_storage_account = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=stg_principal_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.UNWRAP_KEY, + KeyPermissions.WRAP_KEY, + ], + ), + ) + access_policy_app = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=app_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.LIST, + KeyPermissions.CREATE, + KeyPermissions.UPDATE, + KeyPermissions.DELETE, + KeyPermissions.BACKUP, + KeyPermissions.RESTORE, + KeyPermissions.RECOVER, + ], + ), + ) + key_vault_properties = VaultCreateOrUpdateParameters( + location=region, + tags={"Created By": "CHSS"}, + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard",), + access_policies=[access_policy_storage_account, access_policy_app], + soft_delete_retention_in_days=90, + enabled_for_disk_encryption=False, + enabled_for_deployment=False, + enabled_for_template_deployment=False, + enable_soft_delete=True, + enable_purge_protection=True, + ), + ) + logging.info("creating a key vault") + logging.info("executing keyvault_client.vaults.begin_create_or_update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" vault_name={key_vault_name}") + vault = keyvault_client.vaults.begin_create_or_update( + resource_group_name=resource_group_name, + vault_name=key_vault_name, + parameters=key_vault_properties, + ).result() + return vault + + def update_key_vault_access_policy( + self, + keyvault_client, + resource_group_name, + key_vault_name, + tenant_id, + app_object_id, + stg_object_id, + ): + """Updates Key Vault Access Policy + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param resource_group_name: The name of the resource group. + :param key_vault_name: Name of the Key Vault. + :param region: The location in which the Key Vault exists. + :param tenant_id: Azure tenant Id + :param app_object_id: Object Id of the application + :param stg_principal_id: Principal Id of the Storage Account + :type keyvault_client: object + :type resource_group_name: str + :type key_vault_name: str + :type region: str + :type tenant_id: str + :type app_object_id: str + :type stg_principal_id: str + :returns: None + :rtype: None + """ + access_policy_storage = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=stg_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.UNWRAP_KEY, + KeyPermissions.WRAP_KEY, + ], + ), + ) + access_policy_app = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=app_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.LIST, + KeyPermissions.CREATE, + KeyPermissions.UPDATE, + KeyPermissions.DELETE, + KeyPermissions.BACKUP, + KeyPermissions.RESTORE, + KeyPermissions.RECOVER, + ], + ), + ) + access_policy = [access_policy_app, access_policy_storage] + + logging.info("Updating Key Vault Access Policy") + logging.info("executing keyvault_client.vaults.update_access_policy") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" vault_name={key_vault_name}") + keyvault_client.vaults.update_access_policy( + resource_group_name=resource_group_name, + vault_name=key_vault_name, + operation_kind=AccessPolicyUpdateKind.ADD, + parameters=VaultAccessPolicyParameters( + properties=VaultAccessPolicyProperties(access_policies=access_policy), + ), + ) + + def create_key(self, credential, key_vault_name, suffix): + """Creates a Key within the given Key Vault + :param credential: Azure Credentials + :param key_vault_name: Name of the Key Vault. + :param suffix: suffix for Key name + :type key_vault_name: str + :type suffix: str + :returns: Azure Key object which was created + :rtype: object + """ + d = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() + date = datetime.datetime.strptime( + d[0:19], "%Y-%m-%dT%H:%M:%S" + ) + datetime.timedelta(days=180) + expires_on = date_parse.parse( + date.replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() + ) + key_client = KeyClient( + vault_url=f"https://{key_vault_name}.vault.azure.net/", + credential=credential, + ) + rsa_key_name = key_vault_name + "-" + suffix + logging.info("creating a key") + rsa_key = key_client.create_rsa_key( + rsa_key_name, size=2048, expires_on=expires_on, enabled=True + ) + return rsa_key + + def create_role_assignment( + self, + stg_account_name, + subscription_id, + client_authorization, + guid, + scope, + principalId, + sql_server_name, + ): + """Creates a Role Assignment + :param stg_account_name: Storage Account name + :param subscription_id: Azure Subscription Id + :param client_authorization: Instance of the Azure AuthorizationManagementClient. + :param guid: UUID for role name + :param scope: The scope of the role assignment. + :param principalId: Principal Id of the SQL Server + :param sql_server_name: SQL Server Name + :type client_authorization: object + :type stg_account_name: str + :type subscription_id: str + :type guid: str + :type scope: str + :type principalId: str + :type sql_server_name: str + :returns: None + :rtype: None + """ + logging.info( + f"Creating a Role Assignment for Storage Account {stg_account_name} and assigning Storage Blob Data Contributer Role to the SQL Database Server {sql_server_name}" + ) + logging.info("executing client_authorization.role_assignments.create") + logging.info(f" scope={scope}") + logging.info(f" role_assignment_name={guid}") + client_authorization.role_assignments.create( + scope=scope, + role_assignment_name=guid, + parameters=RoleAssignmentCreateParameters( + properties=RoleAssignmentProperties( + role_definition_id=f"/subscriptions/{subscription_id}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe", + principal_id=principalId, + ), + ), + ) + + def create_server_blob_auditing_policy( + self, resource_group_name, sql_server_name, stg_account_name, client + ): + """Creates Server Blob Auditing Policy + :param resource_group_name: Resource group name + :param sql_server_name: SQL Server name + :param stg_account_name: Storage Account name + :param client: Instance of the Azure SqlManagementClient. + :type client: object + :type resource_group_name: str + :type sql_server_name: str + :type stg_account_name: str + :returns: None + :rtype: None + """ + logging.info("Enabling Server blob auditing policy for Azure SQL Server") + logging.info( + " executing client.server_blob_auditing_policies.create_or_update" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" server_name={sql_server_name}") + + client.server_blob_auditing_policies.create_or_update( + resource_group_name=resource_group_name, + server_name=sql_server_name, + parameters=ServerBlobAuditingPolicy( + state=BlobAuditingPolicyState.enabled, + storage_endpoint=f"https://{stg_account_name}.blob.core.windows.net/", + ), + ) + + def ensure_identity_assigned( + self, client, resource_group_name, sql_server_name, region + ): + """Checks if the Identity is assigned to the SQL Server. If not then it assigns the identity + :param client: Instance of the Azure SqlManagementClient. + :param resource_group_name: Resource group name + :param sql_server_name: SQL Server name + :param region: location in which the SQL Server exists + :type client: object + :type resource_group_name: str + :type sql_server_name: str + :type region: str + :returns: Principal Id of the SQL Server + :rtype: str + """ + server = client.servers.get( + resource_group_name=resource_group_name, server_name=sql_server_name, + ) + if server.identity is None: + logging.info( + f"Assigning Azure Active Directory Identity to the SQL Database Server {sql_server_name}" + ) + logging.info("executing client.servers.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" server_name={sql_server_name}") + server = client.servers.update( + resource_group_name=resource_group_name, + server_name=sql_server_name, + parameters=Server( + location=region, + identity=ResourceIdentity(type=IdentityType.system_assigned), + ), + ).result() + return server.identity.principal_id + def remediate( self, + client_id, + tenant_id, + credentials, client, client_storage, + keyvault_client, + graph_client, + monitor_client, client_authorization, resource_group_name, sql_server_name, @@ -126,84 +632,154 @@ def remediate( subscription_id, ): """Enable Server blob auditing policy for Azure SQL Server + :param client_id: Azure Client ID. + :param tenant_id: Azure Tenant ID. + :param graph_client: Instance of the AzureGraphRbacManagementClient. + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param monitor_client: Instance of the Azure MonitorClient. + :param credentials: Azure Credential object. :param client: Instance of the Azure SqlManagementClient. :param client_storage: Instance of the Azure StorageManagementClient. :param resource_group_name: The name of the resource group to which the SQL Server belongs. :param sql_server_name: The name of the SQL Server. + :param subscription_id: Azure Subscription Id + :type client_id: str + :type tenant_id: str + :type graph_client: object + :type keyvault_client: object + :type client_storage: object + :type monitor_client: object + :type credentials: object + :type client: object :type resource_group_name: str. :type sql_server_name: str. :type region: str. + :type subscription_id: str :returns: Integer signaling success or failure :rtype: int :raises: msrestazure.azure_exceptions.CloudError """ - try: - server = client.servers.get( - resource_group_name=resource_group_name, server_name=sql_server_name, - ) - if server.identity is None: - logging.info( - f"Assigning Azure Active Directory Identity to the SQL Database Server {sql_server_name}" - ) - logging.info("executing client.servers.update") - logging.info(f" resource_group_name={resource_group_name}") - logging.info(f" server_name={sql_server_name}") - updated_server = client.servers.update( - resource_group_name=resource_group_name, - server_name=sql_server_name, - parameters=Server( - location=region, - identity=ResourceIdentity(type=IdentityType.system_assigned), - ), - ).result() - principalId = updated_server.identity.principal_id - else: - principalId = server.identity.principal_id - - stg_account_name = generate_storage_account_name(sql_server_name) - logging.info(f"Creating a storage account with name {stg_account_name}") - logging.info("executing client_storage.storage_accounts.begin_create") - logging.info(f" resource_group_name={resource_group_name}") - logging.info(f" storage_account_name={stg_account_name}") - - create_storage_account( - resource_group_name, stg_account_name, region, client_storage - ) - guid = uuid.uuid4() - Scope = f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Storage/storageAccounts/{stg_account_name}" - - logging.info( - f"Creating a Role Assignment for Storage Account {stg_account_name} and assigning Storage Blob Data Contributer Role to the SQL Database Server {sql_server_name}" + # Check if identity is assigned. If not then assign the identity. + principalId = self.ensure_identity_assigned( + client, resource_group_name, sql_server_name, region ) - logging.info("executing client_authorization.role_assignments.create") - logging.info(f" scope={Scope}") - logging.info(f" role_assignment_name={guid}") - - create_role_assignment( - stg_account_name, - subscription_id, - client_authorization, - guid, - Scope, - principalId, + # Check if the Storage Account Created by CHSS exists + stg_name = generate_name(region, subscription_id, resource_group_name) + stg_account = self.check_stg_account( + client_storage, region, stg_name, resource_group_name ) + # If Storage Account created by CHSS does not exists + if stg_account is None: + # Create a Storage Account to store logs if it does not exists + stg_account = self.create_storage_account( + resource_group_name, stg_name, region, client_storage, + ) - logging.info("Enabling Server blob auditing policy for Azure SQL Server") - logging.info( - " executing client.server_blob_auditing_policies.create_or_update" - ) - logging.info(f" resource_group_name={resource_group_name}") - logging.info(f" server_name={sql_server_name}") + app_details = graph_client.applications.get_service_principals_id_by_app_id( + application_id=client_id + ) + log = LogSettings( + category="AuditEvent", + enabled=True, + retention_policy=RetentionPolicy(enabled=True, days=180), + ) + # Check if the keyvault created by CHSS exists + encryption_key_vault_name = generate_name( + region, subscription_id, resource_group_name + ) + key_vault = self.check_key_vault( + keyvault_client, + region, + encryption_key_vault_name, + resource_group_name, + ) + # If Key Vault created by CHSS does not exists + if key_vault is None: + # Create a Key Vault if it does not exists + key_vault = self.create_key_vault( + keyvault_client, + resource_group_name, + encryption_key_vault_name, + region, + tenant_id, + app_details.value, + stg_account.identity.principal_id, + ) + # Create a Key to encrypt the Storage Account + key = self.create_key( + credentials, encryption_key_vault_name, stg_account.name + ) + # Create a diagnostic setting to store Key Vault logs + self.create_diagnostic_setting( + monitor_client, + key_vault.id, + key_vault.name, + stg_account.id, + log, + ) + else: + # If the Key Vault exists. Update the access policy of the Key Vault by giving access to the app and Storage Account + self.update_key_vault_access_policy( + keyvault_client, + resource_group_name, + key_vault.name, + tenant_id, + app_details.value, + stg_account.identity.principal_id, + ) + # Create a Key to encrypt the Storage Account + key = self.create_key(credentials, key_vault.name, stg_account.name) + # Encrypt the Storage Account using the Key which was created + self.update_storage_account_encryption( + client_storage, + resource_group_name, + stg_account.name, + key.name, + key_vault.properties.vault_uri, + ) - client.server_blob_auditing_policies.create_or_update( - resource_group_name=resource_group_name, - server_name=sql_server_name, - parameters=ServerBlobAuditingPolicy( - state=BlobAuditingPolicyState.enabled, - storage_endpoint=f"https://{stg_account_name}.blob.core.windows.net/", - ), + scope = f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Storage/storageAccounts/{stg_account.name}" + # Assign Storage Blob Contributer role to the SQL Server + self.create_role_assignment( + stg_name, + subscription_id, + client_authorization, + guid, + scope, + principalId, + sql_server_name, + ) + else: + # If the Storage Account Created by CHSS exists + stg_id = stg_account.id + stg_components = stg_id.split("/") + if len(stg_components) > MAX_COUNT_COMPONENT: + resource_grp = stg_components[MAX_COUNT_COMPONENT] + scope = f"/subscriptions/{subscription_id}/resourceGroups/{resource_grp}/providers/Microsoft.Storage/storageAccounts/{stg_account.name}" + role_definition_id = f"/subscriptions/{subscription_id}/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe" + # Check if the role assignment for the SQL Server in the Storage Account already exists + status = self.check_role_assignment( + client_authorization, scope, principalId, role_definition_id + ) + if status is False: + # If role assignment does not exists, assign the Storage Blob Contributer Role to the SQL Server + self.create_role_assignment( + stg_account.name, + subscription_id, + client_authorization, + guid, + scope, + principalId, + sql_server_name, + ) + else: + logging.error("Resource group name not found") + return -1 + # Enable blob auditing for the SQL Server + self.create_server_blob_auditing_policy( + resource_group_name, sql_server_name, stg_account.name, client ) except Exception as e: logging.error(f"{str(e)}") @@ -217,30 +793,55 @@ def run(self, args): :returns: int """ params = self.parse(args[1]) + client_id = os.environ.get("AZURE_CLIENT_ID") + client_secret = os.environ.get("AZURE_CLIENT_SECRET") + tenant_id = os.environ.get("AZURE_TENANT_ID") + + # credential for Storage Account and Key Vault management client + credential = ClientSecretCredential( + client_id=client_id, client_secret=client_secret, tenant_id=tenant_id, + ) + # credential for AzureGraphRbacManagementClient + credentials_graph = ServicePrincipalCredentials( + client_id=client_id, + secret=client_secret, + tenant=tenant_id, + resource="https://graph.windows.net", + ) + + # credential for SqlManagementClient and credentials = ServicePrincipalCredentials( - client_id=os.environ.get("AZURE_CLIENT_ID"), - secret=os.environ.get("AZURE_CLIENT_SECRET"), - tenant=os.environ.get("AZURE_TENANT_ID"), + client_id=client_id, secret=client_secret, tenant=tenant_id, + ) + + client_storage = StorageManagementClient(credential, params["subscription_id"]) + + keyvault_client = KeyVaultManagementClient( + credential, params["subscription_id"] ) - credentials_stg = ClientSecretCredential( - client_id=os.environ.get("AZURE_CLIENT_ID"), - client_secret=os.environ.get("AZURE_CLIENT_SECRET"), - tenant_id=os.environ.get("AZURE_TENANT_ID"), + graph_client = GraphRbacManagementClient( + credentials_graph, tenant_id, base_url=None ) + monitor_client = MonitorClient(credential, params["subscription_id"]) + client = SqlManagementClient( credentials, params["subscription_id"], base_url=None ) - client_storage = StorageManagementClient( - credentials_stg, params["subscription_id"] - ) + client_authorization = AuthorizationManagementClient( - credentials, params["subscription_id"] + credential, params["subscription_id"], api_version="2018-01-01-preview" ) return self.remediate( + client_id, + tenant_id, + credential, client, client_storage, + keyvault_client, + graph_client, + monitor_client, client_authorization, params["resource_group_name"], params["sql_server_name"], diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/constraints.txt b/remediation_worker/jobs/azure_sql_auditing_on_server/constraints.txt index 7a72ec4..7b3ac7f 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/constraints.txt +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/constraints.txt @@ -22,9 +22,24 @@ azure-mgmt-network==16.0.0 \ azure-mgmt-storage==16.0.0 \ --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 -azure-mgmt-authorization==0.61.0 \ - --hash=sha256:38f8afcd5c0065e598305de15dbb6b81521ac1e05216049f10ca32a435fd5817 \ - --hash=sha256:f5cceea3add04e9445ea88492f15eecf6c126f0406d967c95f6e48b79be8db75 +azure-mgmt-authorization==1.0.0 \ + --hash=sha256:151bebe4352dd5c73d1df8bfc861d6ae6c9697d09ad761c59ce7ec50c476dff7 \ + --hash=sha256:9a9fc16866b46387853381ab4fa0f84c1765e0afea5b0124709ea9fae10ee752 +azure-mgmt-keyvault==8.0.0 \ + --hash=sha256:2c974c6114d8d27152642c82a975812790a5e86ccf609bf370a476d9ea0d2e7d \ + --hash=sha256:99da24e9455f195d1e45a605af04d663e15cb5d5eb005b0ef60638474bbb2b3f +azure-graphrbac==0.61.1 \ + --hash=sha256:53e98ae2ca7c19b349e9e9bb1b6a824aeae8dcfcbe17190d20fe69c0f185b2e2 \ + --hash=sha256:7b4e0f05676acc912f2b33c71c328d9fb2e4dc8e70ebadc9d3de8ab08bf0b175 +azure-keyvault-keys==4.3.1 \ + --hash=sha256:25cf889bbcafd8dee0e068fd48d84e24e11143bf85e35c68d997a222e4a0f7fb \ + --hash=sha256:fbf67bca913ebf68b9075ee9d2e2b899dc3c7892cc40abfe1b08220a382f6ed9 +azure-mgmt-monitor==1.0.1 \ + --hash=sha256:04bd89d74fe47f966b09e3256ffefcfa5c1a51057a6b33c092afe5ae17a1a7b7 \ + --hash=sha256:92db4d133f58be900e5a2a9e3f44f4f0c068717bf534bf49c97e070c0280d203 +azure-keyvault==4.1.0 \ + --hash=sha256:5fa0438f7f6e2e79543f2724957acf77c3c187e558f4d030a4f9b7493b9f946d \ + --hash=sha256:69002a546921a8290eb54d9a3805cfc515c321bc1d4c0bfcfb463620245eca40 certifi==2020.6.20 \ --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json b/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json index 39bf271..2190e1e 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/minimum_permissions.json @@ -13,7 +13,16 @@ "Microsoft.Sql/servers/auditingSettings/write", "Microsoft.Storage/storageAccounts/write", "Microsoft.Storage/storageAccounts/read", - "Microsoft.Authorization/roleAssignments/write" + "Microsoft.Storage/storageAccounts/blobServices/write", + "Microsoft.Storage/storageAccounts/blobServices/read", + "Microsoft.Authorization/roleAssignments/write", + "Microsoft.Authorization/roleAssignments/read", + "Microsoft.Insights/DiagnosticSettings/Write", + "Microsoft.KeyVault/vaults/read", + "Microsoft.KeyVault/vaults/write", + "Microsoft.KeyVault/vaults/keys/read", + "Microsoft.KeyVault/vaults/keys/write", + "Microsoft.KeyVault/vaults/accessPolicies/write" ], "notActions": [], "dataActions": [], diff --git a/remediation_worker/jobs/azure_sql_auditing_on_server/requirements.txt b/remediation_worker/jobs/azure_sql_auditing_on_server/requirements.txt index 0d2469a..37dc598 100644 --- a/remediation_worker/jobs/azure_sql_auditing_on_server/requirements.txt +++ b/remediation_worker/jobs/azure_sql_auditing_on_server/requirements.txt @@ -4,9 +4,21 @@ azure-identity==1.4.1 \ azure-mgmt-sql==0.24.0 \ --hash=sha256:347f6b7d5e252d48331658fd5febefdacbc341641918df3111c735f4a6906e5e \ --hash=sha256:da391ed00d82cd8e20ca50affdc43b99fd9a7919b54a3a0d53c73cb41eea09d3 -azure-mgmt-authorization==0.61.0 \ - --hash=sha256:38f8afcd5c0065e598305de15dbb6b81521ac1e05216049f10ca32a435fd5817 \ - --hash=sha256:f5cceea3add04e9445ea88492f15eecf6c126f0406d967c95f6e48b79be8db75 azure-mgmt-storage==16.0.0 \ --hash=sha256:2f9d714d9722b1ef4bac6563676612e6e795c4e90f6f3cd323616fdadb0a99e5 \ --hash=sha256:a819e421d50c0b58416b551d3e9e9a9cf6029714cf977ffaaee86a37572e7113 +azure-mgmt-keyvault==8.0.0 \ + --hash=sha256:2c974c6114d8d27152642c82a975812790a5e86ccf609bf370a476d9ea0d2e7d \ + --hash=sha256:99da24e9455f195d1e45a605af04d663e15cb5d5eb005b0ef60638474bbb2b3f +azure-graphrbac==0.61.1 \ + --hash=sha256:53e98ae2ca7c19b349e9e9bb1b6a824aeae8dcfcbe17190d20fe69c0f185b2e2 \ + --hash=sha256:7b4e0f05676acc912f2b33c71c328d9fb2e4dc8e70ebadc9d3de8ab08bf0b175 +azure-keyvault-keys==4.3.1 \ + --hash=sha256:25cf889bbcafd8dee0e068fd48d84e24e11143bf85e35c68d997a222e4a0f7fb \ + --hash=sha256:fbf67bca913ebf68b9075ee9d2e2b899dc3c7892cc40abfe1b08220a382f6ed9 +azure-mgmt-monitor==1.0.1 \ + --hash=sha256:04bd89d74fe47f966b09e3256ffefcfa5c1a51057a6b33c092afe5ae17a1a7b7 \ + --hash=sha256:92db4d133f58be900e5a2a9e3f44f4f0c068717bf534bf49c97e070c0280d203 +azure-mgmt-authorization==1.0.0 \ + --hash=sha256:151bebe4352dd5c73d1df8bfc861d6ae6c9697d09ad761c59ce7ec50c476dff7 \ + --hash=sha256:9a9fc16866b46387853381ab4fa0f84c1765e0afea5b0124709ea9fae10ee752 diff --git a/test/unit/test_azure_sql_auditing_on_server.py b/test/unit/test_azure_sql_auditing_on_server.py index e964520..05c93f0 100644 --- a/test/unit/test_azure_sql_auditing_on_server.py +++ b/test/unit/test_azure_sql_auditing_on_server.py @@ -17,7 +17,16 @@ from remediation_worker.jobs.azure_sql_auditing_on_server.azure_sql_auditing_on_server import ( SqlServerEnableBlobAuditingPolicy, ) -from azure.mgmt.sql.models import Server, ResourceIdentity, BlobAuditingPolicyState +from azure.mgmt.storage.models import ( + StorageAccount, + Identity, +) +from azure.mgmt.keyvault.models import ( + VaultProperties, + Vault, + Sku, +) +from azure.keyvault.keys import KeyVaultKey @pytest.fixture @@ -26,11 +35,11 @@ def valid_payload(): { "notificationInfo": { "RuleId": "5c8c268a7a550e1fb6560cb9", - "Service": "Sql", + "Service": "Microsoft.Sql", "FindingInfo": { - "FindingId": "d0431afd-b82e-4021-8aa6-ba3cf5c60ef7", + "FindingId": "d3bb1d9a-fe52-4458-9935-47183f140e6b", "ObjectId": "sql_server_name", - "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Sql.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.Server.sql_server_name\\",\\"entityName\\":\\"sql_server_name\\",\\"entityType\\":\\"Azure.Sql.Server\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Sql\\",\\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", + "ObjectChain": "{\\"cloudAccountId\\":\\"subscription_id\\",\\"entityId\\":\\"Azure.Microsoft.Sql.server.d687b1a3-9b78-43b1-a17b-7de297fd1fce.resource_group_name.Microsoft.Sql.server.testingresourcename\\",\\"entityName\\":\\"testingresourcename\\",\\"entityType\\":\\"Azure.Microsoft.Sql.server\\",\\"lastUpdateTime\\":\\"2020-09-09T00:36:35.000Z\\",\\"partitionKey\\":\\"d687b1a3-9b78-43b1-a17b-7de297fd1fce\\",\\"provider\\":\\"Azure\\",\\"region\\":\\"eastus\\",\\"service\\":\\"Storage\\", \\"properties\\":[{\\"name\\":\\"ResourceGroup\\",\\"stringV\\":\\"resource_group_name\\",\\"type\\":\\"string\\"}]}", "Region": "region" } } @@ -38,7 +47,7 @@ def valid_payload(): """ -class TestEnableDdosProtection(object): +class TestSqlServerAuditing(object): def test_parse_payload(self, valid_payload): params = SqlServerEnableBlobAuditingPolicy().parse(valid_payload) assert params["sql_server_name"] == "sql_server_name" @@ -46,113 +55,181 @@ def test_parse_payload(self, valid_payload): assert params["subscription_id"] == "subscription_id" assert params["region"] == "region" - def test_remediate_success_without_server_identity(self): + def test_remediate_without_stg_without_keyvault(self): client = Mock() - client_storage = Mock() client_authorization = Mock() - client.servers.get.return_value = Server( - id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", - name="remserver5", - type="Microsoft.Sql/servers", - location="eastus", - identity=None, - administrator_login="accelerators", - administrator_login_password=None, - state="Ready", - fully_qualified_domain_name="remserver5.database.windows.net", - public_network_access="Enabled", - ) - resource_identity = ResourceIdentity( + client_storage = Mock() + keyvault_client = Mock() + monitor_client = Mock() + graph_client = Mock() + credentials = Mock() + client_id = Mock() + tenant_id = Mock() + + action = SqlServerEnableBlobAuditingPolicy() + action.create_key = Mock() + action.create_key_vault = Mock() + action.check_key_vault = Mock() + action.check_stg_account = Mock() + action.update_storage_account_encryption = Mock() + action.create_diagnostic_setting = Mock() + action.create_storage_account = Mock() + action.ensure_identity_assigned = Mock() + action.create_role_assignment = Mock() + action.create_server_blob_auditing_policy = Mock() + + identity = Identity( principal_id="139bcf82-e14e-4773-bcf4-1da136674792", type="SystemAssigned", tenant_id="b39138ca-3cee-4b4a-a4d6-cd83d9dd62f0", ) - client.servers.update.result.return_value = Server( + action.ensure_identity_assigned.return_value = ( + "139bcf82-e14e-4773-bcf4-1da136674792" + ) + action.create_key_vault.return_value = Vault( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", + name="stg-keyvault-rem", + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard"), + vault_uri="https://stg-keyvault-rem.vault.azure.net", + ), + ) + action.create_key.return_value = KeyVaultKey( + key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", + name="rem-key1", + ) + action.create_storage_account.return_value = StorageAccount( id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", - name="remserver5", - type="Microsoft.Sql/servers", + name="remstg5", + type="Microsoft.Storage/storageAccounts", location="eastus", - identity=resource_identity, - administrator_login="accelerators", - administrator_login_password=None, - state="Ready", - fully_qualified_domain_name="remserver5.database.windows.net", - public_network_access="Enabled", + identity=identity, ) - - action = SqlServerEnableBlobAuditingPolicy() + action.check_stg_account.return_value = None + action.check_key_vault.return_value = None assert ( action.remediate( + client_id, + tenant_id, + credentials, client, client_storage, + keyvault_client, + graph_client, + monitor_client, client_authorization, - "resource_group", + "resource_group_name", "sql_server_name", - "subscription_id", "region", + "subscription_id", ) == 0 ) - assert client.server_blob_auditing_policies.create_or_update.call_count == 1 - call_args = client.server_blob_auditing_policies.create_or_update.call_args - updated_auditing_policy = call_args[1]["parameters"] - assert updated_auditing_policy.state == BlobAuditingPolicyState.enabled + assert action.ensure_identity_assigned.call_count == 1 + assert action.check_key_vault.call_count == 1 + assert action.check_stg_account.call_count == 1 + assert action.create_key.call_count == 1 + assert action.update_storage_account_encryption.call_count == 1 + assert action.create_storage_account.call_count == 1 + assert action.create_key_vault.call_count == 1 + assert action.create_diagnostic_setting.call_count == 1 + assert action.create_role_assignment.call_count == 1 + assert action.create_server_blob_auditing_policy.call_count == 1 - def test_remediate_success_with_server_identity(self): + def test_remediate_without_stg_with_keyvault(self): client = Mock() - client_storage = Mock() client_authorization = Mock() - resource_identity = ResourceIdentity( + client_storage = Mock() + keyvault_client = Mock() + monitor_client = Mock() + graph_client = Mock() + credentials = Mock() + client_id = Mock() + tenant_id = Mock() + + action = SqlServerEnableBlobAuditingPolicy() + action.create_key = Mock() + action.check_key_vault = Mock() + action.check_stg_account = Mock() + action.update_key_vault_access_policy = Mock() + action.update_storage_account_encryption = Mock() + action.create_diagnostic_setting = Mock() + action.create_storage_account = Mock() + action.ensure_identity_assigned = Mock() + action.create_role_assignment = Mock() + action.create_server_blob_auditing_policy = Mock() + + identity = Identity( principal_id="139bcf82-e14e-4773-bcf4-1da136674792", type="SystemAssigned", tenant_id="b39138ca-3cee-4b4a-a4d6-cd83d9dd62f0", ) - client.servers.get.return_value = Server( + action.ensure_identity_assigned.return_value = ( + "139bcf82-e14e-4773-bcf4-1da136674792" + ) + action.create_key.return_value = KeyVaultKey( + key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", + name="rem-key1", + ) + action.create_storage_account.return_value = StorageAccount( id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/accelerators-team-resources/providers/Microsoft.Sql/servers/remserver5", - name="remserver5", - type="Microsoft.Sql/servers", + name="remstg5", + type="Microsoft.Storage/storageAccounts", location="eastus", - identity=resource_identity, - administrator_login="accelerators", - administrator_login_password=None, - state="Ready", - fully_qualified_domain_name="remserver5.database.windows.net", - public_network_access="Enabled", + identity=identity, + ) + action.check_stg_account.return_value = None + action.check_key_vault.return_value = Vault( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", + name="stg-keyvault-rem", + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard"), + vault_uri="https://stg-keyvault-rem.vault.azure.net", + ), ) - action = SqlServerEnableBlobAuditingPolicy() assert ( action.remediate( + client_id, + tenant_id, + credentials, client, client_storage, + keyvault_client, + graph_client, + monitor_client, client_authorization, - "resource_group", + "resource_group_name", "sql_server_name", - "subscription_id", "region", + "subscription_id", ) == 0 ) - assert client.server_blob_auditing_policies.create_or_update.call_count == 1 - call_args = client.server_blob_auditing_policies.create_or_update.call_args - updated_auditing_policy = call_args[1]["parameters"] - assert updated_auditing_policy.state == BlobAuditingPolicyState.enabled + assert action.ensure_identity_assigned.call_count == 1 + assert action.check_key_vault.call_count == 1 + assert action.check_stg_account.call_count == 1 + assert action.create_key.call_count == 1 + assert action.update_key_vault_access_policy.call_count == 1 + assert action.update_storage_account_encryption.call_count == 1 + assert action.create_storage_account.call_count == 1 + assert action.create_role_assignment.call_count == 1 + assert action.create_server_blob_auditing_policy.call_count == 1 def test_remediate_with_exception(self): client = Mock() - client_storage = Mock() client_authorization = Mock() + client_storage = Mock() + client_id = Mock() client.server_blob_auditing_policies.create_or_update.side_effect = Exception action = SqlServerEnableBlobAuditingPolicy() with pytest.raises(Exception): - assert ( - action.remediate( + assert action.remediate( + client_id, client, client_storage, client_authorization, - "resource_group", - "security_group", - "subscription_id", - "region", + "resource_group_name", + "sql_server_name", ) - == 0 - ) From cb578109d6ecdae7505433890c78f0bd4c9f95ae Mon Sep 17 00:00:00 2001 From: kshrutik <73834811+kshrutik@users.noreply.github.com> Date: Fri, 12 Feb 2021 16:26:02 +0530 Subject: [PATCH 33/34] PLA-22903 - Updated Remediation Job for Key Vault Logging (#46) * PLA-22903 - Updated Remediation Job for Key Vault Logging * PLA-22903 - Updated Key Vault Logging job * PLA-22903 - Added Comments * PLA-22903 - Fixed review comments * PLA-22903 - Fixed Review Comments * PLA-22903 - Updated the key vault logging job --- .../README.md | 23 +- ..._key_vault_logging_for_keyvault_enabled.py | 585 ++++++++++++++++-- .../constraints.txt | 6 + .../minimum_permissions.json | 7 +- .../requirements-dev.txt | 3 + .../requirements.txt | 6 + .../azure_sql_data_encryption_on/README.md | 15 +- .../README.md | 15 +- ..._key_vault_logging_for_keyvault_enabled.py | 171 ++++- 9 files changed, 748 insertions(+), 83 deletions(-) diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md index 0232689..499ed41 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/README.md @@ -1,6 +1,7 @@ # Enable Logging For Keyvault -This job enables Key Vault Logging. +This job enables Logging for Key Vault. It checks for the existence of Storage Account created by CHSS in the given resource group and region, if the Storage Account exists then it stores the Key Vault Logs in it. Else it creates a new Storage Account to store the Key Vault logs. +The Storage Account created by CHSS is prefixed with "chss" and contains tag `{"Created By" : "CHSS"}`. ### Applicable Rule @@ -15,9 +16,14 @@ Logging For Keyvault Enabled The provided Azure service principal must have the following permissions: `Microsoft.Storage/storageAccounts/read` `Microsoft.Storage/storageAccounts/write` +`Microsoft.Storage/storageAccounts/blobServices/write` +`Microsoft.Storage/storageAccounts/blobServices/read` `Microsoft.Insights/DiagnosticSettings/Write` `Microsoft.KeyVault/vaults/read` `Microsoft.KeyVault/vaults/write` +`Microsoft.KeyVault/vaults/keys/read` +`Microsoft.KeyVault/vaults/keys/write` +`Microsoft.KeyVault/vaults/accessPolicies/write` A sample role with requisite permissions can be found [here](minimum_permissions.json) @@ -38,13 +44,16 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` ## Deployment -Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. -Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. -Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: +Provision an instance by creating an Azure Virtual Machine to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker on newly provisioned Azure Virtual Machine instance.You can refer to the [docs here](https://docs.microsoft.com/en-us/previous-versions/azure/virtual-machines/linux/docker-machine) for more information. +Deploy the worker docker image by SSH into the Azure Virtual Machine instance and run the following commands: ```shell script - docker run --rm -it --name worker \ - -e VSS_CLIENT_ID={ENTER CLIENT ID} - -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + docker run --rm -it --name {worker_name}\ + -e VSS_CLIENT_ID={ENTER CLIENT ID}\ + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET}\ + -e AZURE_CLIENT_ID={ENTER AZURE_CLIENT_ID} \ + -e AZURE_CLIENT_SECRET={ENTER AZURE_CLIENT_SECRET} \ + -e AZURE_TENANT_ID={ENTER AZURE_TENANT_ID} \ vmware/vss-remediation-worker:latest-python ``` ## Contributing diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py index cbd0d87..0dd72bd 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/azure_key_vault_logging_for_keyvault_enabled.py @@ -16,52 +16,79 @@ import os import sys import logging +import datetime +from dateutil import parser as date_parse +from typing import List +from azure.core.paging import ItemPaged +from azure.keyvault.keys import KeyClient +from azure.mgmt.monitor import MonitorClient from azure.identity import ClientSecretCredential from azure.mgmt.keyvault import KeyVaultManagementClient -from azure.mgmt.monitor import MonitorClient from azure.mgmt.storage import StorageManagementClient +from azure.graphrbac import GraphRbacManagementClient +from azure.common.credentials import ServicePrincipalCredentials +from azure.mgmt.storage.models import Sku as sku_storage from azure.mgmt.storage.models import ( StorageAccountCreateParameters, NetworkRuleSet, - Sku, SkuName, SkuTier, DefaultAction, + Identity, + StorageAccountListResult, + BlobServiceProperties, + DeleteRetentionPolicy, + Encryption, + KeySource, + KeyVaultProperties, + StorageAccountUpdateParameters, ) from azure.mgmt.monitor.models import ( DiagnosticSettingsResource, LogSettings, RetentionPolicy, ) +from azure.mgmt.keyvault.models import ( + VaultCreateOrUpdateParameters, + VaultProperties, + Sku, + AccessPolicyEntry, + Permissions, + KeyPermissions, + VaultListResult, + AccessPolicyUpdateKind, + VaultAccessPolicyParameters, + VaultAccessPolicyProperties, +) logging.basicConfig(level=logging.INFO) - -def generate_name(prefix): - prefix = "".join(i for i in prefix if i.islower() or i.isdigit()) - if len(prefix) >= 12: - prefix = str(prefix[:11]) - result_str = prefix + "keyvaultlogs" - return result_str +MAX_COUNT_SUBSCRIPTION = 5 +MAX_COUNT_RESOURCE_NAME = 5 +MAX_COUNT_REGION = 6 +MAX_COUNT_COMPONENT = 4 -def create_storage_account( - resource_group_name, name, region, storage_client, -): - create_params = StorageAccountCreateParameters( - location=region, - sku=Sku(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), - kind="StorageV2", - enable_https_traffic_only=True, - network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), - ) - poller = storage_client.storage_accounts.begin_create( - resource_group_name=resource_group_name, - account_name=name, - parameters=create_params, - ) - return poller.result() +def generate_name(region, subscription_id, resource_group_name): + """Generates a name for the resource + :param region: location in which the resource exists + :param subscription_id: Azure Subscription Id + :param resource_group_name: Resource group name in which the resource exists + :type region: str + :type subscription_id: str + :type resource_group_name: str + :returns: resource name + :rtype: str + """ + random_str = "".join(i for i in subscription_id if i.islower() or i.isdigit()) + subscription_id = random_str[:MAX_COUNT_SUBSCRIPTION] + random_str = "".join(i for i in region if i.islower() or i.isdigit()) + region = random_str[-MAX_COUNT_REGION:] + random_str = "".join(i for i in resource_group_name if i.islower() or i.isdigit()) + resource_group_name = random_str[-MAX_COUNT_RESOURCE_NAME:] + result_str = "chss" + subscription_id + resource_group_name + region + "logs" + return result_str class EnableKeyVaultLogging(object): @@ -106,65 +133,494 @@ def parse(self, payload): "region": region, } + def create_storage_account( + self, resource_group_name, name, region, storage_client, + ): + """Creates a Storage Account + :param storage_client: Instance of the Azure StorageManagementClient. + :param region: The location in which the storage account exists. + :param name: The Storage Account name. + :param resource_group_name: The name of the resource group. + :type storage_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: StorageAccount object + :rtype: object + """ + + create_params = StorageAccountCreateParameters( + location=region, + sku=sku_storage(name=SkuName.STANDARD_LRS, tier=SkuTier.STANDARD), + identity=Identity(type="SystemAssigned"), + kind="StorageV2", + enable_https_traffic_only=True, + network_rule_set=NetworkRuleSet(default_action=DefaultAction.DENY), + tags={"Created By": "CHSS"}, + ) + stg_account = storage_client.storage_accounts.begin_create( + resource_group_name=resource_group_name, + account_name=name, + parameters=create_params, + ).result() + + storage_client.blob_services.set_service_properties( + resource_group_name=resource_group_name, + account_name=name, + parameters=BlobServiceProperties( + delete_retention_policy=DeleteRetentionPolicy(enabled=True, days=7) + ), + ) + return stg_account + + def update_storage_account_encryption( + self, storage_client, resource_group_name, stg_name, key_name, vault_uri + ): + """Updates Storage Account Encryption for a Storage Account. + :param storage_client: Instance of the Azure StorageManagementClient. + :param resource_group_name: The name of the resource group. + :param stg_name: The Storage Account name. + :param key_name: Name of the Key to encrypt the Storage Account with. + :param vault_uri: Key Vault uri in which the Key exists. + :type storage_client: object + :type resource_group_name: str + :type stg_name: str + :type key_name: str + :type vault_uri: str + :returns: None + :rtype: None + """ + logging.info(" Encrypting Storage Account with Customer Managed Key") + logging.info(" executing storage_client.storage_accounts.update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={stg_name}") + logging.info(f" key_vault_uri={vault_uri}") + logging.info(f" key_name={key_name}") + storage_client.storage_accounts.update( + resource_group_name=resource_group_name, + account_name=stg_name, + parameters=StorageAccountUpdateParameters( + encryption=Encryption( + key_source=KeySource.MICROSOFT_KEYVAULT, + key_vault_properties=KeyVaultProperties( + key_name=key_name, key_vault_uri=vault_uri, + ), + ), + ), + ) + + def check_stg_account(self, storage_client, region, name, resource_group_name): + """Checks For the existence of the Storage Account created by CHSS + :param storage_client: Instance of the Azure StorageManagementClient. + :param region: The location in which the storage account exists. + :param name: The Storage Account name. + :param resource_group_name: The name of the resource group. + :type storage_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: StorageAccount object + :rtype: object + """ + storage_accounts_paged: ItemPaged[ + StorageAccountListResult + ] = storage_client.storage_accounts.list() + storage_accounts_list: List[dict] = list(storage_accounts_paged) + storage_account = None + for stg_account in storage_accounts_list: + stg_id = stg_account.id + stg_components = stg_id.split("/") + if len(stg_components) > MAX_COUNT_COMPONENT: + resource_grp = stg_components[MAX_COUNT_COMPONENT] + if ( + stg_account.name == name + and stg_account.location == region + and resource_grp == resource_group_name + ): + storage_account = stg_account + break + return storage_account + + def check_key_vault(self, keyvault_client, region, name, resource_group_name): + """Checks for the existence of the Key Vault created by CHSS. + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param region: The location in which the Key Vault exists. + :param name: Key Vault name. + :param resource_group_name: The name of the resource group. + :type keyvault_client: object + :type region: str + :type name: str + :type resource_group_name: str + :returns: Vault object + :rtype: object + """ + key_vault_paged: ItemPaged[ + VaultListResult + ] = keyvault_client.vaults.list_by_subscription() + key_vault_list: List[dict] = list(key_vault_paged) + chss_key_vault = None + for key_vault in key_vault_list: + key_vault_id = key_vault.id + key_vault_components = key_vault_id.split("/") + if len(key_vault_components) > MAX_COUNT_COMPONENT: + resource_grp = key_vault_components[MAX_COUNT_COMPONENT] + if ( + key_vault.name == name + and key_vault.location == region + and resource_grp == resource_group_name + ): + chss_key_vault = key_vault + break + return chss_key_vault + + def create_diagnostic_setting( + self, monitor_client, key_vault_id, key_vault_name, stg_account_id, log + ): + """Creates a diagnostic setting + :param monitor_client: Instance of the Azure StorageManagementClient. + :param key_vault_id: The resource Id of the Key Vault. + :param key_vault_name: Name of the Key Vault. + :param stg_account_id: The Storage Account resource Id. + :param log: Instance of Azure Monitor LogSettings + :type monitor_client: object + :type log: object + :type key_vault_id: str + :type key_vault_name: str + :type stg_account_id: str + :returns: None + :rtype: None + """ + logging.info(" Creating a Diagnostic setting for key vault logs") + logging.info( + " executing monitor_client.diagnostic_settings.create_or_update" + ) + logging.info(f" resource_uri={key_vault_id}") + logging.info(f" name={key_vault_name}") + monitor_client.diagnostic_settings.create_or_update( + resource_uri=key_vault_id, + name=key_vault_name, + parameters=DiagnosticSettingsResource( + storage_account_id=stg_account_id, logs=[log], + ), + ) + + def create_key_vault( + self, + keyvault_client, + resource_group_name, + key_vault_name, + region, + tenant_id, + app_object_id, + stg_principal_id, + ): + """Creates a Key Vault + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param resource_group_name: The name of the resource group. + :param key_vault_name: Name of the Key Vault. + :param region: location of the Key Vault. + :param tenant_id: Azure tenant Id + :param app_object_id: Object Id of the application + :param stg_principal_id: Principal Id of the Storage Account + :type keyvault_client: object + :type resource_group_name: str + :type key_vault_name: str + :type region: str + :type tenant_id: str + :type app_object_id: str + :type stg_principal_id: str + :returns: Vault object + :rtype: object + """ + access_policy_storage_account = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=stg_principal_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.UNWRAP_KEY, + KeyPermissions.WRAP_KEY, + ], + ), + ) + access_policy_app = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=app_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.LIST, + KeyPermissions.CREATE, + KeyPermissions.UPDATE, + KeyPermissions.DELETE, + KeyPermissions.BACKUP, + KeyPermissions.RESTORE, + KeyPermissions.RECOVER, + ], + ), + ) + key_vault_properties = VaultCreateOrUpdateParameters( + location=region, + tags={"Created By": "CHSS"}, + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard",), + access_policies=[access_policy_storage_account, access_policy_app], + soft_delete_retention_in_days=7, + enabled_for_disk_encryption=False, + enabled_for_deployment=False, + enabled_for_template_deployment=False, + enable_soft_delete=True, + enable_purge_protection=True, + ), + ) + logging.info("creating a key vault") + logging.info("executing keyvault_client.vaults.begin_create_or_update") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" vault_name={key_vault_name}") + vault = keyvault_client.vaults.begin_create_or_update( + resource_group_name=resource_group_name, + vault_name=key_vault_name, + parameters=key_vault_properties, + ).result() + return vault + + def update_key_vault_access_policy( + self, + keyvault_client, + resource_group_name, + key_vault_name, + tenant_id, + app_object_id, + stg_object_id, + ): + """Updates Key Vault Access Policy + :param keyvault_client: Instance of the Azure KeyVaultManagementClient. + :param resource_group_name: The name of the resource group. + :param key_vault_name: Name of the Key Vault. + :param region: The location in which the Key Vault exists. + :param tenant_id: Azure tenant Id + :param app_object_id: Object Id of the application + :param stg_principal_id: Principal Id of the Storage Account + :type keyvault_client: object + :type resource_group_name: str + :type key_vault_name: str + :type region: str + :type tenant_id: str + :type app_object_id: str + :type stg_principal_id: str + :returns: None + :rtype: None + """ + access_policy_storage = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=stg_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.UNWRAP_KEY, + KeyPermissions.WRAP_KEY, + ], + ), + ) + access_policy_app = AccessPolicyEntry( + tenant_id=tenant_id, + object_id=app_object_id, + permissions=Permissions( + keys=[ + KeyPermissions.GET, + KeyPermissions.LIST, + KeyPermissions.CREATE, + KeyPermissions.UPDATE, + KeyPermissions.DELETE, + KeyPermissions.BACKUP, + KeyPermissions.RESTORE, + KeyPermissions.RECOVER, + ], + ), + ) + access_policy = [access_policy_app, access_policy_storage] + + logging.info("Updating Key Vault Access Policy") + logging.info("executing keyvault_client.vaults.update_access_policy") + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" vault_name={key_vault_name}") + keyvault_client.vaults.update_access_policy( + resource_group_name=resource_group_name, + vault_name=key_vault_name, + operation_kind=AccessPolicyUpdateKind.ADD, + parameters=VaultAccessPolicyParameters( + properties=VaultAccessPolicyProperties(access_policies=access_policy), + ), + ) + + def create_key(self, credential, key_vault_name, suffix): + """Creates a Key within the given Key Vault + :param credential: Azure Credentials + :param key_vault_name: Name of the Key Vault. + :param suffix: suffix for Key name + :type key_vault_name: str + :type suffix: str + :returns: Azure Key object which was created + :rtype: object + """ + d = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).isoformat() + date = datetime.datetime.strptime( + d[0:19], "%Y-%m-%dT%H:%M:%S" + ) + datetime.timedelta(days=180) + expires_on = date_parse.parse( + date.replace(microsecond=0, tzinfo=datetime.timezone.utc).isoformat() + ) + key_client = KeyClient( + vault_url=f"https://{key_vault_name}.vault.azure.net/", + credential=credential, + ) + rsa_key_name = key_vault_name + "-" + suffix + logging.info("creating a key") + rsa_key = key_client.create_rsa_key( + rsa_key_name, size=2048, expires_on=expires_on, enabled=True + ) + return rsa_key + def remediate( self, + client_id, + tenant_id, keyvault_client, monitor_client, storage_client, + graph_client, + credentials, resource_group_name, key_vault_name, region, + subscription_id, ): """Enable key vault logging + :param client_id: Azure Client ID. + :param tenant_id: Azure Tenant ID. + :param graph_client: Instance of the AzureGraphRbacManagementClient. :param keyvault_client: Instance of the Azure KeyVaultManagementClient. :param storage_client: Instance of the Azure StorageManagementClient. :param monitor_client: Instance of the Azure MonitorClient. + :param credentials: Azure Credential object. :param resource_group_name: The name of the resource group to which the storage account belongs. :param key_vault_name: The name of the key vault. :param region: The region in which the key vault is present. + :param subscription_id: Azure Subscription Id + :type client_id: str + :type tenant_id: str + :type graph_client: object + :type keyvault_client: object + :type storage_client: object + :type monitor_client: object + :type credentials: object :type resource_group_name: str. :type key_vault_name: str. :type region: str. + :type subscription_id: str :returns: Integer signaling success or failure :rtype: int :raises: msrestazure.azure_exceptions.CloudError """ - key_vault = keyvault_client.vaults.get( - resource_group_name=resource_group_name, vault_name=key_vault_name, - ) try: - stg_name = generate_name(key_vault_name) - logging.info(" Creating a Storage Account") - logging.info(" executing client_storage.storage_accounts.begin_create") - logging.info(f" resource_group_name={resource_group_name}") - logging.info(f" account_name={stg_name}") - stg_account = create_storage_account( - resource_group_name, stg_name, region, storage_client + key_vault = keyvault_client.vaults.get( + resource_group_name=resource_group_name, vault_name=key_vault_name, ) log = LogSettings( category="AuditEvent", enabled=True, retention_policy=RetentionPolicy(enabled=True, days=180), ) - - logging.info(" Creating a Diagnostic setting for key vault logs") - logging.info( - " executing monitor_client.diagnostic_settings.create_or_update" + app_details = graph_client.applications.get_service_principals_id_by_app_id( + application_id=client_id ) - logging.info(f" resource_uri={key_vault.id}") - logging.info(f" name={key_vault_name}") - - monitor_client.diagnostic_settings.create_or_update( - resource_uri=key_vault.id, - name=key_vault_name, - parameters=DiagnosticSettingsResource( - storage_account_id=stg_account.id, logs=[log], - ), + # Check if the Storage Account Created by CHSS is available in the same region and resource group + stg_name = generate_name(region, subscription_id, resource_group_name) + stg_account = self.check_stg_account( + storage_client, region, stg_name, resource_group_name + ) + if stg_account is None: + # If the Storage Account does not exists, create a storage account to store logs + logging.info(" Creating a Storage Account") + logging.info( + " executing client_storage.storage_accounts.begin_create" + ) + logging.info(f" resource_group_name={resource_group_name}") + logging.info(f" account_name={stg_name}") + stg_account = self.create_storage_account( + resource_group_name, stg_name, region, storage_client + ) + # Check if the Key Vault created by CHSS exists in the given region and resource group + encryption_key_vault_name = generate_name( + region, subscription_id, resource_group_name + ) + encryption_key_vault = self.check_key_vault( + keyvault_client, + region, + encryption_key_vault_name, + resource_group_name, + ) + if encryption_key_vault is None: + # If the Key Vault does not exists, create Key Vault + encryption_key_vault = self.create_key_vault( + keyvault_client, + resource_group_name, + encryption_key_vault_name, + region, + tenant_id, + app_details.value, + stg_account.identity.principal_id, + ) + # Create a key to encrypt the Storage Account + key = self.create_key( + credentials, encryption_key_vault_name, stg_account.name + ) + # Encrypt the Storage Account using the above Key + self.update_storage_account_encryption( + storage_client, + resource_group_name, + stg_name, + key.name, + encryption_key_vault.properties.vault_uri, + ) + # Create a diagnostic setting to store Key Vault Logs + self.create_diagnostic_setting( + monitor_client, + encryption_key_vault.id, + encryption_key_vault.name, + stg_account.id, + log, + ) + else: + # Update the key vault access policy to give permissions for both app and storage account. + self.update_key_vault_access_policy( + keyvault_client, + resource_group_name, + encryption_key_vault.name, + tenant_id, + app_details.value, + stg_account.identity.principal_id, + ) + # Create a key to encrypt the Storage Account + key = self.create_key( + credentials, encryption_key_vault.name, stg_account.name + ) + # Encrypt the Storage Account using the above Key + self.update_storage_account_encryption( + storage_client, + resource_group_name, + stg_account.name, + key.name, + encryption_key_vault.properties.vault_uri, + ) + # Creating Diagnostic settings to store violated Key vault's logs + self.create_diagnostic_setting( + monitor_client, key_vault.id, key_vault_name, stg_account.id, log ) except Exception as e: logging.error(f"{str(e)}") raise - return 0 def run(self, args): @@ -174,26 +630,41 @@ def run(self, args): :returns: int """ params = self.parse(args[1]) + client_id = os.environ.get("AZURE_CLIENT_ID") + client_secret = os.environ.get("AZURE_CLIENT_SECRET") + tenant_id = os.environ.get("AZURE_TENANT_ID") - credentials = ClientSecretCredential( - client_id=os.environ.get("AZURE_CLIENT_ID"), - client_secret=os.environ.get("AZURE_CLIENT_SECRET"), - tenant_id=os.environ.get("AZURE_TENANT_ID"), + # credential for Storage Account and Key Vault management client + credential = ClientSecretCredential( + client_id=client_id, client_secret=client_secret, tenant_id=tenant_id, ) - storage_client = StorageManagementClient( - credentials, params["subscription_id"] + + # credential for AzureGraphRbacManagementClient + credentials = ServicePrincipalCredentials( + client_id=client_id, + secret=client_secret, + tenant=tenant_id, + resource="https://graph.windows.net", ) + + storage_client = StorageManagementClient(credential, params["subscription_id"]) keyvault_client = KeyVaultManagementClient( - credentials, params["subscription_id"] + credential, params["subscription_id"] ) - monitor_client = MonitorClient(credentials, params["subscription_id"]) + graph_client = GraphRbacManagementClient(credentials, tenant_id, base_url=None) + monitor_client = MonitorClient(credential, params["subscription_id"]) return self.remediate( + client_id, + tenant_id, keyvault_client, monitor_client, storage_client, + graph_client, + credential, params["resource_group_name"], params["key_vault_name"], params["region"], + params["subscription_id"], ) diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt index b184eeb..0a3f16a 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/constraints.txt @@ -25,6 +25,12 @@ azure-mgmt-monitor==1.0.1 \ azure-mgmt-keyvault==8.0.0 \ --hash=sha256:2c974c6114d8d27152642c82a975812790a5e86ccf609bf370a476d9ea0d2e7d \ --hash=sha256:99da24e9455f195d1e45a605af04d663e15cb5d5eb005b0ef60638474bbb2b3f +azure-graphrbac==0.61.1 \ + --hash=sha256:53e98ae2ca7c19b349e9e9bb1b6a824aeae8dcfcbe17190d20fe69c0f185b2e2 \ + --hash=sha256:7b4e0f05676acc912f2b33c71c328d9fb2e4dc8e70ebadc9d3de8ab08bf0b175 +azure-keyvault-keys==4.3.1 \ + --hash=sha256:25cf889bbcafd8dee0e068fd48d84e24e11143bf85e35c68d997a222e4a0f7fb \ + --hash=sha256:fbf67bca913ebf68b9075ee9d2e2b899dc3c7892cc40abfe1b08220a382f6ed9 certifi==2020.6.20 \ --hash=sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3 \ --hash=sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41 diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json index 16a9bb0..409cffd 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/minimum_permissions.json @@ -9,9 +9,14 @@ "actions": [ "Microsoft.Storage/storageAccounts/read", "Microsoft.Storage/storageAccounts/write", + "Microsoft.Storage/storageAccounts/blobServices/write", + "Microsoft.Storage/storageAccounts/blobServices/read", "Microsoft.Insights/DiagnosticSettings/Write", "Microsoft.KeyVault/vaults/read", - "Microsoft.KeyVault/vaults/write" + "Microsoft.KeyVault/vaults/write", + "Microsoft.KeyVault/vaults/keys/read", + "Microsoft.KeyVault/vaults/keys/write", + "Microsoft.KeyVault/vaults/accessPolicies/write" ], "notActions": [], "dataActions": [], diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt index 759f2ca..4ee3a9a 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements-dev.txt @@ -1,6 +1,9 @@ -r requirements.txt -c constraints.txt +python-dateutil==2.8.1 \ + --hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \ + --hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a attrs==20.1.0 \ --hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \ --hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff diff --git a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt index fbfcae4..2617d96 100644 --- a/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt +++ b/remediation_worker/jobs/azure_key_vault_logging_for_keyvault_enabled/requirements.txt @@ -10,3 +10,9 @@ azure-mgmt-monitor==1.0.1 \ azure-mgmt-keyvault==8.0.0 \ --hash=sha256:2c974c6114d8d27152642c82a975812790a5e86ccf609bf370a476d9ea0d2e7d \ --hash=sha256:99da24e9455f195d1e45a605af04d663e15cb5d5eb005b0ef60638474bbb2b3f +azure-graphrbac==0.61.1 \ + --hash=sha256:53e98ae2ca7c19b349e9e9bb1b6a824aeae8dcfcbe17190d20fe69c0f185b2e2 \ + --hash=sha256:7b4e0f05676acc912f2b33c71c328d9fb2e4dc8e70ebadc9d3de8ab08bf0b175 +azure-keyvault-keys==4.3.1 \ + --hash=sha256:25cf889bbcafd8dee0e068fd48d84e24e11143bf85e35c68d997a222e4a0f7fb \ + --hash=sha256:fbf67bca913ebf68b9075ee9d2e2b899dc3c7892cc40abfe1b08220a382f6ed9 diff --git a/remediation_worker/jobs/azure_sql_data_encryption_on/README.md b/remediation_worker/jobs/azure_sql_data_encryption_on/README.md index cf41263..06fd7b1 100644 --- a/remediation_worker/jobs/azure_sql_data_encryption_on/README.md +++ b/remediation_worker/jobs/azure_sql_data_encryption_on/README.md @@ -35,13 +35,16 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` ## Deployment -Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. -Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. -Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: +Provision a Virtual Machine Create an Azure Virtual Machine instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned Azure Virtual Machine instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the Azure Virtual Machine instance and run the command below to deploy the worker image: ```shell script - docker run --rm -it --name worker \ - -e VSS_CLIENT_ID={ENTER CLIENT ID} - -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + docker run --rm -it --name {worker_name}\ + -e VSS_CLIENT_ID={ENTER CLIENT ID}\ + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET}\ + -e AZURE_CLIENT_ID={ENTER AZURE_CLIENT_ID} \ + -e AZURE_CLIENT_SECRET={ENTER AZURE_CLIENT_SECRET} \ + -e AZURE_TENANT_ID={ENTER AZURE_TENANT_ID} \ vmware/vss-remediation-worker:latest-python ``` ## Contributing diff --git a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md index 7e84da9..f7f92d2 100644 --- a/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md +++ b/remediation_worker/jobs/azure_storage_soft_delete_not_enabled/README.md @@ -35,13 +35,16 @@ You may run test using following command under vss-remediation-worker-job-code-p python3 -m pytest test ``` ## Deployment -Provision a Virtual Machine Create an EC2 instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. -Setup Docker Install Docker on the newly provisioned EC2 instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. -Deploy the worker image SSH into the EC2 instance and run the command below to deploy the worker image: +Provision a Virtual Machine Create an Azure Virtual Machine instance to use for the worker. The minimum required specifications are 128 MB memory and 1/2 Core CPU. +Setup Docker Install Docker on the newly provisioned Azure Virtual Machine instance. You can refer to the [docs here](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html) for more information. +Deploy the worker image SSH into the Azure Virtual Machine instance and run the command below to deploy the worker image: ```shell script - docker run --rm -it --name worker \ - -e VSS_CLIENT_ID={ENTER CLIENT ID} - -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET} \ + docker run --rm -it --name {worker_name}\ + -e VSS_CLIENT_ID={ENTER CLIENT ID}\ + -e VSS_CLIENT_SECRET={ENTER CLIENT SECRET}\ + -e AZURE_CLIENT_ID={ENTER AZURE_CLIENT_ID} \ + -e AZURE_CLIENT_SECRET={ENTER AZURE_CLIENT_SECRET} \ + -e AZURE_TENANT_ID={ENTER AZURE_TENANT_ID} \ vmware/vss-remediation-worker:latest-python ``` ## Contributing diff --git a/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py b/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py index 73901ef..7008b05 100644 --- a/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py +++ b/test/unit/test_azure_key_vault_logging_for_keyvault_enabled.py @@ -17,6 +17,18 @@ from remediation_worker.jobs.azure_key_vault_logging_for_keyvault_enabled.azure_key_vault_logging_for_keyvault_enabled import ( EnableKeyVaultLogging, ) +from azure.mgmt.storage.models import StorageAccount +from azure.mgmt.keyvault.models import ( + VaultProperties, + Vault, + Sku, +) +from azure.keyvault.keys import KeyVaultKey +from azure.mgmt.monitor.models import ( + LogSettings, + RetentionPolicy, + DiagnosticSettingsResource, +) @pytest.fixture @@ -45,28 +57,175 @@ def test_parse_payload(self, valid_payload): assert params["subscription_id"] == "subscription_id" assert params["region"] == "region" - def test_remediate_success(self): + def test_remediate_success_with_stg(self): + client_id = Mock() + tenant_id = Mock() + storage_client = Mock() + keyvault_client = Mock() + monitor_client = Mock() + graph_client = Mock() + credentials = Mock() + log = LogSettings( + category="AuditEvent", + enabled=True, + retention_policy=RetentionPolicy(enabled=True, days=180), + ) + action = EnableKeyVaultLogging() + action.check_stg_account = Mock() + action.create_diagnostic_setting = Mock() + """ + StorageAccountListResult = Mock() + storage_accounts_list = [] + storage_accounts_list.append(StorageAccountListResult) + action.check_stg_account.return_value = storage_accounts_list + """ + action.check_stg_account.return_value = StorageAccount( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.Storage/storageAccounts/chss538f633keyvaultlogs", + name="chss538f633keyvaultlogs", + location="eastus", + ) + action.create_diagnostic_setting.return_value = DiagnosticSettingsResource( + storage_account_id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.Storage/storageAccounts/chss538f633keyvaultlogs", + logs=[log], + ) + assert ( + action.remediate( + client_id, + tenant_id, + keyvault_client, + monitor_client, + storage_client, + graph_client, + credentials, + "resource_group", + "key_vault_name", + "region", + "subscription_id", + ) + == 0 + ) + assert action.create_diagnostic_setting.call_count == 1 + + def test_remediate_success_without_stg_without_keyvault(self): + client_id = Mock() + tenant_id = Mock() storage_client = Mock() keyvault_client = Mock() monitor_client = Mock() + graph_client = Mock() + credentials = Mock() action = EnableKeyVaultLogging() + action.check_stg_account = Mock() + action.check_key_vault = Mock() + action.create_key = Mock() + action.create_key_vault = Mock() + action.create_diagnostic_setting = Mock() + action.create_storage_account = Mock() + action.create_key_vault.return_value = Vault( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", + name="stg-keyvault-rem", + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard"), + vault_uri="https://stg-keyvault-rem.vault.azure.net", + ), + ) + action.create_key.return_value = KeyVaultKey( + key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", + name="rem-key1", + ) + action.check_stg_account.return_value = None + action.check_key_vault.return_value = None assert ( action.remediate( + client_id, + tenant_id, keyvault_client, monitor_client, storage_client, + graph_client, + credentials, "resource_group", "key_vault_name", "region", + "subscription_id", ) == 0 ) - assert storage_client.storage_accounts.begin_create.call_count == 1 - assert monitor_client.diagnostic_settings.create_or_update.call_count == 1 + assert action.create_diagnostic_setting.call_count == 2 + assert action.create_storage_account.call_count == 1 + assert action.create_key_vault.call_count == 1 + assert action.create_key.call_count == 1 + + def test_remediate_success_without_stg_with_keyvault(self): + client_id = Mock() + tenant_id = Mock() + storage_client = Mock() + keyvault_client = Mock() + monitor_client = Mock() + graph_client = Mock() + credentials = Mock() + action = EnableKeyVaultLogging() + action.check_stg_account = Mock() + action.check_key_vault = Mock() + action.create_key = Mock() + action.create_key_vault = Mock() + action.create_diagnostic_setting = Mock() + action.create_storage_account = Mock() + action.create_key.return_value = KeyVaultKey( + key_id="https://stg-keyvault-rem.vault.azure.net/keys/rem-key1/0d7a89bd1f8447b4b65ce962212476b0", + name="rem-key1", + ) + action.check_stg_account.return_value = None + action.check_key_vault.return_value = Vault( + id="/subscriptions/d687b1a3-9b78-43b1-a17b-7de297fd1fce/resourceGroups/kshrutika-1/providers/Microsoft.KeyVault/vaults/stg-keyvault-rem", + name="stg-keyvault-rem", + properties=VaultProperties( + tenant_id=tenant_id, + sku=Sku(family="A", name="standard"), + vault_uri="https://stg-keyvault-rem.vault.azure.net", + ), + ) + assert ( + action.remediate( + client_id, + tenant_id, + keyvault_client, + monitor_client, + storage_client, + graph_client, + credentials, + "resource_group", + "key_vault_name", + "region", + "subscription_id", + ) + == 0 + ) + assert action.create_diagnostic_setting.call_count == 1 + assert action.create_storage_account.call_count == 1 + assert action.create_key.call_count == 1 def test_remediate_with_exception(self): - client = Mock() - client.storage_accounts.update.side_effect = Exception + client_id = Mock() + tenant_id = Mock() + storage_client = Mock() + keyvault_client = Mock() + monitor_client = Mock() + graph_client = Mock() + credentials = Mock() + monitor_client.diagnostic_settings.create_or_update.side_effect = Exception action = EnableKeyVaultLogging() with pytest.raises(Exception): - assert action.remediate(client, "security_group_id", "resource_group") + assert action.remediate( + client_id, + tenant_id, + keyvault_client, + monitor_client, + storage_client, + graph_client, + credentials, + "resource_group", + "key_vault_name", + "region", + ) From 2d96ec710d9bf755946b7a26c3706702fe09b308 Mon Sep 17 00:00:00 2001 From: Shrutika Kulkarni Date: Mon, 22 Feb 2021 22:47:04 +0530 Subject: [PATCH 34/34] Resolved Conflicts --- tox.ini | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tox.ini b/tox.ini index c0571ab..1606668 100644 --- a/tox.ini +++ b/tox.ini @@ -20,16 +20,12 @@ envlist = unit-azure-storage-allow-only-https unit-azure-storage-default-network-access-deny unit-azure-security-center-enable-ddos-protection -<<<<<<< HEAD - unit-azure-sql-threat-detection-on-server -======= unit-azure-sql-data-encryption-on unit-azure-sql-auditing-on-server unit-azure-sql-threat-detection-on-server unit-azure-key-vault-logging-for-keyvault-enabled unit-azure-storage-soft-delete-not-enabled unit-azure-storage-encryption-at-rest-not-configured-with-customer-managed-key ->>>>>>> dev [testenv] passenv =