diff --git a/.github/workflows/test_profile.yml b/.github/workflows/test_profile.yml index 573b595..abd6f80 100644 --- a/.github/workflows/test_profile.yml +++ b/.github/workflows/test_profile.yml @@ -57,11 +57,50 @@ jobs: echo "$AUTH" | base64 --decode > cert.pfx chmod 0400 cert.pfx + - name: Create .inspec directory + run: mkdir -p .inspec + + - name: Check pwsh path + run: | + if command -v pwsh > /dev/null 2>&1; then + echo "pwsh found at: $(which pwsh)" + else + echo "pwsh not found in PATH" + fi + + - name: Create config json file + run: | + echo '{ + "version": "1.1", + "cli_options": { + "color": "true" + }, + "credentials": { + "pwsh": { + "pwsh-options": { + "client_id": "'"${{secrets.SAF_M365_CLIENT_ID}}"'", + "tenant_id": "'"${{secrets.SAF_M365_TENANT_ID}}"'", + "client_secret": "'"${{secrets.SAF_M365_CLIENT_SECRET}}"'", + "certificate_path": "cert.pfx", + "certificate_password": "'"${{secrets.SAF_M365_CERTIFICATE_PASSWORD}}"'", + "organization": "'"${{secrets.SAF_M365_ORGANIZATION}}"'", + "sharepoint_admin_url": "'"${{secrets.SAF_M365_SHAREPOINT_ADMIN_URL}}"'", + "pwsh_path": "'"${{secrets.SAF_M365_PWSH_PATH}}"'" + } + } + } + }' > ~/.inspec/config.json + + - name: Verify config json file + run: | + # Print the contents of the config file + cat ~/.inspec/config.json + - name: Run Inspec test continue-on-error: true run: | bundle exec inspec exec . \ - --input client_id=${{secrets.SAF_M365_CLIENT_ID}} tenant_id=${{secrets.SAF_M365_TENANT_ID}} client_secret=${{secrets.SAF_M365_CLIENT_SECRET}} certificate_path=cert.pfx certificate_password=${{secrets.SAF_M365_CERTIFICATE_PASSWORD}} organization=${{secrets.SAF_M365_ORGANIZATION}} \ + -t pwsh://pwsh-options \ --input-file=inputs.yml \ --enhanced-outcomes \ --reporter json:${{ env.PLATFORM }}-results.json diff --git a/Gemfile b/Gemfile index b59585a..69f8d7b 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem 'highline' gem 'inspec', '>= 6.8.1' gem 'inspec-bin' gem 'inspec-core' +gem 'inspec-pwsh', git: 'https://github.com/mitre/inspec-pwsh.git' gem 'json' gem 'kitchen-ansible' gem 'kitchen-inspec' @@ -15,4 +16,6 @@ gem 'pry-byebug' gem 'rake' gem 'rubocop' gem 'rubocop-rake' +gem 'ruby-pwsh' gem 'test-kitchen' +gem 'train-pwsh' diff --git a/README.md b/README.md index 6bdd4f2..aaaf2d4 100644 --- a/README.md +++ b/README.md @@ -1,303 +1,376 @@ -## Getting Started -It is intended and recommended that InSpec and this profile be run from a __"runner"__ host (such as a DevOps orchestration server, an administrative management system, or a developer's workstation/laptop) against the target. - -__For the best security of the runner, always install on the runner the _latest version_ of InSpec and supporting Ruby language components.__ - -Latest versions and installation options are available at the [InSpec](http://inspec.io/) site. - -The M365 CIS Benchmark includes security requirements for an Microsoft 365 environment. - -## Getting Started - -### Requirements - -#### Microsoft 365 +# Microsoft 365 Foundation CIS Benchmark +This InSpec Profile was created to facilitate testing and auditing of `CIS Microsoft 365 Benchmark` +infrastructure and applications when validating compliancy with [Center for Internet Security (CIS) Benchmark](https://www.cisecurity.org/cis-benchmarks) +requirements. + +- Profile Version: **3.1.1** +- Benchmark Date: **2024-04-29** +- Benchmark Version: **3.1.0** + + +This profile was developed to reduce the time it takes to perform a security checks based upon the +CIS Guidance from the Center for Internet Security (CIS). + +The CIS Microsoft 365 Foundation CIS CIS Profile uses the [InSpec](https://github.com/inspec/inspec) +open-source compliance validation language to support automation of the required compliance, security +and policy testing for Assessment and Authorization (A&A) and Authority to Operate (ATO) decisions +and Continuous Authority to Operate (cATO) processes. + +The M365 CIS Benchmark includes security requirements for a Microsoft 365 environment. + +Table of Contents +================= +* [CIS Benchmark Information](#benchmark-information) +* [Requirements](#requirements) +* [Getting Started](#getting-started) + * [Intended Usage](#intended-usage) + * [Tailoring to Your Environment](#tailoring-to-your-environment) + * [Testing the Profile Controls](#testing-the-profile-controls) +* [Running the Profile](#running-the-profile) + * [Directly from Github](#directly-from-github) + * [Different Run Options](#different-run-options) +* [Using Heimdall for Viewing Test Results](#using-heimdall-for-viewing-test-results) +* [Check Overview]() + +## Benchmark Information +The Center for Internet Security, Inc. (CIS®) create and maintain a set of Critical Security Controls (CIS Controls) for applications, computer systems and networks. + +The original benchmark document that serves as the basis for this automated testing profile can be found at the [CIS Workbench](https://workbench.cisecurity.org) website. + +[top](#table-of-contents) +## Requirements +### Microsoft 365 - M365 account API credentials and certificate - M365 providing appropriate permissions to perform audit scan - This can be done by creating an application registration within your account, which will provide you with the appropriate credentials to login such as Client ID and Tenant ID. You will need to create a Client Secret/Certificate as well. The following link provides more detail on how to setup an application registration: [Application_Registration_Steps](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app?tabs=certificate) -# Ensure the Following Permissions on your Application Registration Account +### Ensure the Following Permissions on your Application Registration Account - Microsoft Graph - SecurityEvents.Read.All - User.Read + - UserAuthenticationMethod.Read.All + - AuditLog.Read.All, + - Policy.Read.All - Office 365 Exchange Online - Exchange.ManageAsApp - SharePoint - Sites.FullControl.All - -#### Required software on the InSpec Runner + +### Required software and steps needed on the InSpec Runner - git - [Powershell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.4) - [InSpec](https://www.chef.io/products/chef-inspec/) +- [train-pwsh](https://github.com/mitre/train-pwsh) +- [inspec-pwsh](https://github.com/mitre/inspec-pwsh) + +It is important to follow/understand the documentation for train-pwsh and inspec-pwsh that is linked above for this profile to run correctly. For context, the train-pwsh is the transport that is used to maintain a persistent connection with various Powershell sessions. Meanwhile, inspec-pwsh is a resource pack that is used to connect controls using different modules to its corresponding session group (e.g. session for exchange, teams, exchange/graph, etc.). The documentation for inspec-pwsh has more detail about the resource pack. + +Particularly, for train-pwsh, your organization field will also need to be defined as a environment variable named `ORGANIZATION` as it is used in a profile. The train-pwsh documentation has more detail on how to do this. -## PowerShell Module Installation +### PowerShell Module Installation Ensure access and install the following powershell modules. The controls also have the module installation code when running the Powershell queries for redundancy purposes: - [Microsoft.Graph](https://learn.microsoft.com/en-us/powershell/microsoftgraph/installation?view=graph-powershell-1.0#installation) - [ExchangeOnlineManagement](https://learn.microsoft.com/en-us/powershell/exchange/connect-to-exchange-online-powershell?view=exchange-ps) - [PnP.PowerShell](https://learn.microsoft.com/en-us/powershell/sharepoint/sharepoint-pnp/sharepoint-pnp-cmdlets) - [MicrosoftTeams](https://learn.microsoft.com/en-us/microsoftteams/teams-powershell-install) -### Setup Environment on the InSpec Runner -#### Install InSpec -Go to https://www.inspec.io/downloads/ and consult the documentation for your Operating System to download and install InSpec. - -#### Check InSpec version is at least version 6 -```sh -inspec --version +## Getting Started +### InSpec (CINC Auditor) setup +For maximum flexibility/accessibility, CINC Auditor (`cinc-auditor`) is the executable program that should be used to run this testing profile. + +CINC Auditor is the open-source packaged binary version of Chef InSpec, +compiled by the CINC (CINC Is Not Chef) project in coordination with Chef using Chef's always-open-source InSpec source code. CINC Auditor and InSpec are built from the same source code and function identically, but CINC Auditor requires no license to use (which means it also does not come with any expectation of support from Chef). + +For more information see [CINC Home](https://cinc.sh/) + +It is intended and recommended that CINC Auditor and this profile executed from a __"runner"__ host +(such as a DevOps orchestration server, an administrative management system, or a developer's workstation/laptop) +against the target. This can be any Unix/Linux/MacOS or Windows runner host, with access to the Internet. + +> [!TIP] +> **For the best security of the runner, always install on the runner the latest version of CINC Auditor and any other supporting language components.** + +To install CINC Auditor on a UNIX/Linux/MacOS platform use the following command: +```bash +curl -L https://omnitruck.cinc.sh/install.sh | sudo bash -s -- -P cinc-auditor ``` -### Profile Input Values -The default values for profile inputs are given in `inspec.yml`. Not all values in `inspec.yml` have been given a default value -- for example, the sensitive connection and authentication variables have not been (and cannot be) given a default. - -These values need to be overridden with values appropriate for your environment. You must create an `inputs.yml` file -- see [the InSpec documentation for inputs](https://docs.chef.io/inspec/inputs/). - -** DO NOT COMMIT YOUR INPUTS.YML FILE! ** That file will include sensitive login info for your own M365 instance. - -```yml - #Controls using this input: - #1.1.3, 1.2.1, 1.2.2, 1.3.1, 1.3.3, 1.3.6, - #2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 2.1.10, 2.1.14, 2.4.4, - #3.1.1, 3.2.2, - #5.1.1.1, 5.1.2.2, 5.1.2.3, 5.1.3.1, 5.1.5.2, 5.1.8.1, 5.2.2.3, 5.2.3.4, - #6.1.1, 6.1.2, 6.1.3, 6.1.4, 6.2.1, 6.2.2, 6.2.3, 6.3.1, 6.5.1, 6.5.2, 6.5.3, - #7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, - #8.1.1, 8.1.2, 8.2.1, 8.5.1, 8.5.2, 8.5.3, 8.5.4, 8.5.5, 8.5.6, 8.5.7, 8.5.8, 8.6.1 - - name: client_id - sensitive: true - description: 'Client ID for Microsoft 365' - type: String - required: true - - #Controls using this input: - #1.1.3, 1.2.1, 1.2.2, 1.3.1, - #5.1.1.1, 5.1.2.2, 5.1.2.3, 5.1.3.1, 5.1.5.2, 5.1.8.1, 5.2.3.4, - #7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, - #8.1.1, 8.1.2, 8.2.1, 8.5.1, 8.5.2, 8.5.3, 8.5.4, 8.5.5, 8.5.6, 8.5.7, 8.5.8, 8.6.1 - - name: tenant_id - sensitive: true - description: 'Tenant ID for Microsoft 365' - type: String - required: true - - #Controls using this input: - #1.1.3, 1.2.1, 1.2.2, 1.3.1, - #5.1.1.1, 5.1.2.2, 5.1.2.3, 5.1.3.1, 5.1.5.2, 5.1.8.1, 5.2.3.4, - #7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, - #8.1.2 - - name: client_secret - sensitive: true - description: 'Client Secret for Microsoft 365' - type: String - required: true - - #Controls using this input: - #1.2.2, 1.3.3, 1.3.6, - #2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 2.1.10, 2.1.14, 2.4.4, - #3.1.1, 3.2.2, - #5.2.2.3, - #6.1.1, 6.1.2, 6.1.3, 6.1.4, 6.2.1, 6.2.2, 6.2.3, 6.3.1, 6.5.1, 6.5.2, 6.5.3, - #7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, - #8.1.1, 8.1.2, 8.2.1, 8.5.1, 8.5.2, 8.5.3, 8.5.4, 8.5.5, 8.5.6, 8.5.7, 8.5.8, 8.6.1 - - name: certificate_path - sensitive: true - description: 'Certificate path for M365' - type: String - required: true - - #Controls using this input: - #1.2.2, 1.3.3, 1.3.6, - #2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 2.1.10, 2.1.14, 2.4.4, - #3.1.1, 3.2.2, - #5.2.2.3, - #6.1.1, 6.1.2, 6.1.3, 6.1.4, 6.2.1, 6.2.2, 6.2.3, 6.3.1, 6.5.1, 6.5.2, 6.5.3, - #7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, - #8.1.1, 8.1.2, 8.2.1, 8.5.1, 8.5.2, 8.5.3, 8.5.4, 8.5.5, 8.5.6, 8.5.7, 8.5.8, 8.6.1 - - name: certificate_password - sensitive: true - description: 'Password for certificate for M365' - type: String - required: true - - #Controls using this input: - #1.2.2, 1.3.1, 1.3.3, 1.3.6, - #2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 2.1.10, 2.1.14, 2.4.4, - #3.1.1, 3.2.2, - #5.2.2.3, - #6.1.1, 6.1.2, 6.1.3, 6.1.4, 6.2.1, 6.2.2, 6.2.3, 6.3.1, 6.5.1, 6.5.2, 6.5.3, - #8.6.1 - - name: organization - sensitive: true - description: 'M365 Organization' - type: String - required: true - - #Controls using this input: - #2.1.6 - - name: notify_outbound_spam_recipients - sensitive: true - description: 'Email address to notify administrator for Exchange Online Spam Policies' - type: Array - required: true - - #Controls using this input: - #2.1.6 - - name: bcc_suspicious_outbound_additional_recipients - sensitive: true - description: 'BCC email address to notify additional recipients for Exchange Online Spam Policies' - type: Array - required: true - - #Controls using this input: - #2.1.8 - - name: spf_domains - sensitive: true - description: 'Array of domains needed to check for SPF record' - type: Array - required: true - - #Controls using this input: - #2.1.10 - - name: dmarc_domains - sensitive: true - description: 'Array of DMARC records to check' - type: Array - required: true - - #Controls using this input: - #2.1.10 - - name: reporting_mail_address - sensitive: true - description: 'Reporting mail address needed for DMARC check' - type: String - required: true - - #Controls using this input: - #2.1.10 - - name: moera_domains - sensitive: true - description: 'Array of MOERA records to check' - type: Array - required: true - - #Controls using this input: - #3.2.2 - - name: permitted_exceptions_teams_locations - sensitive: true - description: 'Permitted exceptions for teams locations' - type: Array - required: true - - #Controls using this input: - #6.2.1 - - name: internal_domains_transport_rule - sensitive: true - description: 'Domains internal to the organization to be checked' - type: Array - required: true - - #Controls using this input: - #6.2.3 - - name: email_addresses_bypass_external_tagging - sensitive: true - description: 'Email address list that are allowed to bypass external tagging' - type: Array - required: true - - #Controls using this input: - #6.5.2 - - name: mailtipslargeaudiencethreshold_value - sensitive: true - description: 'MailTipsLargeAudienceThreshold value to check for in MailTips setting' - required: true - - #Controls using this input: - #6.5.2 - - name: authorized_domains_teams_admin_center - sensitive: true - description: 'List of authorized domains for AllowedDomains option in Teams Admin Center' - type: Array - required: true - - #Controls using this input: - #8.6.1 - - name: reporting_email_addresses_for_malicious_messages - sensitive: true - description: 'Email addresses to check to report malicious messages in Teams and Defender' - type: Array - required: true - - #Controls using this input: - #7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, - - name: sharepoint_admin_url - sensitive: true - description: 'SharePoint Admin URL to connect to' - type: String - required: true - - #Controls using this input: - #7.2.6 - - name: domains_trusted_by_organization - sensitive: true - description: 'Domains that are trusted by organization in SharePoint' - type: Array - required: true - - #Controls using this input: - #7.2.9 - - name: external_user_expiry_in_days_spo_threshold - sensitive: true - description: 'Threshold in days to check for external user expiry in SharePoint' - value: 30 - required: true - - #Controls using this input: - #7.2.10 - - name: email_attestation_re_auth_days_spo_threshold - sensitive: true - description: 'Threshold in days to check for email attestation auth in SharePoint' - value: 15 - required: true - - #Controls using this input: - #7.3.2 - - name: trusted_domains_guids - sensitive: true - description: 'Domain GUIDs trusted from the on premises environment' - type: Array - required: true - + +To install CINC Auditor on a Windows platform (Powershell) use the following command: +```powershell +. { iwr -useb https://omnitruck.cinc.sh/install.ps1 } | iex; install -project cinc-auditor ``` - -### How to execute this instance -(See: https://www.inspec.io/docs/reference/cli/) - -**How to execute the Microsoft 365 profile** - -#### Execute a single Control in the Profile + +To confirm successful install of CINC Auditor: +``` +cinc-auditor -v +``` + +Latest versions and other installation options are available at [CINC Auditor](https://cinc.sh/start/auditor/)'s website. + +[top](#table-of-contents) +### Intended Usage +1. The latest `released` version of the profile is intended for use in A&A testing, as well as + providing formal results to Authorizing Officials and Identity and Access Management (IAM)s. + Please use the `released` versions of the profile in these types of workflows. + +2. The `main` branch is a development branch that will become the next release of the profile. + The `main` branch is intended for use in _developing and testing_ merge requests for the next + release of the profile, and _is not intended_ be used for formal and ongoing testing on systems. + +[top](#table-of-contents) +### Tailoring to Your Environment +This profile uses InSpec Inputs to provide flexibility during testing. Inputs allow for +customizing the behavior of Chef InSpec profiles. + +InSpec Inputs are defined in the `inspec.yml` file. The `inputs` configured in this +file are **profile definitions and defaults for the profile** extracted from the profile +guidances and contain metadata that describe the profile, and shouldn't be modified. + +InSpec provides several methods for customizing profile behaviors at run-time that does not require +modifying the `inspec.yml` file itself (see [Using Customized Inputs](#using-customized-inputs)). + +The following inputs are permitted to be configured in an inputs `.yml` file (often named inputs.yml) +for the profile to run correctly on a specific environment, while still complying with the security +guidance document intent. This is important to prevent confusion when test results are passed downstream +to different stakeholders under the *security guidance name used by this profile repository* + +For changes beyond the inputs cited in this section, users can create an *organizationally-named overlay repository*. +For more information on developing overlays, reference the [MITRE SAF Training](https://mitre-saf-training.netlify.app/courses/beginner/10.html) + +#### Example of tailoring Inputs *While Still Complying* with the security guidance document for the profile: + +```yaml +#Controls using this input: +#1.3.1 +- name: org_domain + sensitive: true + description: 'Domain for organization' + type: String + required: true + +#Controls using this input: +#2.1.6 +- name: notify_outbound_spam_recipients + sensitive: true + description: 'Email address to notify administrator for Exchange Online Spam Policies' + type: Array + required: true + +#Controls using this input: +#2.1.6 +- name: bcc_suspicious_outbound_additional_recipients + sensitive: true + description: 'BCC email address to notify additional recipients for Exchange Online Spam Policies' + type: Array + required: true + +#Controls using this input: +#2.1.8 +- name: spf_domains + sensitive: true + description: 'Array of domains needed to check for SPF record' + type: Array + required: true + +#Controls using this input: +#2.1.10 +- name: dmarc_domains + sensitive: true + description: 'Array of DMARC records to check' + type: Array + required: true + +#Controls using this input: +#2.1.10 +- name: reporting_mail_address + sensitive: true + description: 'Reporting mail address needed for DMARC check' + type: String + required: true + +#Controls using this input: +#3.2.2 +- name: permitted_exceptions_teams_locations + sensitive: true + description: 'Permitted exceptions for teams locations' + type: Array + required: true + +#Controls using this input: +#6.2.1 +- name: internal_domains_transport_rule + sensitive: true + description: 'Domains internal to the organization to be checked' + type: Array + required: true + +#Controls using this input: +#6.2.3 +- name: email_addresses_bypass_external_tagging + sensitive: true + description: 'Email address list that are allowed to bypass external tagging' + type: Array + required: true + +#Controls using this input: +#6.5.2 +- name: mailtipslargeaudiencethreshold_value + sensitive: true + description: 'MailTipsLargeAudienceThreshold value to check for in MailTips setting' + required: true + +#Controls using this input: +#6.5.2 +- name: authorized_domains_teams_admin_center + sensitive: true + description: 'List of authorized domains for AllowedDomains option in Teams Admin Center' + type: Array + required: true + +#Controls using this input: +#8.6.1 +- name: reporting_email_addresses_for_malicious_messages + sensitive: true + description: 'Email addresses to check to report malicious messages in Teams and Defender' + type: Array + required: true + +#Controls using this input: +#7.2.6 +- name: domains_trusted_by_organization + sensitive: true + description: 'Domains that are trusted by organization in SharePoint' + type: Array + required: true + +#Controls using this input: +#7.2.9 +- name: external_user_expiry_in_days_spo_threshold + sensitive: true + description: 'Threshold in days to check for external user expiry in SharePoint' + value: 30 + required: true + +#Controls using this input: +#7.2.10 +- name: email_attestation_re_auth_days_spo_threshold + sensitive: true + description: 'Threshold in days to check for email attestation auth in SharePoint' + value: 15 + required: true + +#Controls using this input: +#7.3.2 +- name: trusted_domains_guids + sensitive: true + description: 'Domain GUIDs trusted from the on premises environment' + type: Array + required: true +``` + +> [!NOTE] +>Inputs are variables that are referenced by control(s) in the profile that implement them. + They are declared (defined) and given a default value in the `inspec.yml` file. + +#### Using Customized Inputs +Customized inputs may be used at the CLI by providing an input file or a flag at execution time. + +1. Using the `--input` flag + + Example: `[inspec or cinc-auditor] exec --input disable_slow_controls=true` + +2. Using the `--input-file` flag. + + Example: `[inspec or cinc-auditor] exec --input-file=` + +>[!TIP] +> For additional information about `input` file examples reference the [MITRE SAF Training](https://mitre.github.io/saf-training/courses/beginner/06.html#input-file-example) + +Chef InSpec Resources: +- [InSpec Profile Documentation](https://docs.chef.io/inspec/profiles/). +- [InSpec Inputs](https://docs.chef.io/inspec/profiles/inputs/). +- [inspec.yml](https://docs.chef.io/inspec/profiles/inspec_yml/). + + +[top](#table-of-contents) +### Testing the Profile Controls +The Gemfile provided contains all the necessary ruby dependencies for checking the profile controls. +#### Requirements +All action are conducted using `ruby` (gemstone/programming language). Currently `inspec` +commands have been tested with ruby version 3.1.2. A higher version of ruby is not guaranteed to +provide the expected results. Any modern distribution of Ruby comes with Bundler preinstalled by default. + +Install ruby based on the OS being used, see [Installing Ruby](https://www.ruby-lang.org/en/documentation/installation/) + +After installing `ruby` install the necessary dependencies by invoking the bundler command +(must be in the same directory where the Gemfile is located): +```bash +bundle install +``` + +#### Testing Commands + +Linting and validating controls: +```bash + bundle exec rake [inspec or cinc-auditor]:check # Validate the InSpec Profile + bundle exec rake lint # Run RuboCop Linter + bundle exec rake lint:auto_correct # Autocorrect RuboCop offenses (only when it's safe) + bundle exec rake pre_commit_checks # Pre-commit checks +``` + +Ensure the controls are ready to be committed into the repo: +```bash + bundle exec rake pre_commit_checks +``` + + +[top](#table-of-contents) +## Running the Profile **Note**: Replace the profile's directory name - e.g. - `` with `.` if currently in the profile's root directory. ```sh -inspec exec --controls= --input client_id= tenant_id= client_secret= certificate_path=cert.pfx certificate_password= organization= --enhanced-outcomes --input-file=inputs.yml +inspec exec -t pwsh:// --controls= --enhanced-outcomes --input-file=inputs.yml ``` #### Execute a Single Control and save results as JSON ```sh -inspec exec --input client_id= tenant_id= client_secret= certificate_path= certificate_password= organization= --controls= --enhanced-outcomes --input-file=inputs.yml --reporter json:results.json +inspec exec -t pwsh:// --controls= --enhanced-outcomes --input-file=inputs.yml --reporter json:results.json ``` #### Execute All Controls in the Profile ```sh -inspec exec --input client_id= tenant_id= client_secret= certificate_path= certificate_password= organization= --enhanced-outcomes --input-file=inputs.yml +inspec exec -t pwsh:// --enhanced-outcomes --input-file=inputs.yml ``` #### Execute all the Controls in the Profile and save results as JSON ```sh -inspec exec --input client_id= tenant_id= client_secret= certificate_path= certificate_password= organization= --enhanced-outcomes --input-file=inputs.yml --reporter json:results.json +inspec exec -t pwsh:// --enhanced-outcomes --input-file=inputs.yml --reporter json:results.json ``` - -It is important to note that in the commands above there are both an input file as well as inputs. The inputs that are in the input file are non-sensitive, while the inputs that are being passed along in the command line are sensitive. The reasoning behind keeping these separate is to ensure folks do not accidentally commit their input files with sensitive data. +[top](#table-of-contents) + +## Different Run Options + +[Full exec options](https://docs.chef.io/inspec/cli/#options-3) + +[top](#table-of-contents) + +## Using Heimdall for Viewing Test Results +The JSON results output file can be loaded into **[Heimdall-Lite](https://heimdall-lite.mitre.org/)** +or **[Heimdall-Server](https://github.com/mitre/heimdall2)** for a user-interactive, graphical view of the profile scan results. + +Heimdall-Lite is a `browser only` viewer that allows you to easily view your results directly and locally rendered in your browser. +Heimdall-Server is configured with a `data-services backend` allowing for data persistency to a database (PostgreSQL). +For more detail on feature capabilities see [Heimdall Features](https://github.com/mitre/heimdall2?tab=readme-ov-file#features) + +Heimdall can **_export your results into a DISA Checklist (CKL) file_** for easily uploading into eMass using the `Heimdall Export` function. + +Depending on your environment restrictions, the [SAF CLI](https://saf-cli.mitre.org) can be used to run a local docker instance +of Heimdall-Lite via the `saf view:heimdall` command. + +Additionally both Heimdall applications can be deployed via docker, kubernetes, or the installation packages. ## Check Overview -**M365 Services** +### M365 Services This profile evaluates the M365 CIS Benchmark compliance of the following M365 administrative centers by evaluating their setting configurations: @@ -309,7 +382,7 @@ This profile evaluates the M365 CIS Benchmark compliance of the following M365 a - Microsoft SharePoint Admin Center - Microsoft Fabric -# Control and Automation Status +### Control and Automation Status Not all controls in the CIS Benchmark are capable of automated assessment. The table below marks which controls are automated and which ones are manual. @@ -354,7 +427,7 @@ Not all controls in the CIS Benchmark are capable of automated assessment. The t | 3.2.1 | Manual | | 3.2.2 | Automated | | 3.3.1 | Manual | -| 5.1.1.1 | Manual | +| 5.1.1.1 | Automated | | 5.1.2.1 | Manual | | 5.1.2.2 | Automated | | 5.1.2.3 | Automated | @@ -366,6 +439,7 @@ Not all controls in the CIS Benchmark are capable of automated assessment. The t | 5.1.5.2 | Automated | | 5.1.5.3 | Manual | | 5.1.6.1 | Manual | +| 5.1.8.1 | Automated | | 5.2.2.1 | Manual | | 5.2.2.2 | Manual | | 5.2.2.3 | Automated | @@ -377,7 +451,7 @@ Not all controls in the CIS Benchmark are capable of automated assessment. The t | 5.2.3.1 | Manual | | 5.2.3.2 | Manual | | 5.2.3.3 | Manual | -| 5.2.3.4 | Manual | +| 5.2.3.4 | Automated | | 5.2.4.1 | Manual | | 5.2.4.2 | Manual | | 5.2.6.1 | Manual | @@ -434,3 +508,31 @@ Not all controls in the CIS Benchmark are capable of automated assessment. The t | 9.1.9 | Manual | For any controls marked as 'Manual', please refer to the following following at [SAF-CLI](https://saf-cli.mitre.org/) on how to apply manual attestations to the output of an automated assessment. The following [link](https://vmware.github.io/dod-compliance-and-automation/docs/automation-tools/safcli/) that references the SAF-CLI is also useful. + +[top](#table-of-contents) + +## Authors +[Center for Internet Security (CIS)](https://www.cisecurity.org/) + +[MITRE Security Automation Framework Team](https://saf.mitre.org) + +## NOTICE + +© 2018-2025 The MITRE Corporation. + +Approved for Public Release; Distribution Unlimited. Case Number 18-3678. + +## NOTICE + +MITRE hereby grants express written permission to use, reproduce, distribute, modify, and otherwise leverage this software to the extent permitted by the licensed terms provided in the LICENSE.md file included with this project. + +## NOTICE + +This software was produced for the U. S. Government under Contract Number HHSM-500-2012-00008I, and is subject to Federal Acquisition Regulation Clause 52.227-14, Rights in Data-General. + +No other use other than that granted to the U. S. Government, or to those acting on behalf of the U. S. Government under that Clause is authorized without the express written permission of The MITRE Corporation. + +For further information, please contact The MITRE Corporation, Contracts Management Office, 7515 Colshire Drive, McLean, VA 22102-7539, (703) 983-6000. + +## NOTICE +[CIS Benchmarks are published by Center for Internet Security](https://www.cisecurity.org/cis-benchmarks) diff --git a/controls/microsoft-365-foundations-1.1.3.rb b/controls/microsoft-365-foundations-1.1.3.rb index 17fa9d2..90806a5 100644 --- a/controls/microsoft-365-foundations-1.1.3.rb +++ b/controls/microsoft-365-foundations-1.1.3.rb @@ -47,21 +47,15 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.identity.directorymanagement/get-mgdirectoryrole?view=graph-powershell-1.0' ref 'https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#role-template-ids' - get_admin_user_count_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - Install-Module -Name Microsoft.Graph -Force -AllowClobber - import-module microsoft.graph - $password = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force - $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential($client_id,$password) - Connect-MgGraph -TenantId $tenantid -ClientSecretCredential $ClientSecretCredential -NoWelcome - $globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'" - $globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id - Write-Host $globalAdmins.AdditionalProperties.Count - } + get_admin_user_count_script = %( + $globalAdminRole = Get-MgDirectoryRole -Filter "RoleTemplateId eq '62e90394-69f5-4237-9190-012177145e10'" + $globalAdmins = Get-MgDirectoryRoleMember -DirectoryRoleId $globalAdminRole.Id + Write-Host $globalAdmins.AdditionalProperties.Count + ) + powershell_output = pwsh_single_session_executor(get_admin_user_count_script).run_script_in_graph_exchange + + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 - powershell_output = powershell(get_admin_user_count_script) describe 'Ensure global tenant administrator count' do subject { powershell_output.stdout.strip } it 'should be between two to four' do diff --git a/controls/microsoft-365-foundations-1.2.1.rb b/controls/microsoft-365-foundations-1.2.1.rb index f64f719..7107893 100644 --- a/controls/microsoft-365-foundations-1.2.1.rb +++ b/controls/microsoft-365-foundations-1.2.1.rb @@ -34,19 +34,12 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/admin/create-groups/compare-groups?view=o365-worldwide' all_groups_private_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - Install-Module -Name Microsoft.Graph -Force -AllowClobber - import-module microsoft.graph - $password = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force - $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential($client_id,$password) - Connect-MgGraph -TenantId $tenantid -ClientSecretCredential $ClientSecretCredential -NoWelcome - # Determine Id of role using the immutable RoleTemplateId value. Write-Host (Get-MgGroup | where {$_.Visibility -eq "Public"} | select DisplayName,Visibility).Count } - powershell_output = powershell(all_groups_private_script) + powershell_output = pwsh_single_session_executor(all_groups_private_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Public groups count' do subject { powershell_output.stdout.to_i } it 'should be 0' do diff --git a/controls/microsoft-365-foundations-1.2.2.rb b/controls/microsoft-365-foundations-1.2.2.rb index 9d67a97..25b4933 100644 --- a/controls/microsoft-365-foundations-1.2.2.rb +++ b/controls/microsoft-365-foundations-1.2.2.rb @@ -50,27 +50,14 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/admin/email/create-a-shared-mailbox?view=o365-worldwide#block-sign-in-for-the-shared-mailbox-account' ref 'https://learn.microsoft.com/en-us/microsoft-365/enterprise/block-user-accounts-with-microsoft-365-powershell?view=o365-worldwide#block-individual-user-accounts' - ensure_signin_mailboxes_blocked_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - $clientSecret = '#{input('client_secret')}' - Install-Module -Name Microsoft.Graph -Force -AllowClobber - import-module microsoft.graph - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - $password = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force - $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential($client_id,$password) - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - Connect-MgGraph -TenantId $tenantid -ClientSecretCredential $ClientSecretCredential -NoWelcome + ensure_signin_mailboxes_blocked_script = %( $MBX = Get-EXOMailbox -RecipientTypeDetails SharedMailbox $disabled_account_count = $MBX | ForEach-Object { Get-MgUser -UserId $_.ExternalDirectoryObjectId ` -Property DisplayName, UserPrincipalName, AccountEnabled } | Where-Object { $_.AccountEnabled -eq $true } | Measure-Object | Select-Object -ExpandProperty Count Write-Output $disabled_account_count - } + ) + powershell_output = pwsh_single_session_executor(ensure_signin_mailboxes_blocked_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 - powershell_output = powershell(ensure_signin_mailboxes_blocked_script) describe 'Ensure the number of shared mailboxes with AccountEnabled as true' do subject { powershell_output.stdout.strip } it 'is equal to 0' do diff --git a/controls/microsoft-365-foundations-1.3.1.rb b/controls/microsoft-365-foundations-1.3.1.rb index bbd147e..dc556a6 100644 --- a/controls/microsoft-365-foundations-1.3.1.rb +++ b/controls/microsoft-365-foundations-1.3.1.rb @@ -36,19 +36,13 @@ ref 'https://learn.microsoft.com/en-US/microsoft-365/admin/misc/password-policy-recommendations?view=o365-worldwide' password_expiration_days_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $organization = '#{input('organization')}' - Install-Module -Name Microsoft.Graph -Force -AllowClobber - import-module microsoft.graph - $password = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force - $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential($client_id,$password) - Connect-MgGraph -TenantId $tenantid -ClientSecretCredential $ClientSecretCredential -NoWelcome + $organization = "#{input('org_domain')}" $passwordValidityPeriod = (Get-MgDomain -DomainId $organization).PasswordValidityPeriodInDays Write-Output $passwordValidityPeriod } - powershell_output = powershell(password_expiration_days_script) + powershell_output = pwsh_single_session_executor(password_expiration_days_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'The password validity period' do subject { powershell_output.stdout.to_i } it 'should be at integer max value' do diff --git a/controls/microsoft-365-foundations-1.3.3.rb b/controls/microsoft-365-foundations-1.3.3.rb index 8a14ff2..80b96d8 100644 --- a/controls/microsoft-365-foundations-1.3.3.rb +++ b/controls/microsoft-365-foundations-1.3.3.rb @@ -36,18 +36,13 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/admin/manage/share-calendars-with-external-users?view=o365-worldwide' - ensure_external_calendar_sharing_blocked_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ensure_external_calendar_sharing_blocked_script = %( $enabledStatus = Get-SharingPolicy -Identity "Default Sharing Policy" | Select-Object -ExpandProperty Enabled Write-Output $enabledStatus - } + ) + powershell_output = pwsh_single_session_executor(ensure_external_calendar_sharing_blocked_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 - powershell_output = powershell(ensure_external_calendar_sharing_blocked_script) describe 'Ensure the Enabled option for Default Sharing Policy' do subject { powershell_output.stdout.strip } it 'is set to False' do diff --git a/controls/microsoft-365-foundations-1.3.6.rb b/controls/microsoft-365-foundations-1.3.6.rb index bbb3a49..488cdc5 100644 --- a/controls/microsoft-365-foundations-1.3.6.rb +++ b/controls/microsoft-365-foundations-1.3.6.rb @@ -42,18 +42,13 @@ ref 'https://learn.microsoft.com/en-us/azure/security/fundamentals/customer-lockbox-overview' - ensure_customer_lockbox_is_enabled_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ensure_customer_lockbox_is_enabled_script = %( $lock_box_status = Get-OrganizationConfig | Select-Object -ExpandProperty CustomerLockBoxEnabled Write-Output $lock_box_status - } + ) + powershell_output = pwsh_single_session_executor(ensure_customer_lockbox_is_enabled_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 - powershell_output = powershell(ensure_customer_lockbox_is_enabled_script) describe 'Ensure the CustomerLockBoxEnabled option from Get-OrganizationConfig' do subject { powershell_output.stdout.strip } it 'is set to True' do diff --git a/controls/microsoft-365-foundations-2.1.1.rb b/controls/microsoft-365-foundations-2.1.1.rb index 66b47ba..4aef108 100644 --- a/controls/microsoft-365-foundations-2.1.1.rb +++ b/controls/microsoft-365-foundations-2.1.1.rb @@ -89,65 +89,51 @@ ref 'https://learn.microsoft.com/en-us/defender-office-365/preset-security-policies?view=o365-worldwide' get_policy_names_line = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - $policy_names = Get-SafeLinksPolicy | Select-Object -ExpandProperty Name - Write-Output $policy_names - } - policy_links_script = powershell(get_policy_names_line) + $policies = Get-SafeLinksPolicy + foreach ($policy in $policies) { + $failedConditions = @() - describe 'Ensure the number of Safe Links policies' do - subject { powershell(get_policy_names_line).stdout.strip } - it 'is not 0' do - expect(subject).to_not be_empty - end - end + if ($policy.EnableSafeLinksForEmail -eq $false) { + $failedConditions += "EnableSafeLinksForEmail" + } + if ($policy.EnableSafeLinksForTeams -eq $false) { + $failedConditions += "EnableSafeLinksForTeams" + } + if ($policy.EnableSafeLinksForOffice -eq $false) { + $failedConditions += "EnableSafeLinksForOffice" + } + if ($policy.TrackClicks -eq $false) { + $failedConditions += "TrackClicks" + } + if ($policy.AllowClickThrough -eq $true) { + $failedConditions += "AllowClickThrough" + } + if ($policy.ScanUrls -eq $false) { + $failedConditions += "ScanUrls" + } + if ($policy.EnableForInternalSenders -eq $false) { + $failedConditions += "EnableForInternalSenders" + } + if ($policy.DeliverMessageAfterScan -eq $false) { + $failedConditions += "DeliverMessageAfterScan" + } + if ($policy.DisableUrlRewrite -eq $true) { + $failedConditions += "DisableUrlRewrite" + } - policy_names = policy_links_script.stdout.strip.split("\n") - policy_names.each do |policy_name| - get_state_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - Get-SafeLinksPolicy -Identity "#{policy_name.strip}" | Select-Object -Property EnableSafeLinksForEmail, EnableSafeLinksForTeams, EnableSafeLinksForOffice, TrackClicks, AllowClickThrough, ScanUrls, EnableForInternalSenders, DeliverMessageAfterScan, DisableUrlRewrite | ConvertTo-Json + if ($failedConditions.Count -gt 0) { + Write-Output "Policy Name: $($policy.Name), Failed Conditions = [$($failedConditions -join ', ')]" + } } - describe "Safe Links Policy: #{policy_name}" do - subject { JSON.parse(powershell(get_state_script).stdout.strip) } - it 'should have EnableSafeLinksForEmail set to True' do - expect(subject['EnableSafeLinksForEmail']).to eq(true) - end - it 'should have EnableSafeLinksForTeams set to True' do - expect(subject['EnableSafeLinksForTeams']).to eq(true) - end - it 'should have EnableSafeLinksForOffice set to True' do - expect(subject['EnableSafeLinksForOffice']).to eq(true) - end - it 'should have TrackClicks set to True' do - expect(subject['TrackClicks']).to eq(true) - end - it 'should have AllowClickThrough set to False' do - expect(subject['AllowClickThrough']).to eq(false) - end - it 'should have ScanUrls set to True' do - expect(subject['ScanUrls']).to eq(true) - end - it 'should have EnableForInternalSenders set to True' do - expect(subject['EnableForInternalSenders']).to eq(true) - end - it 'should have DeliverMessageAfterScan set to True' do - expect(subject['DeliverMessageAfterScan']).to eq(true) - end - it 'should have DisableUrlRewrite set to False' do - expect(subject['DisableUrlRewrite']).to eq(false) - end + } + policy_links_script = pwsh_single_session_executor(get_policy_names_line).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{policy_links_script.stderr}" if policy_links_script.exit_status != 0 + + describe 'Ensure the number of safe links policies that have the settings EnableSafeLinksForEmail as False, EnableSafeLinksForTeams as False, EnableSafeLinksForOffice as False, TrackClicks as False, AllowClickThrough as True, ScanUrls as False, EnableForInternalSenders as False, DeliverMessageAfterScan as False, or DisableUrlRewrite as True' do + subject { policy_links_script.stdout.strip } + it 'is 0' do + failure_message = "The following safe link policies have failed along with conditions they have failed on: #{policy_links_script.stdout.strip.split("\n").join(',')}" + expect(subject).to be_empty, failure_message end end end diff --git a/controls/microsoft-365-foundations-2.1.10.rb b/controls/microsoft-365-foundations-2.1.10.rb index c0498d7..49d1bcd 100644 --- a/controls/microsoft-365-foundations-2.1.10.rb +++ b/controls/microsoft-365-foundations-2.1.10.rb @@ -46,44 +46,76 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/email-authentication-dmarc-configure?view=o365-worldwide' ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/step-by-step-guides/how-to-enable-dmarc-reporting-for-microsoft-online-email-routing-address-moera-and-parked-domains?view=o365-worldwide' - # This does not work on Mac - need to find a different way to test. Additionally, CIS Benchmark says manual - domain_list = input('dmarc_domains') - domain_list.each do |domain| - check_dmarc_domain_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - Import-Module DNSClient - Resolve-DnsName _dmarc.#{domain} txt + dmarc_domain_list = %("#{input('dmarc_domains').sort.join('", "')}") + check_dmarc_domain_script = %{ + $domains = (#{dmarc_domain_list}) + try{ + Import-Module DNSClient -ErrorAction Stop + } + catch{ + } + foreach ($domain in $domains) { + try { + $dmarcRecord = Resolve-DnsName -Name "_dmarc.$domain" -Type TXT -ErrorAction Stop + $dmarcText = $dmarcRecord.Strings -join ";" + + if ($dmarcText.Contains("v=DMARC1") -and + ($dmarcText.Contains("p=quarantine") -or $dmarcText.Contains("p=reject")) -and + $dmarcText.Contains("pct=100") -and + $dmarcText.Contains("rua=mailto:#{input('reporting_mail_address')}") -and + $dmarcText.Contains("ruf=mailto:#{input('reporting_mail_address')}")) { + Write-Output "DMARC record for $domain is valid." + } else { + Write-Output "DMARC record for $domain is invalid or missing required flags." + } + } catch { + Write-Output "No DMARC record found for $domain." + } + } } - describe "Ensure the following DMARC domain (#{domain})" do - subject { powershell(check_dmarc_domain_script).stdout.strip } - it %{should contain all parts following string: v=DMARC1; (p=quarantine OR p=reject), pct=100, rua=mailto:#{input('reporting_mail_address')} and ruf=mailto:#{input('reporting_mail_address')}} do - expect(subject).to match %{v=DMARC1;.*p=(quarantine|reject);.*pct=100;.*rua=mailto:.*ruf=mailto:#{input('reporting_mail_address')}} - end + powershell_output_dmarc = pwsh_single_session_executor(check_dmarc_domain_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_dmarc.stderr}" if powershell_output_dmarc.exit_status != 0 + + describe "Ensure the number of DMARC domains that do not contain a record or does not contain the following substring in the record v=DMARC1; (p=quarantine OR p=reject), pct=100, rua=mailto:#{input('reporting_mail_address')} and ruf=mailto:#{input('reporting_mail_address')}" do + subject { powershell_output_dmarc.stdout.strip } + it 'is 0' do + failure_message = "The following DMARC domains have failed: #{powershell_output_dmarc.stdout.strip.split("\n").join(',')}" + expect(subject).to be_empty, failure_message end end - domain_list_moera = input('moera_domains') - domain_list_moera.each do |domain| - check_moera_domain_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - Import-Module DNSClient - Resolve-DnsName _dmarc.#{domain}.onmicrosoft.com txt + check_moera_domain_script = %{ + try{ + Import-Module DNSClient -ErrorAction Stop + } + catch{ + + } + $domain = "_dmarc.$tenantid.onmicrosoft.com" + try { + $moeraRecord = Resolve-DnsName -Name $domain -Type TXT -ErrorAction Stop + $moeraText = $moeraRecord.Strings -join ";" + + if ($moeraText.Contains("v=DMARC1") -and + ($moeraText.Contains("p=quarantine") -or $moeraText.Contains("p=reject")) -and + $moeraText.Contains("pct=100") -and + $moeraText.Contains("rua=mailto:#{input('reporting_mail_address')}") -and + $moeraText.Contains("ruf=mailto:#{input('reporting_mail_address')}")) { + Write-Output "MOERA record for $domain is valid." + } else { + Write-Output "MOERA record for $domain is invalid or missing required flags." + } + } catch { + Write-Output "No MOERA record found for $domain." + } } - describe "Ensure the following MOERA domain (#{domain})" do - subject { powershell(check_moera_domain_script).stdout.strip } - it %{should contain all parts following string: v=DMARC1; (p=quarantine OR p=reject), pct=100, rua=mailto:#{input('reporting_mail_address')} and ruf=mailto:#{input('reporting_mail_address')}} do - expect(subject).to match %{v=DMARC1;.*p=(quarantine|reject);.*pct=100;.*rua=mailto:.*ruf=mailto:#{input('reporting_mail_address')}} - end + powershell_output_moera = pwsh_single_session_executor(check_moera_domain_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_moera.stderr}" if powershell_output_moera.exit_status != 0 + + describe "Ensure the number of MOERA domains that do not contain a record or does not contain the following substring in the record v=DMARC1; (p=quarantine OR p=reject), pct=100, rua=mailto:#{input('reporting_mail_address')} and ruf=mailto:#{input('reporting_mail_address')}" do + subject { powershell_output_moera.stdout.strip } + it 'is 0' do + failure_message = "The following MOERA domains have failed: #{powershell_output_moera.stdout.strip.split("\n").join(',')}" + expect(subject).to be_empty, failure_message end end end diff --git a/controls/microsoft-365-foundations-2.1.14.rb b/controls/microsoft-365-foundations-2.1.14.rb index 6c4a7e0..12fc71d 100644 --- a/controls/microsoft-365-foundations-2.1.14.rb +++ b/controls/microsoft-365-foundations-2.1.14.rb @@ -153,19 +153,15 @@ def ruby_to_powershell(dictionary) powershell_script end - get_policies_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + get_policies_script = %( $ExtensionPolicies = Get-MalwareFilterPolicy | Where-Object {$_.FileTypes.Count -gt 120 } $ExtensionPolicies | ConvertTo-Json - } + ) - get_polices_output = powershell(get_policies_script).stdout.strip + get_polices_output = pwsh_single_session_executor(get_policies_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{get_polices_output.stderr}" if get_polices_output.exit_status != 0 + + get_polices_output = get_polices_output.stdout ||= '' policy_list = JSON.parse(get_polices_output) unless get_polices_output.empty? describe 'Ensure there is at least one policy that' do subject { policy_list } @@ -176,12 +172,6 @@ def ruby_to_powershell(dictionary) policy_list&.each do |_policy| file_ext_list = _policy.map { |file_ext| "'#{file_ext}'" }.join(', ') ensure_comprehensive_attachment_filtering_applied_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false $L2Extensions = @( "7z", "a3x", "ace", "ade", "adp", "ani", "app", "appinstaller", "applescript", "application", "appref-ms", "appx", "appxbundle", "arj", "asd", "asx", "bas", "bat", "bgi", "bz2", "cab", "chm", "cmd", "com", "cpl", "crt", "cs", "csh", "daa", "dbf", "dcr", "deb", "desktopthemepackfile", "dex", "diagcab", "dif", "dir", "dll", "dmg", "doc", "docm", "dot", "dotm", "elf", "eml", "exe", "fxp", "gadget", "gz", "hlp", "hta", "htc", "htm", "htm", "html", "html", "hwpx", "ics", "img", "inf", "ins", "iqy", "iso", "isp", "jar", "jnlp", "js", "jse", "kext", "ksh", "lha", "lib", "library-ms", "lnk", "lzh", "macho", "mam", "mda", "mdb", "mde", "mdt", "mdw", "mdz", "mht", "mhtml", "mof", "msc", "msi", "msix", "msp", "msrcincident", "mst", "ocx", "odt", "ops", "oxps", "pcd", "pif", "plg", "pot", "potm", "ppa", "ppam", "ppkg", "pps", "ppsm", "ppt", "pptm", "prf", "prg", "ps1", "ps11", "ps11xml", "ps1xml", "ps2", "ps2xml", "psc1", "psc2", "pub", "py", "pyc", "pyo", "pyw", "pyz", "pyzw", "rar", "reg", "rev", "rtf", "scf", "scpt", "scr", "sct", "searchConnector-ms", "service", "settingcontent-ms", "sh", "shb", "shs", "shtm", "shtml", "sldm", "slk", "so", "spl", "stm", "svg", "swf", "sys", "tar", "theme", "themepack", "timer", "uif", "url", "uue", "vb", "vbe", "vbs", "vhd", "vhdx", "vxd", "wbk", "website", "wim", "wiz", "ws", "wsc", "wsf", "wsh", "xla", "xlam", "xlc", "xll", "xlm", "xls", "xlsb", "xlsm", "xlt", "xltm", "xlw", "xml", "xnk", "xps", "xsl", "xz", "z" ) $MissingCount = 0 $ExtensionPolicies = $null @@ -225,7 +215,7 @@ def ruby_to_powershell(dictionary) } describe "Ensure the following malware policy (#{_policy['Identity']})" do - subject { powershell(ensure_comprehensive_attachment_filtering_applied_script).stdout.strip } + subject { pwsh_single_session_executor(ensure_comprehensive_attachment_filtering_applied_script).run_script_in_graph_exchange.stdout ||= '' } it 'should cover and contain all malware extensions' do expect(subject).to include 'PASS: Policy contains all extensions' end diff --git a/controls/microsoft-365-foundations-2.1.2.rb b/controls/microsoft-365-foundations-2.1.2.rb index c28d840..c543e7a 100644 --- a/controls/microsoft-365-foundations-2.1.2.rb +++ b/controls/microsoft-365-foundations-2.1.2.rb @@ -40,18 +40,13 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/exchange/get-malwarefilterpolicy?view=exchange-ps' ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/anti-malware-policies-configure?view=o365-worldwide' - ensure_common_attachment_types_filter_enabled_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ensure_common_attachment_types_filter_enabled_script = %( Get-MalwareFilterPolicy -Identity Default | Select-Object -ExpandProperty EnableFileFilter - } + ) + + powershell_output = pwsh_single_session_executor(ensure_common_attachment_types_filter_enabled_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 - powershell_output = powershell(ensure_common_attachment_types_filter_enabled_script) describe 'Ensure the EnableFileFilter option from Get-MalwareFilterPolicy' do subject { powershell_output.stdout.strip } it 'is set to True' do diff --git a/controls/microsoft-365-foundations-2.1.3.rb b/controls/microsoft-365-foundations-2.1.3.rb index c623214..b6d3f1e 100644 --- a/controls/microsoft-365-foundations-2.1.3.rb +++ b/controls/microsoft-365-foundations-2.1.3.rb @@ -45,39 +45,31 @@ tag nist: ['IR-1', 'IR-8', 'RA-5', 'AU-1', 'AU-2'] ensure_notifications_for_internal_users_sending_malware_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - Get-MalwareFilterPolicy | Select-Object Identity, EnableInternalSenderAdminNotifications, InternalSenderAdminAddress | ConvertTo-Json - } + $policies = Get-MalwareFilterPolicy | Select-Object Identity, EnableInternalSenderAdminNotifications, InternalSenderAdminAddress - powershell_output = powershell(ensure_notifications_for_internal_users_sending_malware_script).stdout.strip - powershell_data = JSON.parse(powershell_output) unless powershell_output.empty? - case powershell_data - when Hash - describe "Ensure the following policy (#{powershell_data['Identity']})" do - it 'should have EnableInternalSenderAdminNotifications set to true' do - expect(powershell_data['EnableInternalSenderAdminNotifications']).to eq(true) - end + foreach ($policy in $policies) { + $failedConditions = @() - it 'should have a non-empty InternalSenderAdminAddress' do - expect(powershell_data['InternalSenderAdminAddress']).not_to be_empty - end - end - when Array - powershell_data.each do |policy| - describe %(Ensure the following policy (#{policy['Identity']})) do - it 'should have EnableInternalSenderAdminNotifications set to true' do - expect(policy['EnableInternalSenderAdminNotifications']).to eq(true) - end - it 'should have a non-empty InternalSenderAdminAddress' do - expect(policy['InternalSenderAdminAddress']).not_to be_empty - end - end + if ($policy.EnableInternalSenderAdminNotifications -eq $false) { + $failedConditions += "EnableInternalSenderAdminNotifications" + } + if ([string]::IsNullOrEmpty($policy.InternalSenderAdminAddress)) { + $failedConditions += "InternalSenderAdminAddress" + } + + if ($failedConditions.Count -gt 0) { + Write-Output "Policy Name: $($policy.Identity), Failed Conditions = [$($failedConditions -join ', ')]" + } + } + } + powershell_output = pwsh_single_session_executor(ensure_notifications_for_internal_users_sending_malware_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + describe 'Ensure the number of policies that have the settings EnableInternalSenderAdminNotifications as True and InternalSenderAdminAddress should not be empty' do + subject { powershell_output.stdout.strip } + it 'is 0' do + failure_message = "The following policies have failed along with conditions they have failed on: #{powershell_output.stdout.strip.split("\n").join(',')}" + expect(subject).to be_empty, failure_message end end end diff --git a/controls/microsoft-365-foundations-2.1.4.rb b/controls/microsoft-365-foundations-2.1.4.rb index 9c9ec6b..1d665b7 100644 --- a/controls/microsoft-365-foundations-2.1.4.rb +++ b/controls/microsoft-365-foundations-2.1.4.rb @@ -44,18 +44,13 @@ ] tag nist: ['SI-3', 'SI-8', 'AU-1', 'AU-2'] - ensure_safe_attachments_policy_enabled_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ensure_safe_attachments_policy_enabled_script = %( Get-SafeAttachmentPolicy | where-object {$_.Enable -eq "True"} - } + ) + + powershell_output = pwsh_single_session_executor(ensure_safe_attachments_policy_enabled_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 - powershell_output = powershell(ensure_safe_attachments_policy_enabled_script) describe 'Ensure that there is at least one Safe Attachment policy with an Enabled state that' do subject { powershell_output.stdout.strip } it 'is set to True' do diff --git a/controls/microsoft-365-foundations-2.1.5.rb b/controls/microsoft-365-foundations-2.1.5.rb index 3f557fa..bf06e0f 100644 --- a/controls/microsoft-365-foundations-2.1.5.rb +++ b/controls/microsoft-365-foundations-2.1.5.rb @@ -47,46 +47,36 @@ { '7' => ['8.1'] } ] tag nist: ['SI-3', 'SI-8', 'AU-1', 'AU-2'] - ensure_safe_attachments_for_msproducts_enabled_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - Get-AtpPolicyForO365 | Select-Object Name, EnableATPForSPOTeamsODB, EnableSafeDocs, AllowSafeDocsOpen | ConvertTo-Json + $policies = Get-AtpPolicyForO365 | Select-Object Name, EnableATPForSPOTeamsODB, EnableSafeDocs, AllowSafeDocsOpen + + foreach ($policy in $policies) { + $failedConditions = @() + + if ($policy.EnableATPForSPOTeamsODB -eq $false) { + $failedConditions += "EnableATPForSPOTeamsODB" + } + if ($policy.EnableSafeDocs -eq $false) { + $failedConditions += "EnableSafeDocs" + } + if ($policy.AllowSafeDocsOpen -eq $true) { + $failedConditions += "AllowSafeDocsOpen" + } + + if ($failedConditions.Count -gt 0) { + Write-Output "Policy Name: $($policy.Name), Failed Conditions = [$($failedConditions -join ', ')]" + } + } } - powershell_output = powershell(ensure_safe_attachments_for_msproducts_enabled_script) - powershell_data = JSON.parse(powershell_output.stdout.strip) unless powershell_output.stdout.strip.empty? - case powershell_data - when Hash - describe "Ensure the following Safe Attachment Policy (#{powershell_data['Name']})" do - it 'should have EnableATPForSPOTeamsODB set to True' do - expect(powershell_data['EnableATPForSPOTeamsODB']).to eq(true) - end - it 'should have EnableSafeDocs set to True' do - expect(powershell_data['EnableSafeDocs']).to eq(true) - end - it 'should have AllowSafeDocsOpen set to False' do - expect(powershell_data['AllowSafeDocsOpen']).to eq(false) - end - end - when Array - powershell_data.each do |policy| - describe %(Ensure the Safe Attachment Policy #{policy['Name']}) do - it 'should have EnableATPForSPOTeamsODB set to True' do - expect(policy['EnableATPForSPOTeamsODB']).to eq(true) - end - it 'should have EnableSafeDocs set to True' do - expect(policy['EnableSafeDocs']).to eq(true) - end - it 'should have AllowSafeDocsOpen set to False' do - expect(policy['AllowSafeDocsOpen']).to eq(false) - end - end + powershell_output = pwsh_single_session_executor(ensure_safe_attachments_for_msproducts_enabled_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + describe 'Ensure the number of Safe Attachment Policies that have the settings EnableATPForSPOTeamsODB as False, EnableSafeDocs as False, or AllowSafeDocsOpen as True' do + subject { powershell_output.stdout ||= '' } + it 'is 0' do + failure_message = "The following policies have failed along with conditions they have failed on: #{powershell_output.stdout.strip.split("\n").join(',')}" + expect(subject).to be_empty, failure_message end end end diff --git a/controls/microsoft-365-foundations-2.1.6.rb b/controls/microsoft-365-foundations-2.1.6.rb index d8abbd5..96c0993 100644 --- a/controls/microsoft-365-foundations-2.1.6.rb +++ b/controls/microsoft-365-foundations-2.1.6.rb @@ -49,50 +49,42 @@ { '7' => ['7.10'] } ] tag nist: ['IR-1', 'IR-8'] - + notify_outbound_spam_recipients_list = %("#{input('notify_outbound_spam_recipients').sort.join('", "')}") + bcc_suspicious_outbound_additional_recipients_list = %("#{input('bcc_suspicious_outbound_additional_recipients').sort.join('", "')}") ensure_exchange_online_spam_policies_set_to_notify_admins_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - Get-HostedOutboundSpamFilterPolicy | Select-Object Name, Bcc*, Notify* | ConvertTo-Json + $notify_outbound_spam_recipients_list = @(#{notify_outbound_spam_recipients_list}) + $bcc_suspicious_outbound_additional_recipients_list = @(#{bcc_suspicious_outbound_additional_recipients_list}) + $policies = Get-HostedOutboundSpamFilterPolicy | Select-Object Name, BccSuspiciousOutboundMail, NotifyOutboundSpam, NotifyOutboundSpamRecipients, BccSuspiciousOutboundAdditionalRecipients + foreach ($policy in $policies) { + $failedConditions = @() + + if ($policy.BccSuspiciousOutboundMail -eq $false) { + $failedConditions += "BccSuspiciousOutboundMail" + } + if ($policy.NotifyOutboundSpam -eq $false) { + $failedConditions += "NotifyOutboundSpam" + } + if (($notify_outbound_spam_recipients_list | Sort-Object ) -ne ($policy.NotifyOutboundSpamRecipients.ToArray() | Sort-Object)) { + $failedConditions += "NotifyOutboundSpamRecipients" + } + if (($bcc_suspicious_outbound_additional_recipients_list | Sort-Object ) -ne ($policy.BccSuspiciousOutboundAdditionalRecipients.ToArray() | Sort-Object)) { + $failedConditions += "BccSuspiciousOutboundAdditionalRecipients" + } + + if ($failedConditions.Count -gt 0) { + Write-Output "Policy Name: $($policy.Name), Failed Conditions = [$($failedConditions -join ', ')]" + } + } } - powershell_output = powershell(ensure_exchange_online_spam_policies_set_to_notify_admins_script).stdout.strip - powershell_data = JSON.parse(powershell_output) unless powershell_output.empty? - case powershell_data - when Hash - describe "Ensure the following Exchange Online Spam Policy (#{powershell_data['Name']})" do - it 'should have BccSuspiciousOutboundMail set to True' do - expect(powershell_data['BccSuspiciousOutboundMail']).to eq(true) - end - it 'should have NotifyOutboundSpam set to True' do - expect(powershell_data['NotifyOutboundSpam']).to eq(true) - end - it 'should have NotifyOutboundSpamRecipients set to correct email address' do - expect(powershell_data['NotifyOutboundSpamRecipients']).to eq(input('notify_outbound_spam_recipients')) - end - it 'should have BccSuspiciousOutboundAdditionalRecipients set to correct email address' do - expect(powershell_data['BccSuspiciousOutboundAdditionalRecipients']).to eq(input('bcc_suspicious_outbound_additional_recipients')) - end - end - when Array - powershell_data.each do |policy| - describe %(Ensure the following Exchange Online Spam Policy #{policy['Name']}) do - it 'should have BccSuspiciousOutboundMail set to True' do - expect(policy['BccSuspiciousOutboundMail']).to eq(true) - end - it 'should have NotifyOutboundSpam set to True' do - expect(policy['NotifyOutboundSpam']).to eq(true) - end - it 'should have NotifyOutboundSpamRecipients set to correct email address' do - expect(policy['NotifyOutboundSpamRecipients'].sort).to match_array(input('notify_outbound_spam_recipients').sort) - end - it 'should have BccSuspiciousOutboundAdditionalRecipients set to correct email address' do - expect(policy['BccSuspiciousOutboundAdditionalRecipients'].sort).to match_array(input('bcc_suspicious_outbound_additional_recipients').sort) - end - end + + powershell_output = pwsh_single_session_executor(ensure_exchange_online_spam_policies_set_to_notify_admins_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + describe 'Ensure the number of Exchange Online Spam Policies that have the settings BccSuspiciousOutboundMail as False, NotifyOutboundSpam as False, NotifyOutboundSpamRecipients set to an incorrect email address, or BccSuspiciousOutboundAdditionalRecipients set to an incorrect email addresses' do + subject { powershell_output.stdout.strip } + it 'is 0' do + failure_message = "The following Exchange Online Spam Policies have failed along with conditions they have failed on: #{powershell_output.stdout.strip.split("\n").join(',')}" + expect(subject).to be_empty, failure_message end end end diff --git a/controls/microsoft-365-foundations-2.1.7.rb b/controls/microsoft-365-foundations-2.1.7.rb index 0c5d20a..6a73b50 100644 --- a/controls/microsoft-365-foundations-2.1.7.rb +++ b/controls/microsoft-365-foundations-2.1.7.rb @@ -50,54 +50,40 @@ tag nist: ['SI-3', 'SI-8'] ensure_anti_phishing_policy_created_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - Get-AntiPhishPolicy | Select-Object Name, Enabled, PhishThresholdLevel, EnableMailboxIntelligenceProtection, EnableMailboxIntelligence, EnableSpoofIntelligence | ConvertTo-Json + $policies = Get-AntiPhishPolicy | Select-Object Name, Enabled, PhishThresholdLevel, EnableMailboxIntelligenceProtection, EnableMailboxIntelligence, EnableSpoofIntelligence + + foreach ($policy in $policies) { + $failedConditions = @() + + if ($policy.Enabled -eq $false) { + $failedConditions += "Enabled" + } + if ($policy.PhishThresholdLevel -lt 2) { + $failedConditions += "PhishThresholdLevel" + } + if ($policy.EnableMailboxIntelligenceProtection -eq $false) { + $failedConditions += "EnableMailboxIntelligenceProtection" + } + if ($policy.EnableMailboxIntelligence -eq $false) { + $failedConditions += "EnableMailboxIntelligence" + } + if ($policy.EnableSpoofIntelligence -eq $false) { + $failedConditions += "EnableSpoofIntelligence" + } + + if ($failedConditions.Count -gt 0) { + Write-Output "Policy Name: $($policy.Name), Failed Conditions = [$($failedConditions -join ', ')]" + } + } } - powershell_output = powershell(ensure_anti_phishing_policy_created_script).stdout.strip - powershell_data = JSON.parse(powershell_output) unless powershell_output.empty? - case powershell_data - when Hash - describe "Ensure the following Exchange Anti-Fishing Policy (#{powershell_data['Name']})" do - it 'should have Enabled state set to True' do - expect(powershell_data['Enabled']).to eq(true) - end - it 'should have PhishThresholdLevel at least 2' do - expect(powershell_data['PhishThresholdLevel']).to be >= 2 - end - it 'should have EnableMailboxIntelligenceProtection state set to True' do - expect(powershell_data['EnableMailboxIntelligenceProtection']).to eq(true) - end - it 'should have EnableMailboxIntelligence state set to True' do - expect(powershell_data['EnableMailboxIntelligence']).to eq(true) - end - it 'should have EnableSpoofIntelligence state set to True' do - expect(powershell_data['EnableSpoofIntelligence']).to eq(true) - end - end - when Array - powershell_output.each do |policy| - describe %(Ensure the following Exchange Anti-Fishing Policy #{policy['Name']}) do - it 'should have Enabled state set to True' do - expect(policy['Enabled']).to eq(true) - end - it 'should have PhishThresholdLevel at least 2' do - expect(policy['PhishThresholdLevel']).to be >= 2 - end - it 'should have EnableMailboxIntelligenceProtection state set to True' do - expect(policy['EnableMailboxIntelligenceProtection']).to eq(true) - end - it 'should have EnableMailboxIntelligence state set to True' do - expect(policy['EnableMailboxIntelligence']).to eq(true) - end - it 'should have EnableSpoofIntelligence state set to True' do - expect(policy['EnableSpoofIntelligence']).to eq(true) - end - end + powershell_output = pwsh_single_session_executor(ensure_anti_phishing_policy_created_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + describe 'Ensure the number of anti-phishing policies that have the settings Enabled as False, PhishThresholdLevel < 2, EnableMailboxIntelligenceProtection as False, EnableMailboxIntelligence as False, or EnableSpoofIntelligence as False' do + subject { powershell_output.stdout.strip } + it 'is 0' do + failure_message = "The following anti-phishing policies have failed along with the conditions they have failed on: #{powershell_output.stdout.strip.split("\n").join(',')}" + expect(subject).to be_empty, failure_message end end end diff --git a/controls/microsoft-365-foundations-2.1.8.rb b/controls/microsoft-365-foundations-2.1.8.rb index 32b0853..de846a1 100644 --- a/controls/microsoft-365-foundations-2.1.8.rb +++ b/controls/microsoft-365-foundations-2.1.8.rb @@ -30,27 +30,36 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/email-authentication-spf-configure?view=o365-worldwide' # This does not work on Mac - need to find a different way to test. Additionally, CIS Benchmark says manual - domain_list = input('spf_domains') - domain_list.each do |domain| - resolve_domain_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - Import-Module DNSClient - Resolve-DnsName #{domain} txt | fl + domain_list = %("#{input('spf_domains').sort.join('", "')}") + resolve_domain_script = %{ + $domain_list = @(#{domain_list}) + try{ + Import-Module DNSClient -ErrorAction Stop + } + catch{ + } + try{ + foreach ($domain in $domain_list) { + $txtRecords = Resolve-DnsName $domain -Type TXT | Select-Object -ExpandProperty Strings + + if ($txtRecords -and $txtRecords -contains "v=spf1 include:spf.protection.outlook.com") { + Write-Output "Domain: $domain - SPF record is correct." + } else { + Write-Output "Domain: $domain - SPF record is missing or incorrect." + } + } + } + catch{ + } } - describe "Ensure the following domain (#{domain})" do - subject { powershell(resolve_domain_script).stdout.strip } - it 'should exist' do - expect(subject).not_to be_empty - end - it 'should contain the following string: v=spf1 include:spf.protection.outlook.com' do - expect(subject).to include 'v=spf1 include:spf.protection.outlook.com' - end + powershell_output = pwsh_single_session_executor(resolve_domain_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + describe 'Ensure the number of Exchange domains that do not contain a SPF record or contain v=spf1 include:spf.protection.outlook.com in the SPF record' do + subject { powershell_output.stdout.strip } + it 'is 0' do + failure_message = "The following Exchange domains have failed: #{powershell_output.stdout.strip.split("\n").join(',')}" + expect(subject).to be_empty, failure_message end end end diff --git a/controls/microsoft-365-foundations-2.1.9.rb b/controls/microsoft-365-foundations-2.1.9.rb index c13f27c..77da6eb 100644 --- a/controls/microsoft-365-foundations-2.1.9.rb +++ b/controls/microsoft-365-foundations-2.1.9.rb @@ -54,17 +54,12 @@ ref 'https://learn.microsoft.com/en-us/defender-office-365/email-authentication-dkim-configure?view=o365-worldwide' - ensure_dkim_enabled_for_exchange_domains_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ensure_dkim_enabled_for_exchange_domains_script = %( Get-DkimSigningConfig | Where-Object { $_.Enabled -eq $false } | Measure-Object | Select-Object -ExpandProperty Count - } - powershell_output = powershell(ensure_dkim_enabled_for_exchange_domains_script) + ) + powershell_output = pwsh_single_session_executor(ensure_dkim_enabled_for_exchange_domains_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the count of Exchange Online Domains with the DKIM Enabled setting set to False' do subject { powershell_output.stdout.strip } it 'is 0' do diff --git a/controls/microsoft-365-foundations-2.4.4.rb b/controls/microsoft-365-foundations-2.4.4.rb index 2bfae38..ce378a1 100644 --- a/controls/microsoft-365-foundations-2.4.4.rb +++ b/controls/microsoft-365-foundations-2.4.4.rb @@ -40,24 +40,11 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/zero-hour-auto-purge?view=o365-worldwide#zero-hour-auto-purge-zap-in-microsoft-teams' ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/mdo-support-teams-about?view=o365-worldwide#configure-zap-for-teams-protection-in-defender-for-office-365-plan-2' - ensure_zap_enabled_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ensure_zap_enabled_script = %( $zapEnabledValue = Get-TeamsProtectionPolicy | Select-Object -ExpandProperty ZapEnabled Write-Host $zapEnabledValue - } - check_exclusions_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ) + check_exclusions_script = %( $zapEnabledValue = Get-TeamsProtectionPolicy | Select-Object -ExpandProperty ZapEnabled $rules = Get-TeamsProtectionPolicyRule $filteredRules = $rules | ForEach-Object { @@ -66,9 +53,11 @@ $exceptIfDataString } $filteredRules -} +) + + powershell_output_zap = pwsh_single_session_executor(ensure_zap_enabled_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_zap.stderr}" if powershell_output_zap.exit_status != 0 - powershell_output_zap = powershell(ensure_zap_enabled_script) describe 'Ensure the ZapEnabled option for Default Sharing Policy' do subject { powershell_output_zap.stdout.strip } it 'is set to True' do @@ -76,9 +65,11 @@ end end - powershell_output_exclusions = powershell(check_exclusions_script) + powershell_output_exclusions = pwsh_single_session_executor(check_exclusions_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_exclusions.stderr}" if powershell_output_exclusions.exit_status != 0 + describe 'Ensure that the list of exclusions' do - subject { powershell_output_exclusions.stdout.strip } + subject { powershell_output_exclusions.stdout ||= '' } it 'is empty. In case of failure, a manual review is required to check the justification of each present exclusion.' do expect(subject).to be_empty end diff --git a/controls/microsoft-365-foundations-3.1.1.rb b/controls/microsoft-365-foundations-3.1.1.rb index e695585..e554b54 100644 --- a/controls/microsoft-365-foundations-3.1.1.rb +++ b/controls/microsoft-365-foundations-3.1.1.rb @@ -39,17 +39,12 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/exchange/set-adminauditlogconfig?view=exchange-ps' ensure_m365_audit_log_enabled_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false (Get-AdminAuditLogConfig | Select-Object -ExpandProperty UnifiedAuditLogIngestionEnabled) } - powershell_output = powershell(ensure_m365_audit_log_enabled_script) + powershell_output = pwsh_single_session_executor(ensure_m365_audit_log_enabled_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the UnifiedAuditLogIngestionEnabled option for audit logs' do subject { powershell_output.stdout.strip } it 'is set to True' do diff --git a/controls/microsoft-365-foundations-3.2.2.rb b/controls/microsoft-365-foundations-3.2.2.rb index 455a41a..a5d88b4 100644 --- a/controls/microsoft-365-foundations-3.2.2.rb +++ b/controls/microsoft-365-foundations-3.2.2.rb @@ -54,54 +54,38 @@ ref 'https://learn.microsoft.com/en-us/purview/dlp-teams-default-policy?view=o365-worldwide%2F1000' ref 'https://learn.microsoft.com/en-us/powershell/module/exchange/connect-ippssession?view=exchange-ps' + permitted_exceptions_list = %("#{input('permitted_exceptions_teams_locations').sort.join('", "')}") ensure_dlp_policies_enabled_teams_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-IPPSSession -AppID $client_id -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -Organization $organization -ShowBanner:$false + $permitted_exceptions_list = @(#{permitted_exceptions_list}) $DlpPolicy = Get-DlpCompliancePolicy - $DlpPolicy | Where-Object {$_.Workload -match "Teams"} | Select-Object Name, Mode, TeamsLocation, TeamsLocationException | ConvertTo-Json + $filteredPolicies = $DlpPolicy | Where-Object { $_.Workload -match "Teams" } + + foreach ($policy in $filteredPolicies) { + $failedConditions = @() + if (-not $policy.Mode.Contains('Enable')) { + $failedConditions += "Mode is not set to Enable" + } + if (-not $policy.TeamsLocation.Contains('All')) { + $failedConditions += "TeamsLocation does not include All" + } + $exceptions = $policy.TeamsLocationException + if (($permitted_exceptions_list | Sort-Object) -ne ($exceptions.ToArray() | Sort-Object)) { + $failedConditions += "TeamsLocationException has unpermitted exceptions" + } + # Print the policy name and failed conditions if any + if ($failedConditions.Count -gt 0) { + Write-Output "Policy name: $($policy.Name), Failed Conditions = [$($failedConditions -join ', ')]" + } + } } + powershell_output = pwsh_single_session_executor(ensure_dlp_policies_enabled_teams_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 - powershell_output = powershell(ensure_dlp_policies_enabled_teams_script).stdout.strip - powershell_data = JSON.parse(powershell_output) unless powershell_output.empty? - case powershell_data - when Hash - describe "Ensure the following DLP policy (#{powershell_data['Name']})" do - it 'should have its Mode state set to Enable' do - expect(powershell_data['Mode']).to eq('Enable') - end - it %(should have its TeamsLocation state include 'All') do - expect(powershell_data['TeamsLocation'][0]['DisplayName']).to include('All') - end - it 'should have its TeamsLocationException state to be empty or include only permitted exceptions' do - permitted_exceptions = input('permitted_exceptions_teams_locations') - actual_exceptions = powershell_data['TeamsLocationException'] - expect(actual_exceptions.empty? || - (actual_exceptions - permitted_exceptions).empty? || - actual_exceptions.sort == permitted_exceptions.sort).to eq(true) - end - end - when Array - powershell_output.each do |policy| - describe %(Ensure the following DLP policy (#{policy['Identity']})) do - it 'should have its Mode state set to Enable' do - expect(policy['Mode']).to eq('Enable') - end - it %(should have its TeamsLocation state include 'All') do - expect(policy['TeamsLocation'][0]['DisplayName']).to include('All') - end - it 'should have its TeamsLocationException state to be empty or include only permitted exceptions' do - permitted_exceptions = input('permitted_exceptions_teams_locations') - actual_exceptions = policy['TeamsLocationException'] - expect(actual_exceptions.empty? || - (actual_exceptions - permitted_exceptions).empty? || - actual_exceptions.sort == permitted_exceptions.sort).to eq(true) - end - end + describe 'Ensure the number of Teams DLP Policies that have the settings Mode not set to Enable, TeamsLocation not set to All, or TeamsLocationException not including permitted exceptions' do + subject { powershell_output.stdout.strip } + it 'is 0' do + failure_message = "The following Teams DLP Policies have failed along with conditions they have failed on: #{powershell_output.stdout.strip.split("\n").join(',')}" + expect(subject).to be_empty, failure_message end end end diff --git a/controls/microsoft-365-foundations-5.1.1.1.rb b/controls/microsoft-365-foundations-5.1.1.1.rb index 6b92d05..7b2b6e2 100644 --- a/controls/microsoft-365-foundations-5.1.1.1.rb +++ b/controls/microsoft-365-foundations-5.1.1.1.rb @@ -51,7 +51,17 @@ ref 'https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/concept-fundamentals-security-defaults' ref 'https://techcommunity.microsoft.com/t5/azure-active-directory-identity/introducing-security-defaults/ba-p/1061414' - describe 'manual' do - skip 'The test for this control needs to be done manually' + ensure_security_defaults_disabled_script = %{ + Write-Output (Get-MgPolicyIdentitySecurityDefaultEnforcementPolicy).IsEnabled + } + + powershell_output = pwsh_single_session_executor(ensure_security_defaults_disabled_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + describe 'Ensure security defaults option MgPolicyIdentitySecurityDefaultEnforcementPolicy on Azure Active Directory' do + subject { powershell_output.stdout.strip } + it 'is disabled' do + expect(subject).to eq('False') + end end end diff --git a/controls/microsoft-365-foundations-5.1.2.2.rb b/controls/microsoft-365-foundations-5.1.2.2.rb index 23a7780..8d7d5f1 100644 --- a/controls/microsoft-365-foundations-5.1.2.2.rb +++ b/controls/microsoft-365-foundations-5.1.2.2.rb @@ -40,19 +40,13 @@ ref 'https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-how-applications-are-added' ensure_third_party_apps_not_allowed_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - Install-Module -Name Microsoft.Graph -Force -AllowClobber - import-module microsoft.graph - $password = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force - $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential($client_id,$password) - Connect-MgGraph -TenantId $tenantid -ClientSecretCredential $ClientSecretCredential -NoWelcome $thirdPartyAllowance = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions Write-Output $thirdPartyAllowance.AllowedToCreateApps } - powershell_output = powershell(ensure_third_party_apps_not_allowed_script) + powershell_output = pwsh_single_session_executor(ensure_third_party_apps_not_allowed_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure DefaultUserRolePermissions.AllowedToCreateApps' do subject { powershell_output.stdout.strip } it 'is set to false' do diff --git a/controls/microsoft-365-foundations-5.1.2.3.rb b/controls/microsoft-365-foundations-5.1.2.3.rb index 53e4deb..cd5a40c 100644 --- a/controls/microsoft-365-foundations-5.1.2.3.rb +++ b/controls/microsoft-365-foundations-5.1.2.3.rb @@ -41,19 +41,13 @@ ref 'https://learn.microsoft.com/en-us/azure/active-directory/fundamentals/users-default-permissions#restrict-member-users-default-permissions' ensure_nonadmins_cant_make_tenants_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - Install-Module -Name Microsoft.Graph -Force -AllowClobber - import-module microsoft.graph - $password = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force - $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential($client_id,$password) - Connect-MgGraph -TenantId $tenantid -ClientSecretCredential $ClientSecretCredential -NoWelcome $allowedToCreateTenants = (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object -ExpandProperty AllowedToCreateTenants Write-Output $allowedToCreateTenants.toString() } - powershell_output = powershell(ensure_nonadmins_cant_make_tenants_script) + powershell_output = pwsh_single_session_executor(ensure_nonadmins_cant_make_tenants_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure AllowedToCreateTenants' do subject { powershell_output.stdout.strip } it 'should not be able to create tenants for non-admins' do diff --git a/controls/microsoft-365-foundations-5.1.3.1.rb b/controls/microsoft-365-foundations-5.1.3.1.rb index 6f11bca..5ec05d8 100644 --- a/controls/microsoft-365-foundations-5.1.3.1.rb +++ b/controls/microsoft-365-foundations-5.1.3.1.rb @@ -53,21 +53,15 @@ ref 'https://learn.microsoft.com/en-us/azure/active-directory/external-identities/use-dynamic-groups' ensure_dynamic_group_for_guest_users_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - Install-Module -Name Microsoft.Graph -Force -AllowClobber - import-module microsoft.graph - $password = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force - $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential($client_id,$password) - Connect-MgGraph -TenantId $tenantid -ClientSecretCredential $ClientSecretCredential -NoWelcome $groups = Get-MgGroup | Where-Object { $_.GroupTypes -contains "DynamicMembership" -and $_.MembershipRule -notmatch '(user.userType -eq "guest")'} $groups | ft DisplayName } - powershell_output = powershell(ensure_dynamic_group_for_guest_users_script).stdout.strip.split("\n").drop(2).count + powershell_output = pwsh_single_session_executor(ensure_dynamic_group_for_guest_users_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the number of dyanmic groups without guests' do - subject { powershell_output } + subject { powershell_output.stdout.strip.split("\n").drop(2).count } it 'should be 0' do expect(subject).to eq 0 end diff --git a/controls/microsoft-365-foundations-5.1.5.2.rb b/controls/microsoft-365-foundations-5.1.5.2.rb index 6699b34..111bc5d 100644 --- a/controls/microsoft-365-foundations-5.1.5.2.rb +++ b/controls/microsoft-365-foundations-5.1.5.2.rb @@ -37,18 +37,11 @@ ref 'https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/configure-user-consent?tabs=azure-portal&pivots=portal' ensure_user_cant_access_company_data_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - Install-Module -Name Microsoft.Graph -Force -AllowClobber - import-module microsoft.graph - $password = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force - $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential($client_id,$password) - Connect-MgGraph -TenantId $tenantid -ClientSecretCredential $ClientSecretCredential -NoWelcome (Get-MgPolicyAuthorizationPolicy).DefaultUserRolePermissions | Select-Object -ExpandProperty PermissionGrantPoliciesAssigned } - powershell_output = powershell(ensure_user_cant_access_company_data_script) + powershell_output = pwsh_single_session_executor(ensure_user_cant_access_company_data_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 describe.one do describe powershell_output do diff --git a/controls/microsoft-365-foundations-5.1.8.1.rb b/controls/microsoft-365-foundations-5.1.8.1.rb index c93ebd3..4a313b2 100644 --- a/controls/microsoft-365-foundations-5.1.8.1.rb +++ b/controls/microsoft-365-foundations-5.1.8.1.rb @@ -54,21 +54,15 @@ ref 'https://www.microsoft.com/en-us/download/details.aspx?id=47594' ensure_password_hash_enabled_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - Install-Module -Name Microsoft.Graph -Force -AllowClobber - import-module microsoft.graph - $password = ConvertTo-SecureString -String $clientSecret -AsPlainText -Force - $ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential($client_id,$password) - Connect-MgGraph -TenantId $tenantid -ClientSecretCredential $ClientSecretCredential -NoWelcome $onPremisesSyncEnabled = (Get-MgOrganization).OnPremisesSyncEnabled Write-Output $onPremisesSyncEnabled } - powershell_output = powershell(ensure_password_hash_enabled_script) + powershell_output = pwsh_single_session_executor(ensure_password_hash_enabled_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure OnPremisesSyncEnabled count' do - subject { powershell_output.stdout.strip } + subject { powershell_output.stdout ||= '' } it 'should not be empty' do expect(subject).not_to be_empty end diff --git a/controls/microsoft-365-foundations-5.2.2.3.rb b/controls/microsoft-365-foundations-5.2.2.3.rb index 038e863..e2fff2d 100644 --- a/controls/microsoft-365-foundations-5.2.2.3.rb +++ b/controls/microsoft-365-foundations-5.2.2.3.rb @@ -80,20 +80,16 @@ ref 'https://learn.microsoft.com/en-us/exchange/mail-flow-best-practices/how-to-set-up-a-multifunction-device-or-application-to-send-email-using-microsoft-365-or-office-365' ref 'https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/deprecation-of-basic-authentication-exchange-online' - check_basic_authentication_types_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + check_basic_authentication_types_script = %( $defaultPolicy = Get-OrganizationConfig | Select-Object -ExpandProperty DefaultAuthenticationPolicy $authSettings = Get-AuthenticationPolicy $defaultPolicy | Select-Object AllowBasicAuth* $trueSettings = $authSettings.PSObject.Properties | Where-Object { $_.Value -eq $true } | Select-Object Name, Value $jsonOutput = $trueSettings | ConvertTo-Json - } - powershell_authentication_types_output = powershell(check_basic_authentication_types_script).stdout.strip + ) + powershell_authentication_types_output = pwsh_single_session_executor(check_basic_authentication_types_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_authentication_types_output.stderr}" if powershell_authentication_types_output.exit_status != 0 + + powershell_authentication_types_output = powershell_authentication_types_output.stdout.strip authentication_policy_data = JSON.parse(powershell_authentication_types_output) unless powershell_authentication_types_output.empty? describe 'Ensure there is no Conditional Access policy that' do @@ -103,20 +99,15 @@ expect(subject).to be_nil, failure_message end end - check_authentication_block_basic_auth_policy_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + check_authentication_block_basic_auth_policy_script = %( $users = Get-User -ResultSize Unlimited | Where-Object { $_.AuthenticationPolicy -ne "Block Basic Auth" } | Select-Object UserPrincipalName, AuthenticationPolicy $jsonOutput = $users | ConvertTo-Json $jsonOutput - } - powershell_block_basic_output = powershell(check_authentication_block_basic_auth_policy_script).stdout.strip + ) + powershell_block_basic_output = pwsh_single_session_executor(check_authentication_block_basic_auth_policy_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_block_basic_output.stderr}" if powershell_block_basic_output.exit_status != 0 + powershell_block_basic_output = powershell_block_basic_output.stdout.strip block_basic_policy_data = JSON.parse(powershell_block_basic_output) unless powershell_block_basic_output.empty? describe 'Ensure there is no Exchange Online User' do subject { block_basic_policy_data } diff --git a/controls/microsoft-365-foundations-5.2.3.4.rb b/controls/microsoft-365-foundations-5.2.3.4.rb index cd6038c..7f3a842 100644 --- a/controls/microsoft-365-foundations-5.2.3.4.rb +++ b/controls/microsoft-365-foundations-5.2.3.4.rb @@ -50,7 +50,17 @@ ref 'https://learn.microsoft.com/en-us/entra/identity/conditional-access/what-if-tool' ref 'https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-methods-activity' - describe 'manual' do - skip 'The test for this control needs to be done manually' + ensure_member_users_mfa_capable_script = %( + $count = Get-MgReportAuthenticationMethodUserRegistrationDetail ` -Filter "IsMfaCapable eq false and UserType eq 'Member'" | Measure-Object + Write-Output $count.Count + ) + powershell_output = pwsh_single_session_executor(ensure_member_users_mfa_capable_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + describe 'Ensure count for IsMfaCapable equals False' do + subject { powershell_output.stdout.to_i } + it 'should be 0 for all member users' do + expect(subject).to eq(0) + end end end diff --git a/controls/microsoft-365-foundations-6.1.1.rb b/controls/microsoft-365-foundations-6.1.1.rb index 50daada..c1586e5 100644 --- a/controls/microsoft-365-foundations-6.1.1.rb +++ b/controls/microsoft-365-foundations-6.1.1.rb @@ -37,17 +37,12 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/exchange/set-organizationconfig?view=exchange-ps#-auditdisabled' ensure_auditdisabled_set_false_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false (Get-OrganizationConfig).AuditDisabled } - powershell_output = powershell(ensure_auditdisabled_set_false_script) + powershell_output = pwsh_single_session_executor(ensure_auditdisabled_set_false_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the AuditDisabled state from Get-OrganizationConfig' do subject { powershell_output.stdout.strip } it 'is set to False' do diff --git a/controls/microsoft-365-foundations-6.1.2.rb b/controls/microsoft-365-foundations-6.1.2.rb index 6b58459..94c1154 100644 --- a/controls/microsoft-365-foundations-6.1.2.rb +++ b/controls/microsoft-365-foundations-6.1.2.rb @@ -87,13 +87,6 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/compliance/audit-mailboxes?view=o365-worldwide' e3_user_mailbox_auditing_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false $AdminActions = @( "ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules" ) $DelegateActions = @( "ApplyRecord", "Create", "FolderBind", "HardDelete", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules" ) $OwnerActions = @( "ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MoveToDeletedItems", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules" ) @@ -137,11 +130,13 @@ Write-Host } } - powershell_output = powershell(e3_user_mailbox_auditing_script).stdout.strip + powershell_output = pwsh_single_session_executor(e3_user_mailbox_auditing_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that Mailbox auditing for E3 users' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'returns no actions needed from auditing' do - failure_message = "The following mailboxes failed with the following issues: #{powershell_output.split("\n").join(',')}" + failure_message = "The following mailboxes failed with the following issues: #{powershell_output.stdout.strip.split("\n").join(',')}" expect(subject).to be_empty, failure_message end end diff --git a/controls/microsoft-365-foundations-6.1.3.rb b/controls/microsoft-365-foundations-6.1.3.rb index 67dfca1..6649745 100644 --- a/controls/microsoft-365-foundations-6.1.3.rb +++ b/controls/microsoft-365-foundations-6.1.3.rb @@ -84,13 +84,6 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/compliance/audit-mailboxes?view=o365-worldwide' e5_user_mailbox_auditing_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false $AdminActions = @( "ApplyRecord", "Copy", "Create", "FolderBind", "HardDelete", "MailItemsAccessed", "Move", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules" ) $DelegateActions = @( "ApplyRecord", "Create", "FolderBind", "HardDelete", "Move", "MailItemsAccessed", "MoveToDeletedItems", "SendAs", "SendOnBehalf", "SoftDelete", "Update", "UpdateFolderPermissions", "UpdateInboxRules" ) $OwnerActions = @( "ApplyRecord", "Create", "HardDelete", "MailboxLogin", "Move", "MailItemsAccessed", "MoveToDeletedItems", "Send", "SoftDelete", "Update", "UpdateCalendarDelegation", "UpdateFolderPermissions", "UpdateInboxRules" ) @@ -135,11 +128,13 @@ Write-Host } } - powershell_output = powershell(e5_user_mailbox_auditing_script).stdout.strip + powershell_output = pwsh_single_session_executor(e5_user_mailbox_auditing_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that mailbox auditing for E5 users' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'returns no actions needed from auditing' do - failure_message = "The following mailboxes failed with the following issues: #{powershell_output.split("\n").join(',')}" + failure_message = "The following mailboxes failed with the following issues: #{powershell_output.stdout.strip.split("\n").join(',')}" expect(subject).to be_empty, failure_message end end diff --git a/controls/microsoft-365-foundations-6.1.4.rb b/controls/microsoft-365-foundations-6.1.4.rb index f3627ee..c558b39 100644 --- a/controls/microsoft-365-foundations-6.1.4.rb +++ b/controls/microsoft-365-foundations-6.1.4.rb @@ -41,19 +41,15 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/exchange/get-mailboxauditbypassassociation?view=exchange-ps' - ensure_auditbybass_not_enabled_mailbox_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - $MBX = Get-MailboxAuditBypassAssociation -ResultSize unlimited + ensure_auditbybass_not_enabled_mailbox_script = %( + $MBX = Get-MailboxAuditBypassAssociation -ResultSize unlimited -WarningAction SilentlyContinue $MBX | where {$_.AuditBypassEnabled -eq $true} | Select-Object Name, AuditBypassEnabled | ConvertTo-Json - } + ) - powershell_output = powershell(ensure_auditbybass_not_enabled_mailbox_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_auditbybass_not_enabled_mailbox_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + powershell_output = powershell_output.stdout.strip mailboxes_with_true = JSON.parse(powershell_output) unless powershell_output.empty? describe 'Ensure the number of mailboxes with the AuditBypassEnabled state set to True' do subject { powershell_output } diff --git a/controls/microsoft-365-foundations-6.2.1.rb b/controls/microsoft-365-foundations-6.2.1.rb index a941922..05572b3 100644 --- a/controls/microsoft-365-foundations-6.2.1.rb +++ b/controls/microsoft-365-foundations-6.2.1.rb @@ -70,19 +70,17 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/outbound-spam-policies-external-email-forwarding?view=o365-worldwide' ref 'https://learn.microsoft.com/en-us/powershell/module/exchange/Remove-TransportRule?view=exchange-ps' - ensure_no_external_address_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ensure_no_external_address_script = %( Get-TransportRule | Where-Object { $_.RedirectMessageTo -ne $null -and $_.RedirectMessageTo -notmatch '#{input('internal_domains_transport_rule').join('|')}' } | Select-Object -ExpandProperty Name - } - powershell_output_address = powershell(ensure_no_external_address_script).stdout.strip + ) + powershell_output_address = pwsh_single_session_executor(ensure_no_external_address_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_address.stderr}" if powershell_output_address.exit_status != 0 + + powershell_output_address = powershell_output_address.stdout ||= '' + external_rules = powershell_output_address.split("\n") unless powershell_output_address.empty? describe 'Ensure only internal domains' do subject { powershell_output_address } @@ -92,18 +90,14 @@ end end - ensure_all_mail_forwarding_blocked_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ensure_all_mail_forwarding_blocked_script = %( Get-HostedOutboundSpamFilterPolicy | Where-Object { $_.AutoForwardingMode -ne "Off" } | Select-Object Name, AutoForwardingMode | ConvertTo-Json - } + ) + + powershell_output = pwsh_single_session_executor(ensure_all_mail_forwarding_blocked_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 - powershell_output = powershell(ensure_all_mail_forwarding_blocked_script).stdout.strip + powershell_output = powershell_output.stdout ||= '' mailboxes_without_off = JSON.parse(powershell_output) unless powershell_output.empty? describe 'Ensure the number of mailboxes with the AutoForwardingMode state not set to Off' do subject { powershell_output } diff --git a/controls/microsoft-365-foundations-6.2.2.rb b/controls/microsoft-365-foundations-6.2.2.rb index 4d40d76..7889e1c 100644 --- a/controls/microsoft-365-foundations-6.2.2.rb +++ b/controls/microsoft-365-foundations-6.2.2.rb @@ -38,16 +38,12 @@ ref 'https://learn.microsoft.com/en-us/exchange/security-and-compliance/mail-flow-rules/mail-flow-rules' ensure_mail_transport_rules_dont_whitelist_specific_domains_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false Get-TransportRule | Where-Object { ($_.SetScl -eq -1 -and $_.SenderDomainIs -ne $null) } | Select-Object -ExpandProperty Name } - powershell_output = powershell(ensure_mail_transport_rules_dont_whitelist_specific_domains_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_mail_transport_rules_dont_whitelist_specific_domains_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + powershell_output = powershell_output.stdout ||= '' whitelisted_domain_rules = powershell_output.split("\n") unless powershell_output.empty? describe 'Ensure the mail transport rules' do subject { powershell_output } diff --git a/controls/microsoft-365-foundations-6.2.3.rb b/controls/microsoft-365-foundations-6.2.3.rb index f31cfa5..ac38fea 100644 --- a/controls/microsoft-365-foundations-6.2.3.rb +++ b/controls/microsoft-365-foundations-6.2.3.rb @@ -36,13 +36,6 @@ permitted_emails = input('email_addresses_bypass_external_tagging') email_pattern = permitted_emails.map { |email| "'#{email}'" }.join(', ') ensure_email_from_external_senders_identified_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false $allowedEmails = @(#{email_pattern}) $object = Get-ExternalInOutlook $object | Where-Object { @@ -50,7 +43,10 @@ ($_.AllowList | ForEach-Object { $allowedEmails -contains $_ } | Where-Object { $_ -eq $false } | Measure-Object).Count -gt 0 } | Select-Object -ExpandProperty Identity } - powershell_output = powershell(ensure_email_from_external_senders_identified_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_email_from_external_senders_identified_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + powershell_output = powershell_output.stdout.strip error_identities = powershell_output.split("\n") unless powershell_output.empty? describe 'Ensure the number of identities with Enabled state as False and AllowedList with non-permitted email addresses' do subject { powershell_output } diff --git a/controls/microsoft-365-foundations-6.3.1.rb b/controls/microsoft-365-foundations-6.3.1.rb index acc10b7..94621fd 100644 --- a/controls/microsoft-365-foundations-6.3.1.rb +++ b/controls/microsoft-365-foundations-6.3.1.rb @@ -59,13 +59,6 @@ ref 'https://learn.microsoft.com/en-us/exchange/permissions-exo/role-assignment-policies' ensure_installing_outlook_addins_not_allowed_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false Get-EXOMailbox | Select-Object -Unique RoleAssignmentPolicy | ForEach-Object { @@ -80,7 +73,10 @@ } } } - powershell_output = powershell(ensure_installing_outlook_addins_not_allowed_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_installing_outlook_addins_not_allowed_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + powershell_output = powershell_output.stdout.strip error_identities = powershell_output.split("\n") unless powershell_output.empty? describe 'Ensure the number of policies that contain My Custom Apps, My Marketplace Apps, or My ReadWriteMailboxApps' do subject { powershell_output } diff --git a/controls/microsoft-365-foundations-6.5.1.rb b/controls/microsoft-365-foundations-6.5.1.rb index b826471..815d148 100644 --- a/controls/microsoft-365-foundations-6.5.1.rb +++ b/controls/microsoft-365-foundations-6.5.1.rb @@ -37,17 +37,12 @@ ref 'https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/enable-or-disable-modern-authentication-in-exchange-online' ensure_modern_authentication_for_exchange_enabled_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false (Get-OrganizationConfig).OAuth2ClientProfileEnabled } - powershell_output = powershell(ensure_modern_authentication_for_exchange_enabled_script) + powershell_output = pwsh_single_session_executor(ensure_modern_authentication_for_exchange_enabled_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the OAuth2ClientProfileEnabled state from Get-OrganizationConfig' do subject { powershell_output.stdout.strip } it 'is set to True' do diff --git a/controls/microsoft-365-foundations-6.5.2.rb b/controls/microsoft-365-foundations-6.5.2.rb index 050e280..b2966cd 100644 --- a/controls/microsoft-365-foundations-6.5.2.rb +++ b/controls/microsoft-365-foundations-6.5.2.rb @@ -28,31 +28,19 @@ ref 'https://learn.microsoft.com/en-us/exchange/clients-and-mobile-in-exchange-online/mailtips/mailtips' ref 'https://learn.microsoft.com/en-us/powershell/module/exchange/set-organizationconfig?view=exchange-ps' - ensure_mailtip_enabled_for_end_users_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false + ensure_mailtip_enabled_for_end_users_script = %( Get-OrganizationConfig | Select-Object -Property MailTips* | ConvertTo-Json - } - powershell_output = powershell(ensure_mailtip_enabled_for_end_users_script).stdout.strip + ) + powershell_output = pwsh_single_session_executor(ensure_mailtip_enabled_for_end_users_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + powershell_output = powershell_output.stdout.strip mailtips_settings = JSON.parse(powershell_output) unless powershell_output.empty? describe 'Ensure that the MailTip setting' do subject { mailtips_settings } - it 'MailTipsAllTipsEnabled should be set to True' do - expect(mailtips_settings['MailTipsAllTipsEnabled']).to eq(true) - end - it 'MailTipsExternalRecipientsTipsEnabled should be set to True' do - expect(mailtips_settings['MailTipsExternalRecipientsTipsEnabled']).to eq(true) - end - it 'MailTipsGroupMetricsEnabled should be set to True' do - expect(mailtips_settings['MailTipsGroupMetricsEnabled']).to eq(true) - end - it 'MailTipsLargeAudienceThreshold should be set to an acceptable value' do - expect(mailtips_settings['MailTipsLargeAudienceThreshold']).to eq(input('mailtipslargeaudiencethreshold_value')) - end + its(['MailTipsAllTipsEnabled']) { should cmp true } + its(['MailTipsExternalRecipientsTipsEnabled']) { should cmp true } + its(['MailTipsGroupMetricsEnabled']) { should cmp true } + its(['MailTipsLargeAudienceThreshold']) { should eq((input('mailtipslargeaudiencethreshold_value'))) } end end diff --git a/controls/microsoft-365-foundations-6.5.3.rb b/controls/microsoft-365-foundations-6.5.3.rb index ebb4bd0..875ae3c 100644 --- a/controls/microsoft-365-foundations-6.5.3.rb +++ b/controls/microsoft-365-foundations-6.5.3.rb @@ -36,17 +36,12 @@ ref 'https://support.microsoft.com/en-us/topic/3rd-party-cloud-storage-services-supported-by-office-apps-fce12782-eccc-4cf5-8f4b-d1ebec513f72' ensure_additional_storage_providers_restricted_web_outlook_script = %{ - $client_id = '#{input('client_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false (Get-OwaMailboxPolicy).AdditionalStorageProvidersAvailable } - powershell_output = powershell(ensure_additional_storage_providers_restricted_web_outlook_script) + powershell_output = pwsh_single_session_executor(ensure_additional_storage_providers_restricted_web_outlook_script).run_script_in_graph_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the AdditionalStorageProvidersAvailable option from Get-OwaMailboxPolicy' do subject { powershell_output.stdout.strip } it 'is set to False' do diff --git a/controls/microsoft-365-foundations-7.2.1.rb b/controls/microsoft-365-foundations-7.2.1.rb index 7069b3c..bcb370b 100644 --- a/controls/microsoft-365-foundations-7.2.1.rb +++ b/controls/microsoft-365-foundations-7.2.1.rb @@ -40,21 +40,13 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/sharepoint-online/set-spotenant?view=sharepoint-ps' ensure_modern_authentication_spo_applications_required_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid (Get-PnPTenant).LegacyAuthProtocolsEnabled } - powershell_output = powershell(ensure_modern_authentication_spo_applications_required_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_modern_authentication_spo_applications_required_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the LegacyAuthProtocolsEnabled option for SharePoint' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'is set to False' do expect(subject).to eq('False') end diff --git a/controls/microsoft-365-foundations-7.2.10.rb b/controls/microsoft-365-foundations-7.2.10.rb index f9d3243..b57a890 100644 --- a/controls/microsoft-365-foundations-7.2.10.rb +++ b/controls/microsoft-365-foundations-7.2.10.rb @@ -41,29 +41,18 @@ ref 'https://learn.microsoft.com/en-US/sharepoint/turn-external-sharing-on-or-off?WT.mc_id=365AdminCSH_spo#change-the-organization-level-external-sharing-setting' ref 'https://learn.microsoft.com/en-us/azure/active-directory/external-identities/one-time-passcode' - ensure_reauth_with_verification_code_restricted = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid + ensure_reauth_with_verification_code_restricted = %( Get-PnPTenant | Select-Object EmailAttestationRequired, EmailAttestationReAuthDays | ConvertTo-Json - } + ) - powershell_output = powershell(ensure_reauth_with_verification_code_restricted).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_reauth_with_verification_code_restricted).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + powershell_output = powershell_output.stdout.strip powershell_data = JSON.parse(powershell_output) unless powershell_output.empty? describe 'Ensure the following setting' do subject { powershell_data } - it 'EmailAttestationRequired in SharePoint is set to True' do - expect(subject['EmailAttestationRequired']).to eq(true) - end - it 'EmailAttestationReAuthDays in SharePoint is less than or equal to 15' do - expect(subject['EmailAttestationReAuthDays']).to be <= input('email_attestation_re_auth_days_spo_threshold') - end + its(['EmailAttestationRequired']) { should cmp true } + its(['EmailAttestationReAuthDays']) { should be <= input('email_attestation_re_auth_days_spo_threshold') } end end diff --git a/controls/microsoft-365-foundations-7.2.2.rb b/controls/microsoft-365-foundations-7.2.2.rb index d530abd..6aeb35b 100644 --- a/controls/microsoft-365-foundations-7.2.2.rb +++ b/controls/microsoft-365-foundations-7.2.2.rb @@ -33,21 +33,13 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/sharepoint-online/set-spotenant?view=sharepoint-ps' ensure_spo_od_integration_with_azure_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid (Get-PnPTenant).EnableAzureADB2BIntegration } - powershell_output = powershell(ensure_spo_od_integration_with_azure_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_spo_od_integration_with_azure_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the EnableAzureADB2BIntegration option for SharePoint' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'is set to True' do expect(subject).to eq('True') end diff --git a/controls/microsoft-365-foundations-7.2.3.rb b/controls/microsoft-365-foundations-7.2.3.rb index 1ac3852..8f97c48 100644 --- a/controls/microsoft-365-foundations-7.2.3.rb +++ b/controls/microsoft-365-foundations-7.2.3.rb @@ -52,23 +52,15 @@ 'Disabled' ] ensure_external_content_sharing_restricted_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid (Get-PnPTenant).SharingCapability } - powershell_output = powershell(ensure_external_content_sharing_restricted_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_external_content_sharing_restricted_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the SharingCapability option for SharePoint' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'is set to either ExternalUserSharingOnly, ExistingExternalUserSharingOnly, or Disabled' do - expect(acceptable_values).to include(powershell_output) + expect(acceptable_values).to include(powershell_output.stdout.strip) end end end diff --git a/controls/microsoft-365-foundations-7.2.4.rb b/controls/microsoft-365-foundations-7.2.4.rb index d9e49c7..f39914b 100644 --- a/controls/microsoft-365-foundations-7.2.4.rb +++ b/controls/microsoft-365-foundations-7.2.4.rb @@ -61,21 +61,13 @@ ref 'https://learn.microsoft.com/en-us/sharepoint/dev/embedded/concepts/app-concepts/sharing-and-perm#container-partition' ensure_od_content_sharing_restricted_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid (Get-PnPTenant).OneDriveSharingCapability } - powershell_output = powershell(ensure_od_content_sharing_restricted_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_od_content_sharing_restricted_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the OneDriveSharingCapability option for SharePoint' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'is set to Disabled' do expect(subject).to eq('Disabled') end diff --git a/controls/microsoft-365-foundations-7.2.5.rb b/controls/microsoft-365-foundations-7.2.5.rb index a5cda9e..0ecdaa2 100644 --- a/controls/microsoft-365-foundations-7.2.5.rb +++ b/controls/microsoft-365-foundations-7.2.5.rb @@ -38,21 +38,13 @@ ref 'https://learn.microsoft.com/en-us/sharepoint/external-sharing-overview' ensure_spo_guest_users_cannot_share_items_dont_own_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid (Get-PnPTenant).PreventExternalUsersFromResharing } - powershell_output = powershell(ensure_spo_guest_users_cannot_share_items_dont_own_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_spo_guest_users_cannot_share_items_dont_own_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the PreventExternalUsersFromResharing option for SharePoint' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'is set to True' do expect(subject).to eq('True') end diff --git a/controls/microsoft-365-foundations-7.2.6.rb b/controls/microsoft-365-foundations-7.2.6.rb index b6f9508..77d2d1d 100644 --- a/controls/microsoft-365-foundations-7.2.6.rb +++ b/controls/microsoft-365-foundations-7.2.6.rb @@ -41,21 +41,13 @@ tag nist: ['AC-3', 'AC-5', 'AC-6', 'MP-2', 'CA-9', 'SC-7', 'AT-2'] ensure_sharingdomainrestriction_set_to_allowlist_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid (Get-PnPTenant).SharingDomainRestrictionMode } - powershell_output_allowlist = powershell(ensure_sharingdomainrestriction_set_to_allowlist_script).stdout.strip + powershell_output_allowlist = pwsh_single_session_executor(ensure_sharingdomainrestriction_set_to_allowlist_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_allowlist.stderr}" if powershell_output_allowlist.exit_status != 0 + describe 'Ensure the SharingDomainRestrictionMode option for SharePoint' do - subject { powershell_output_allowlist } + subject { powershell_output_allowlist.stdout.strip } it 'is set to AllowList' do expect(subject).to eq('AllowList') end @@ -64,15 +56,6 @@ trusted_domains = input('domains_trusted_by_organization') domain_pattern = trusted_domains.map { |domain| "'#{domain}'" }.join(', ') ensure_trusted_domains_allowed_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid $trustedDomains = @("trusted.com", "example.com", "secure.org") $domain_data = (Get-PnPTenant).SharingAllowedDomainList $trustedDomains = @(#{domain_pattern}) @@ -82,10 +65,12 @@ Write-Output "Some domains are not in the list of trusted domains: $($untrustedDomains -join ', ')" } } - powershell_output_domains = powershell(ensure_trusted_domains_allowed_script).stdout.strip + powershell_output_domains = pwsh_single_session_executor(ensure_trusted_domains_allowed_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_domains.stderr}" if powershell_output_domains.exit_status != 0 + describe 'Ensure the number of domains not trusted by the organization outputted by SharingAllowedDomainList option on SharePoint' do - subject { powershell_output_domains } - failure_message = "Failure: #{powershell_output_domains}" + subject { powershell_output_domains.stdout.strip } + failure_message = "Failure: #{powershell_output_domains.stdout.strip}" it 'is 0' do expect(subject).to be_empty, failure_message end diff --git a/controls/microsoft-365-foundations-7.2.7.rb b/controls/microsoft-365-foundations-7.2.7.rb index 9a0caa1..d94b535 100644 --- a/controls/microsoft-365-foundations-7.2.7.rb +++ b/controls/microsoft-365-foundations-7.2.7.rb @@ -39,21 +39,13 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/sharepoint-online/set-spotenant?view=sharepoint-ps' ensure_link_sharing_restricted_spo_od_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid (Get-PnPTenant).DefaultSharingLinkType } - powershell_output = powershell(ensure_link_sharing_restricted_spo_od_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_link_sharing_restricted_spo_od_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the DefaultSharingLinkType option for SharePoint' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'is set to Direct' do expect(subject).to eq('Direct') end diff --git a/controls/microsoft-365-foundations-7.2.9.rb b/controls/microsoft-365-foundations-7.2.9.rb index 61b8992..8935fe1 100644 --- a/controls/microsoft-365-foundations-7.2.9.rb +++ b/controls/microsoft-365-foundations-7.2.9.rb @@ -44,29 +44,18 @@ ref 'https://learn.microsoft.com/en-US/sharepoint/turn-external-sharing-on-or-off?WT.mc_id=365AdminCSH_spo#change-the-organization-level-external-sharing-setting' ref 'https://learn.microsoft.com/en-us/microsoft-365/community/sharepoint-security-a-team-effort' - ensure_guest_access_to_od_will_expire_automatically_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid + ensure_guest_access_to_od_will_expire_automatically_script = %( Get-PnPTenant | Select-Object ExternalUserExpirationRequired, ExternalUserExpireInDays | ConvertTo-Json - } + ) - powershell_output = powershell(ensure_guest_access_to_od_will_expire_automatically_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_guest_access_to_od_will_expire_automatically_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + powershell_output = powershell_output.stdout.strip powershell_data = JSON.parse(powershell_output) unless powershell_output.empty? describe 'Ensure the following setting' do subject { powershell_data } - it 'ExternalUserExpirationRequired in SharePoint/OneDrive is set to True' do - expect(subject['ExternalUserExpirationRequired']).to eq(true) - end - it 'ExternalUserExpireInDays in SharePoint/OneDrive is less than or equal to 30' do - expect(subject['ExternalUserExpireInDays']).to be <= input('external_user_expiry_in_days_spo_threshold') - end + its(['ExternalUserExpirationRequired']) { should cmp true } + its(['ExternalUserExpireInDays']) { should be <= input('external_user_expiry_in_days_spo_threshold') } end end diff --git a/controls/microsoft-365-foundations-7.3.1.rb b/controls/microsoft-365-foundations-7.3.1.rb index dd51120..b9e32e2 100644 --- a/controls/microsoft-365-foundations-7.3.1.rb +++ b/controls/microsoft-365-foundations-7.3.1.rb @@ -37,22 +37,14 @@ ref 'https://learn.microsoft.com/en-us/azure/active-directory/roles/permissions-reference#global-reader' ensure_office_m365spo_infected_files_disallowed_download_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid (Get-PnPTenant).DisallowInfectedFileDownload } - powershell_output = powershell(ensure_office_m365spo_infected_files_disallowed_download_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_office_m365spo_infected_files_disallowed_download_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the DisallowInfectedFileDownload option for Office 365 SharePoint' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'is set to True' do expect(subject).to eq('True') end diff --git a/controls/microsoft-365-foundations-7.3.2.rb b/controls/microsoft-365-foundations-7.3.2.rb index 76b7a32..5e83ffc 100644 --- a/controls/microsoft-365-foundations-7.3.2.rb +++ b/controls/microsoft-365-foundations-7.3.2.rb @@ -47,21 +47,13 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/sharepoint-online/set-spotenantsyncclientrestriction?view=sharepoint-ps' tenantrestrictionenabled_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid (Get-PnPTenantSyncClientRestriction).TenantRestrictionEnabled } - powershell_output_tenant = powershell(tenantrestrictionenabled_script).stdout.strip + powershell_output_tenant = pwsh_single_session_executor(tenantrestrictionenabled_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_tenant.stderr}" if powershell_output_tenant.exit_status != 0 + describe 'Ensure the TenantRestrictionEnabled option for SharePoint/OneDrive Sync' do - subject { powershell_output_tenant } + subject { powershell_output_tenant.stdout.strip } it 'is set to True' do expect(subject).to eq('True') end @@ -70,16 +62,6 @@ trusted_domains = input('trusted_domains_guids') domain_pattern = trusted_domains.map { |domain| "'#{domain}'" }.join(', ') ensure_trusted_domains_guids = %{ - $appName = 'cisBenchmarkL512' - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid $allowedDomains = (Get-PnPTenant).AllowedDomainList $trustedDomains = @(#{domain_pattern}) $domainList = $domain_data -split ' ' @@ -88,10 +70,12 @@ Write-Output "Some domains are not in the list of trusted domains: $($untrustedDomains -join ', ')" } } - powershell_output_domains = powershell(ensure_trusted_domains_guids).stdout.strip + powershell_output_domains = pwsh_single_session_executor(ensure_trusted_domains_guids).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_domains.stderr}" if powershell_output_domains.exit_status != 0 + describe 'Ensure the number of domains GUIDs not trusted from AllowedDomainList option on SharePoint' do - subject { powershell_output_domains } - failure_message = "Failure: #{powershell_output_domains}" + subject { powershell_output_domains.stdout ||= '' } + failure_message = "Failure: #{powershell_output_domains.stdout}" it 'is 0' do expect(subject).to be_empty, failure_message end diff --git a/controls/microsoft-365-foundations-7.3.4.rb b/controls/microsoft-365-foundations-7.3.4.rb index 6213068..91a7194 100644 --- a/controls/microsoft-365-foundations-7.3.4.rb +++ b/controls/microsoft-365-foundations-7.3.4.rb @@ -42,20 +42,13 @@ ref 'https://learn.microsoft.com/en-us/sharepoint/security-considerations-of-allowing-custom-script' ref 'https://learn.microsoft.com/en-us/powershell/module/sharepoint-online/set-sposite?view=sharepoint-ps' - ensure_spo_guest_users_cannot_share_items_dont_own_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $sharepoint_admin_url = '#{input('sharepoint_admin_url')}' - Install-Module -Name PnP.PowerShell -Force -AllowClobber - import-module pnp.powershell - $password = (ConvertTo-SecureString -AsPlainText $certificate_password -Force) - Connect-PnPOnline -Url $sharepoint_admin_url -ClientId $client_id -CertificatePath $certificate_path -CertificatePassword $password -Tenant $tenantid + ensure_spo_guest_users_cannot_share_items_dont_own_script = %( Get-PnPTenantSite | Where-Object { $_.DenyAddAndCustomizePages -eq "Disabled" -and $_.Url -notlike "*-my.sharepoint.com/" } | Select-Object -ExpandProperty Url - } - powershell_output = powershell(ensure_spo_guest_users_cannot_share_items_dont_own_script).stdout.strip + ) + powershell_output = pwsh_single_session_executor(ensure_spo_guest_users_cannot_share_items_dont_own_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + + powershell_output = powershell_output.stdout.strip disabled_urls = powershell_output.split("\n") unless powershell_output.empty? describe 'Ensure the number of sites with DenyAddAndCustomizePages setting as Disabled' do subject { powershell_output } diff --git a/controls/microsoft-365-foundations-8.1.1.rb b/controls/microsoft-365-foundations-8.1.1.rb index 2293c0d..a47c880 100644 --- a/controls/microsoft-365-foundations-8.1.1.rb +++ b/controls/microsoft-365-foundations-8.1.1.rb @@ -37,12 +37,6 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/enterprise/manage-skype-for-business-online-with-microsoft-365-powershell?view=o365-worldwide' ensure_file_sharing_enabled_cloud_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null - $teamsClientConfig = Get-CsTeamsClientConfiguration | Select-Object AllowDropbox,AllowBox,AllowGoogleDrive,AllowShareFile,AllowEgnyte $allTrue = $false foreach ($property in @('AllowDropbox', 'AllowBox', 'AllowGoogleDrive', 'AllowShareFile', 'AllowEgnyte')) { @@ -58,7 +52,9 @@ } } - powershell_output = powershell(ensure_file_sharing_enabled_cloud_script) + powershell_output = pwsh_single_session_executor(ensure_file_sharing_enabled_cloud_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that all the authorized cloud storage services ' do subject { powershell_output.stdout.strip } it 'are set to true' do diff --git a/controls/microsoft-365-foundations-8.1.2.rb b/controls/microsoft-365-foundations-8.1.2.rb index 7c4ea81..bf867b2 100644 --- a/controls/microsoft-365-foundations-8.1.2.rb +++ b/controls/microsoft-365-foundations-8.1.2.rb @@ -37,16 +37,12 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/skype/set-csteamsclientconfiguration?view=skype-ps' ensure_users_cant_send_emails_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $clientSecret = '#{input('client_secret')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null Write-Output (Get-CsTeamsClientConfiguration -Identity Global).AllowEmailIntoChannel } - powershell_output = powershell(ensure_users_cant_send_emails_script) + powershell_output = pwsh_single_session_executor(ensure_users_cant_send_emails_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that AllowEmailIntoChannel state' do subject { powershell_output.stdout.strip } it 'is set to False' do diff --git a/controls/microsoft-365-foundations-8.2.1.rb b/controls/microsoft-365-foundations-8.2.1.rb index e27b73a..a785553 100644 --- a/controls/microsoft-365-foundations-8.2.1.rb +++ b/controls/microsoft-365-foundations-8.2.1.rb @@ -66,12 +66,6 @@ authorized_domains = input('authorized_domains_teams_admin_center') domain_pattern = authorized_domains.map { |domain| "'#{domain}'" }.join(', ') ensure_external_access_restricted_teams_admin_center_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null $authorizedDomains = @(#{domain_pattern}) $federationConfig = Get-CsTenantFederationConfiguration @@ -101,11 +95,13 @@ } } } - powershell_output = powershell(ensure_external_access_restricted_teams_admin_center_script).stdout.strip + powershell_output = pwsh_single_session_executor(ensure_external_access_restricted_teams_admin_center_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure the AllowTeamsConsumer, AllowPublicUsers, AllowFederatedUsers, and AllowedDomains' do - subject { powershell_output } + subject { powershell_output.stdout.strip } it 'are set to appropriate values and authorized domains present' do - failure_message = "The following failed:\n#{powershell_output}" + failure_message = "The following failed:\n#{powershell_output.stdout.strip}" expect(subject).to be_empty, failure_message end end diff --git a/controls/microsoft-365-foundations-8.5.1.rb b/controls/microsoft-365-foundations-8.5.1.rb index 1f2f253..23e7532 100644 --- a/controls/microsoft-365-foundations-8.5.1.rb +++ b/controls/microsoft-365-foundations-8.5.1.rb @@ -42,15 +42,11 @@ ref 'https://learn.microsoft.com/en-us/MicrosoftTeams/configure-meetings-sensitive-protection' ensure_anonymous_users_cant_join_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null Write-Output (Get-CsTeamsMeetingPolicy -Identity Global).AllowAnonymousUsersToJoinMeeting } - powershell_output = powershell(ensure_anonymous_users_cant_join_script) + powershell_output = pwsh_single_session_executor(ensure_anonymous_users_cant_join_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that AllowAnonymousUsersToJoinMeeting state' do subject { powershell_output.stdout.strip } it 'is set to False' do diff --git a/controls/microsoft-365-foundations-8.5.2.rb b/controls/microsoft-365-foundations-8.5.2.rb index 9ae142d..3a4cf5d 100644 --- a/controls/microsoft-365-foundations-8.5.2.rb +++ b/controls/microsoft-365-foundations-8.5.2.rb @@ -43,16 +43,12 @@ ref 'https://learn.microsoft.com/en-US/microsoftteams/who-can-bypass-meeting-lobby?WT.mc_id=TeamsAdminCenterCSH#overview-of-lobby-settings-and-policies' ensure_anonymous_users_cant_start_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null Write-Output (Get-CsTeamsMeetingPolicy -Identity Global).AllowAnonymousUsersToStartMeeting } - powershell_output = powershell(ensure_anonymous_users_cant_start_script) + powershell_output = pwsh_single_session_executor(ensure_anonymous_users_cant_start_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that AllowAnonymousUsersToStartMeeting state' do subject { powershell_output.stdout.strip } it 'is set to False' do diff --git a/controls/microsoft-365-foundations-8.5.3.rb b/controls/microsoft-365-foundations-8.5.3.rb index f28acbd..ef2cb06 100644 --- a/controls/microsoft-365-foundations-8.5.3.rb +++ b/controls/microsoft-365-foundations-8.5.3.rb @@ -40,16 +40,12 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/skype/set-csteamsmeetingpolicy?view=skype-ps' ensure_people_in_org_bypass_lobby_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null Write-Output (Get-CsTeamsMeetingPolicy -Identity Global).AutoAdmittedUsers } - powershell_output = powershell(ensure_people_in_org_bypass_lobby_script) + powershell_output = pwsh_single_session_executor(ensure_people_in_org_bypass_lobby_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that the AutoAdmittedUsers state' do subject { powershell_output.stdout.strip } it 'is set to EveryoneInCompanyExcludingGuests' do diff --git a/controls/microsoft-365-foundations-8.5.4.rb b/controls/microsoft-365-foundations-8.5.4.rb index 00a79d8..b9a3e86 100644 --- a/controls/microsoft-365-foundations-8.5.4.rb +++ b/controls/microsoft-365-foundations-8.5.4.rb @@ -38,16 +38,12 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/skype/set-csteamsmeetingpolicy?view=skype-ps' ensure_people_dialing_in_cant_bypass_lobby_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null Write-Output (Get-CsTeamsMeetingPolicy -Identity Global).AllowPSTNUsersToBypassLobby } - powershell_output = powershell(ensure_people_dialing_in_cant_bypass_lobby_script) + powershell_output = pwsh_single_session_executor(ensure_people_dialing_in_cant_bypass_lobby_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that the AllowPSTNUsersToBypassLobby state' do subject { powershell_output.stdout.strip } it 'is set to False' do diff --git a/controls/microsoft-365-foundations-8.5.5.rb b/controls/microsoft-365-foundations-8.5.5.rb index d27397a..5b3ccdf 100644 --- a/controls/microsoft-365-foundations-8.5.5.rb +++ b/controls/microsoft-365-foundations-8.5.5.rb @@ -38,16 +38,12 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/skype/set-csteamsmeetingpolicy?view=skype-ps#-meetingchatenabledtype' ensure_meeting_chat_not_allow_anon_users = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null Write-Output (Get-CsTeamsMeetingPolicy -Identity Global).MeetingChatEnabledType } - powershell_output = powershell(ensure_meeting_chat_not_allow_anon_users) + powershell_output = pwsh_single_session_executor(ensure_meeting_chat_not_allow_anon_users).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that the MeetingChatEnabledType state' do subject { powershell_output.stdout.strip } it 'is set to EnabledExceptAnonymous' do diff --git a/controls/microsoft-365-foundations-8.5.6.rb b/controls/microsoft-365-foundations-8.5.6.rb index 083b0bf..f48ff23 100644 --- a/controls/microsoft-365-foundations-8.5.6.rb +++ b/controls/microsoft-365-foundations-8.5.6.rb @@ -41,16 +41,12 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/skype/set-csteamsmeetingpolicy?view=skype-ps' ensure_organizers_only_can_present_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null Write-Output (Get-CsTeamsMeetingPolicy -Identity Global).DesignatedPresenterRoleMode } - powershell_output = powershell(ensure_organizers_only_can_present_script) + powershell_output = pwsh_single_session_executor(ensure_organizers_only_can_present_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that the DesignatedPresenterRoleMode state' do subject { powershell_output.stdout.strip } it 'is set to OrganizerOnlyUserOverride' do diff --git a/controls/microsoft-365-foundations-8.5.7.rb b/controls/microsoft-365-foundations-8.5.7.rb index 8df54cc..2a0ae37 100644 --- a/controls/microsoft-365-foundations-8.5.7.rb +++ b/controls/microsoft-365-foundations-8.5.7.rb @@ -41,16 +41,12 @@ ref 'https://learn.microsoft.com/en-us/powershell/module/skype/set-csteamsmeetingpolicy?view=skype-ps' ensure_organizers_only_can_present_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null Write-Output (Get-CsTeamsMeetingPolicy -Identity Global).AllowExternalParticipantGiveRequestControl } - powershell_output = powershell(ensure_organizers_only_can_present_script) + powershell_output = pwsh_single_session_executor(ensure_organizers_only_can_present_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that the AllowExternalParticipantGiveRequestControl state' do subject { powershell_output.stdout.strip } it 'is set to False' do diff --git a/controls/microsoft-365-foundations-8.5.8.rb b/controls/microsoft-365-foundations-8.5.8.rb index 033f0ae..63d317c 100644 --- a/controls/microsoft-365-foundations-8.5.8.rb +++ b/controls/microsoft-365-foundations-8.5.8.rb @@ -38,16 +38,12 @@ ref 'https://learn.microsoft.com/en-US/microsoftteams/settings-policies-reference?WT.mc_id=TeamsAdminCenterCSH#meeting-engagement' ensure_external_meeting_chat_off_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null Write-Output (Get-CsTeamsMeetingPolicy -Identity Global).AllowExternalNonTrustedMeetingChat } - powershell_output = powershell(ensure_external_meeting_chat_off_script) + powershell_output = pwsh_single_session_executor(ensure_external_meeting_chat_off_script).run_script_in_teams_pnp + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 + describe 'Ensure that the AllowExternalNonTrustedMeetingChat state' do subject { powershell_output.stdout.strip } it 'is set to False' do diff --git a/controls/microsoft-365-foundations-8.6.1.rb b/controls/microsoft-365-foundations-8.6.1.rb index 8016dd2..1a3da0c 100644 --- a/controls/microsoft-365-foundations-8.6.1.rb +++ b/controls/microsoft-365-foundations-8.6.1.rb @@ -74,78 +74,37 @@ ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/submissions-teams?view=o365-worldwide' microsoft_teams_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - Install-Module -Name MicrosoftTeams -Force -AllowClobber - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null (Get-CsTeamsMessagingPolicy -Identity Global).AllowSecurityEndUserReporting } - powershell_output_teams = powershell(microsoft_teams_script).stdout.strip + powershell_output_teams = pwsh_teams_executor(microsoft_teams_script).run_script_in_teams + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output_teams.stderr}" if powershell_output_teams.exit_status != 0 + describe 'Ensure the AllowSecurityEndUserReporting state from Get-CsTeamsMessagingPolicy' do - subject { powershell_output_teams } + subject { powershell_output_teams.stdout.strip } it 'is set to True' do expect(subject).to eq('True') end end - microsoft_defender_script = %{ - $client_id = '#{input('client_id')}' - $tenantid = '#{input('tenant_id')}' - $certificate_password = '#{input('certificate_password')}' - $certificate_path = '#{input('certificate_path')}' - $organization = '#{input('organization')}' - Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber - import-module exchangeonlinemanagement - Connect-ExchangeOnline -CertificateFilePath $certificate_path -CertificatePassword (ConvertTo-SecureString -String $certificate_password -AsPlainText -Force) -AppID $client_id -Organization $organization -ShowBanner:$false - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2('#{input('certificate_path')}','#{input('certificate_password')}') - import-module MicrosoftTeams - Connect-MicrosoftTeams -Certificate $cert -ApplicationId $client_id -TenantId $tenantid > $null + microsoft_defender_script = %( Get-ReportSubmissionPolicy | Select-Object -Property ReportJunkToCustomizedAddress, ReportNotJunkToCustomizedAddress, ReportPhishToCustomizedAddress, ReportJunkAddresses, ReportNotJunkAddresses, ReportPhishAddresses, ReportChatMessageEnabled, ReportChatMessageToCustomizedAddressEnabled | ConvertTo-Json - } + ) reporting_email_addresses = input('reporting_email_addresses_for_malicious_messages') - powershell_output = powershell(microsoft_defender_script).stdout.strip - submission_policy_data = JSON.parse(powershell_output) unless powershell_output.empty? - describe 'Ensure that the following states:' do - subject { powershell_output } - it 'ReportJunkToCustomizedAddress should be True' do - expect(submission_policy_data['ReportJunkToCustomizedAddress']).to eq(true) - end - - it 'ReportNotJunkToCustomizedAddress should be True' do - expect(submission_policy_data['ReportNotJunkToCustomizedAddress']).to eq(true) - end - - it 'ReportPhishToCustomizedAddress should be True' do - expect(submission_policy_data['ReportPhishToCustomizedAddress']).to eq(true) - end - - it "ReportJunkAddresses should be #{reporting_email_addresses}" do - expect(submission_policy_data['ReportJunkAddresses'].sort).to match_array(reporting_email_addresses.sort) - end + powershell_output = pwsh_exchange_executor(microsoft_defender_script).run_script_in_exchange + raise Inspec::Error, "The powershell output returned the following error: #{powershell_output.stderr}" if powershell_output.exit_status != 0 - it "ReportNotJunkAddresses should be #{reporting_email_addresses}" do - expect(submission_policy_data['ReportNotJunkAddresses'].sort).to match_array(reporting_email_addresses.sort) - end - - it "ReportPhishAddresses should be #{reporting_email_addresses}" do - expect(submission_policy_data['ReportPhishAddresses'].sort).to match_array(reporting_email_addresses.sort) - end - - it 'ReportChatMessageEnabled should be False' do - expect(submission_policy_data['ReportChatMessageEnabled']).to eq(false) - end - - it 'ReportChatMessageToCustomizedAddressEnabled should be True' do - expect(submission_policy_data['ReportChatMessageToCustomizedAddressEnabled']).to eq(true) - end + powershell_output = powershell_output.stdout.strip + submission_policy_data = JSON.parse(powershell_output) unless powershell_output.empty? + describe 'Ensure that the following state:' do + subject { submission_policy_data } + its(['ReportJunkToCustomizedAddress']) { should cmp true } + its(['ReportNotJunkToCustomizedAddress']) { should cmp true } + its(['ReportPhishToCustomizedAddress']) { should cmp true } + its(['ReportJunkAddresses'].sort) { should match_array(reporting_email_addresses.sort) } + its(['ReportNotJunkAddresses'].sort) { should match_array(reporting_email_addresses.sort) } + its(['ReportPhishAddresses'].sort) { should match_array(reporting_email_addresses.sort) } + its(['ReportChatMessageEnabled']) { should cmp false } + its(['ReportChatMessageToCustomizedAddressEnabled']) { should cmp true } end end diff --git a/inputs.yml b/inputs.yml index 75c2f81..2c24a17 100644 --- a/inputs.yml +++ b/inputs.yml @@ -1,3 +1,4 @@ +org_domain: mitredev.onmicrosoft.com notify_outbound_spam_recipients: - test@msft.com bcc_suspicious_outbound_additional_recipients: @@ -6,8 +7,6 @@ spf_domains: - google.com dmarc_domains: - google.com -moera_domains: - - google.com permitted_exceptions_teams_locations: - a - b diff --git a/inspec.yml b/inspec.yml index e7fb013..893f9f9 100644 --- a/inspec.yml +++ b/inspec.yml @@ -5,9 +5,11 @@ copyright: "MITRE, 2024" copyright_email: "saf@groups.mitre.org" license: "Apache-2.0" summary: "InSpec Validation Profile for the CIS Microsoft 365 Foundations Benchmark" -version: 3.1.0 +version: 3.1.1 inspec_version: ">= 6" - +depends: + - name: inspec-pwsh + git: https://github.com/mitre/inspec-pwsh.git inputs: - name: disable_slow_controls description: "Don't Run Long Running Controls (dev/testing only)" @@ -16,79 +18,10 @@ inputs: required: false #Controls using this input: -#1.1.3, 1.2.1, 1.2.2, 1.3.1, 1.3.3, 1.3.6, -#2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 2.1.10, 2.1.14, 2.4.4, -#3.1.1, 3.2.2, -#5.1.1.1, 5.1.2.2, 5.1.2.3, 5.1.3.1, 5.1.5.2, 5.1.8.1, 5.2.2.3, 5.2.3.4, -#6.1.1, 6.1.2, 6.1.3, 6.1.4, 6.2.1, 6.2.2, 6.2.3, 6.3.1, 6.5.1, 6.5.2, 6.5.3, -#7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, -#8.1.1, 8.1.2, 8.2.1, 8.5.1, 8.5.2, 8.5.3, 8.5.4, 8.5.5, 8.5.6, 8.5.7, 8.5.8, 8.6.1 -- name: client_id - sensitive: true - description: 'Client ID for Microsoft 365' - type: String - required: true - -#Controls using this input: -#1.1.3, 1.2.1, 1.2.2, 1.3.1, -#5.1.1.1, 5.1.2.2, 5.1.2.3, 5.1.3.1, 5.1.5.2, 5.1.8.1, 5.2.3.4, -#7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, -#8.1.1, 8.1.2, 8.2.1, 8.5.1, 8.5.2, 8.5.3, 8.5.4, 8.5.5, 8.5.6, 8.5.7, 8.5.8, 8.6.1 -- name: tenant_id - sensitive: true - description: 'Tenant ID for Microsoft 365' - type: String - required: true - -#Controls using this input: -#1.1.3, 1.2.1, 1.2.2, 1.3.1, -#5.1.1.1, 5.1.2.2, 5.1.2.3, 5.1.3.1, 5.1.5.2, 5.1.8.1, 5.2.3.4, -#7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, -#8.1.2 -- name: client_secret +#1.3.1 +- name: org_domain sensitive: true - description: 'Client Secret for Microsoft 365' - type: String - required: true - -#Controls using this input: -#1.2.2, 1.3.3, 1.3.6, -#2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 2.1.10, 2.1.14, 2.4.4, -#3.1.1, 3.2.2, -#5.2.2.3, -#6.1.1, 6.1.2, 6.1.3, 6.1.4, 6.2.1, 6.2.2, 6.2.3, 6.3.1, 6.5.1, 6.5.2, 6.5.3, -#7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, -#8.1.1, 8.1.2, 8.2.1, 8.5.1, 8.5.2, 8.5.3, 8.5.4, 8.5.5, 8.5.6, 8.5.7, 8.5.8, 8.6.1 -- name: certificate_path - sensitive: true - description: 'Certificate path for M365' - type: String - required: true - -#Controls using this input: -#1.2.2, 1.3.3, 1.3.6, -#2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 2.1.10, 2.1.14, 2.4.4, -#3.1.1, 3.2.2, -#5.2.2.3, -#6.1.1, 6.1.2, 6.1.3, 6.1.4, 6.2.1, 6.2.2, 6.2.3, 6.3.1, 6.5.1, 6.5.2, 6.5.3, -#7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, -#8.1.1, 8.1.2, 8.2.1, 8.5.1, 8.5.2, 8.5.3, 8.5.4, 8.5.5, 8.5.6, 8.5.7, 8.5.8, 8.6.1 -- name: certificate_password - sensitive: true - description: 'Password for certificate for M365' - type: String - required: true - -#Controls using this input: -#1.2.2, 1.3.1, 1.3.3, 1.3.6, -#2.1.1, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 2.1.10, 2.1.14, 2.4.4, -#3.1.1, 3.2.2, -#5.2.2.3, -#6.1.1, 6.1.2, 6.1.3, 6.1.4, 6.2.1, 6.2.2, 6.2.3, 6.3.1, 6.5.1, 6.5.2, 6.5.3, -#8.6.1 -- name: organization - sensitive: true - description: 'M365 Organization' + description: 'Domain for organization' type: String required: true @@ -132,14 +65,6 @@ inputs: type: String required: true -#Controls using this input: -#2.1.10 -- name: moera_domains - sensitive: true - description: 'Array of MOERA records to check' - type: Array - required: true - #Controls using this input: #3.2.2 - name: permitted_exceptions_teams_locations @@ -187,14 +112,6 @@ inputs: type: Array required: true -#Controls using this input: -#7.2.1, 7.2.2, 7.2.3, 7.2.4, 7.2.5, 7.2.6, 7.2.7, 7.2.9, 7.2.10, 7.3.1, 7.3.2, 7.3.4, -- name: sharepoint_admin_url - sensitive: true - description: 'SharePoint Admin URL to connect to' - type: String - required: true - #Controls using this input: #7.2.6 - name: domains_trusted_by_organization