inspec 4.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +63 -0
  3. data/inspec.gemspec +36 -0
  4. data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/Gemfile +11 -0
  5. data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/inspec-plugin-template.gemspec +43 -0
  6. data/lib/plugins/inspec-init/templates/profiles/aws/README.md +192 -0
  7. data/lib/plugins/inspec-init/templates/profiles/aws/attributes.yml +2 -0
  8. data/lib/plugins/inspec-init/templates/profiles/aws/controls/example.rb +39 -0
  9. data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +22 -0
  10. data/lib/plugins/inspec-init/templates/profiles/azure/README.md +56 -0
  11. data/lib/plugins/inspec-init/templates/profiles/azure/controls/example.rb +14 -0
  12. data/lib/plugins/inspec-init/templates/profiles/azure/inspec.yml +14 -0
  13. data/lib/plugins/inspec-init/templates/profiles/gcp/README.md +66 -0
  14. data/lib/plugins/inspec-init/templates/profiles/gcp/attributes.yml +2 -0
  15. data/lib/plugins/inspec-init/templates/profiles/gcp/controls/example.rb +27 -0
  16. data/lib/plugins/inspec-init/templates/profiles/gcp/inspec.yml +19 -0
  17. data/lib/resource_support/aws.rb +76 -0
  18. data/lib/resource_support/aws/aws_backend_base.rb +12 -0
  19. data/lib/resource_support/aws/aws_backend_factory_mixin.rb +12 -0
  20. data/lib/resource_support/aws/aws_plural_resource_mixin.rb +24 -0
  21. data/lib/resource_support/aws/aws_resource_mixin.rb +69 -0
  22. data/lib/resource_support/aws/aws_singular_resource_mixin.rb +27 -0
  23. data/lib/resources/aws/aws_billing_report.rb +107 -0
  24. data/lib/resources/aws/aws_billing_reports.rb +74 -0
  25. data/lib/resources/aws/aws_cloudtrail_trail.rb +97 -0
  26. data/lib/resources/aws/aws_cloudtrail_trails.rb +51 -0
  27. data/lib/resources/aws/aws_cloudwatch_alarm.rb +67 -0
  28. data/lib/resources/aws/aws_cloudwatch_log_metric_filter.rb +105 -0
  29. data/lib/resources/aws/aws_config_delivery_channel.rb +74 -0
  30. data/lib/resources/aws/aws_config_recorder.rb +99 -0
  31. data/lib/resources/aws/aws_ebs_volume.rb +127 -0
  32. data/lib/resources/aws/aws_ebs_volumes.rb +69 -0
  33. data/lib/resources/aws/aws_ec2_instance.rb +162 -0
  34. data/lib/resources/aws/aws_ec2_instances.rb +69 -0
  35. data/lib/resources/aws/aws_ecs_cluster.rb +88 -0
  36. data/lib/resources/aws/aws_eks_cluster.rb +105 -0
  37. data/lib/resources/aws/aws_elb.rb +85 -0
  38. data/lib/resources/aws/aws_elbs.rb +84 -0
  39. data/lib/resources/aws/aws_flow_log.rb +106 -0
  40. data/lib/resources/aws/aws_iam_access_key.rb +112 -0
  41. data/lib/resources/aws/aws_iam_access_keys.rb +153 -0
  42. data/lib/resources/aws/aws_iam_group.rb +62 -0
  43. data/lib/resources/aws/aws_iam_groups.rb +56 -0
  44. data/lib/resources/aws/aws_iam_password_policy.rb +121 -0
  45. data/lib/resources/aws/aws_iam_policies.rb +57 -0
  46. data/lib/resources/aws/aws_iam_policy.rb +311 -0
  47. data/lib/resources/aws/aws_iam_role.rb +60 -0
  48. data/lib/resources/aws/aws_iam_root_user.rb +82 -0
  49. data/lib/resources/aws/aws_iam_user.rb +145 -0
  50. data/lib/resources/aws/aws_iam_users.rb +160 -0
  51. data/lib/resources/aws/aws_kms_key.rb +100 -0
  52. data/lib/resources/aws/aws_kms_keys.rb +58 -0
  53. data/lib/resources/aws/aws_rds_instance.rb +74 -0
  54. data/lib/resources/aws/aws_route_table.rb +67 -0
  55. data/lib/resources/aws/aws_route_tables.rb +64 -0
  56. data/lib/resources/aws/aws_s3_bucket.rb +142 -0
  57. data/lib/resources/aws/aws_s3_bucket_object.rb +87 -0
  58. data/lib/resources/aws/aws_s3_buckets.rb +52 -0
  59. data/lib/resources/aws/aws_security_group.rb +314 -0
  60. data/lib/resources/aws/aws_security_groups.rb +71 -0
  61. data/lib/resources/aws/aws_sns_subscription.rb +82 -0
  62. data/lib/resources/aws/aws_sns_topic.rb +57 -0
  63. data/lib/resources/aws/aws_sns_topics.rb +60 -0
  64. data/lib/resources/aws/aws_sqs_queue.rb +66 -0
  65. data/lib/resources/aws/aws_subnet.rb +92 -0
  66. data/lib/resources/aws/aws_subnets.rb +56 -0
  67. data/lib/resources/aws/aws_vpc.rb +77 -0
  68. data/lib/resources/aws/aws_vpcs.rb +55 -0
  69. data/lib/resources/azure/azure_backend.rb +379 -0
  70. data/lib/resources/azure/azure_generic_resource.rb +55 -0
  71. data/lib/resources/azure/azure_resource_group.rb +151 -0
  72. data/lib/resources/azure/azure_virtual_machine.rb +262 -0
  73. data/lib/resources/azure/azure_virtual_machine_data_disk.rb +131 -0
  74. metadata +202 -0
@@ -0,0 +1,112 @@
1
+ require "resource_support/aws/aws_singular_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-iam"
4
+
5
+ class AwsIamAccessKey < Inspec.resource(1)
6
+ name "aws_iam_access_key"
7
+ desc "Verifies settings for an individual IAM access key"
8
+ example <<~EXAMPLE
9
+ describe aws_iam_access_key(username: 'username', id: 'access-key id') do
10
+ it { should exist }
11
+ it { should_not be_active }
12
+ its('create_date') { should be > Time.now - 365 * 86400 }
13
+ its('last_used_date') { should be > Time.now - 90 * 86400 }
14
+ end
15
+ EXAMPLE
16
+ supports platform: "aws"
17
+
18
+ include AwsSingularResourceMixin
19
+ attr_reader :access_key_id, :create_date, :status, :username
20
+ alias id access_key_id
21
+
22
+ def validate_params(raw_params)
23
+ recognized_params = check_resource_param_names(
24
+ raw_params: raw_params,
25
+ allowed_params: %i{username id access_key_id},
26
+ allowed_scalar_name: :access_key_id,
27
+ allowed_scalar_type: String
28
+ )
29
+
30
+ # id and access_key_id are aliases; standardize on access_key_id
31
+ recognized_params[:access_key_id] = recognized_params.delete(:id) if recognized_params.key?(:id)
32
+
33
+ # Validate format of access_key_id
34
+ if recognized_params[:access_key_id] &&
35
+ recognized_params[:access_key_id] !~ (/^AKIA[0-9A-Z]{16}$/)
36
+ raise ArgumentError, "Incorrect format for Access Key ID - expected AKIA followed " \
37
+ "by 16 letters or numbers"
38
+ end
39
+
40
+ # One of username and access_key_id is required
41
+ if recognized_params[:username].nil? && recognized_params[:access_key_id].nil?
42
+ raise ArgumentError, "You must provide at lease one of access_key_id or username to aws_iam_access_key"
43
+ end
44
+
45
+ recognized_params
46
+ end
47
+
48
+ def active?
49
+ return nil unless exists?
50
+
51
+ status == "Active"
52
+ end
53
+
54
+ def to_s
55
+ "IAM Access-Key #{access_key_id}"
56
+ end
57
+
58
+ def last_used_date
59
+ return nil unless exists?
60
+ return @last_used_date if defined? @last_used_date
61
+
62
+ backend = BackendFactory.create(inspec_runner)
63
+ catch_aws_errors do
64
+ @last_used_date = backend.get_access_key_last_used({ access_key_id: access_key_id }).access_key_last_used.last_used_date
65
+ end
66
+ end
67
+
68
+ def fetch_from_api
69
+ backend = BackendFactory.create(inspec_runner)
70
+ query = {}
71
+ query[:user_name] = username if username
72
+
73
+ response = backend.list_access_keys(query)
74
+
75
+ access_keys = response.access_key_metadata.select do |key|
76
+ if access_key_id
77
+ key.access_key_id == access_key_id
78
+ else
79
+ true
80
+ end
81
+ end
82
+
83
+ if access_keys.empty?
84
+ @exists = false
85
+ return
86
+ end
87
+
88
+ if access_keys.count > 1
89
+ raise "More than one access key matched for aws_iam_access_key. Use more specific paramaters, such as access_key_id."
90
+ end
91
+
92
+ @exists = true
93
+ @access_key_id = access_keys[0].access_key_id
94
+ @username = access_keys[0].user_name
95
+ @create_date = access_keys[0].create_date
96
+ @status = access_keys[0].status
97
+ # Last used date is lazily loaded, separate API call
98
+ rescue Aws::IAM::Errors::NoSuchEntity
99
+ @exists = false
100
+ end
101
+
102
+ class Backend
103
+ class AwsClientApi < AwsBackendBase
104
+ BackendFactory.set_default_backend(self)
105
+ self.aws_client_class = Aws::IAM::Client
106
+
107
+ def list_access_keys(query)
108
+ aws_service_client.list_access_keys(query)
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,153 @@
1
+ require "resource_support/aws/aws_plural_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-iam"
4
+
5
+ class AwsIamAccessKeys < Inspec.resource(1)
6
+ name "aws_iam_access_keys"
7
+ desc "Verifies settings for AWS IAM Access Keys in bulk"
8
+ example <<~EXAMPLE
9
+ describe aws_iam_access_keys do
10
+ it { should_not exist }
11
+ end
12
+ EXAMPLE
13
+ supports platform: "aws"
14
+
15
+ include AwsPluralResourceMixin
16
+
17
+ def validate_params(raw_params)
18
+ recognized_params = check_resource_param_names(
19
+ raw_params: raw_params,
20
+ allowed_params: %i{username id access_key_id created_date},
21
+ allowed_scalar_name: :access_key_id,
22
+ allowed_scalar_type: String
23
+ )
24
+
25
+ # id and access_key_id are aliases; standardize on access_key_id
26
+ recognized_params[:access_key_id] = recognized_params.delete(:id) if recognized_params.key?(:id)
27
+ if recognized_params[:access_key_id] &&
28
+ recognized_params[:access_key_id] !~ (/^AKIA[0-9A-Z]{16}$/)
29
+ raise "Incorrect format for Access Key ID - expected AKIA followed " \
30
+ "by 16 letters or numbers"
31
+ end
32
+
33
+ recognized_params
34
+ end
35
+
36
+ def fetch_from_api
37
+ # TODO: this interface should be normalized to match the AWS API
38
+ criteria = {}
39
+ criteria[:username] = @username if defined? @username
40
+ @table = BackendFactory.create(inspec_runner).fetch(criteria)
41
+ end
42
+
43
+ # Underlying FilterTable implementation.
44
+ filter = FilterTable.create
45
+ filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
46
+ filter.register_column(:access_key_ids, field: :access_key_id)
47
+ .register_column(:created_date, field: :create_date)
48
+ .register_column(:created_days_ago, field: :created_days_ago)
49
+ .register_column(:created_with_user, field: :created_with_user)
50
+ .register_column(:created_hours_ago, field: :created_hours_ago)
51
+ .register_column(:usernames, field: :username)
52
+ .register_column(:active, field: :active)
53
+ .register_column(:inactive, field: :inactive)
54
+ .register_column(:last_used_date, field: :last_used_date)
55
+ .register_column(:last_used_hours_ago, field: :last_used_hours_ago)
56
+ .register_column(:last_used_days_ago, field: :last_used_days_ago)
57
+ .register_column(:ever_used, field: :ever_used)
58
+ .register_column(:never_used, field: :never_used)
59
+ .register_column(:user_created_date, field: :user_created_date)
60
+ filter.install_filter_methods_on_resource(self, :table)
61
+
62
+ def to_s
63
+ "IAM Access Keys"
64
+ end
65
+
66
+ # Internal support class. This is used to fetch
67
+ # the users and access keys. We have an abstract
68
+ # class with a concrete AWS implementation provided here;
69
+ # a few mock implementations are also provided in the unit tests.
70
+ class Backend
71
+ # Implementation of AccessKeyProvider which operates by looping over
72
+ # all users, then fetching their access keys.
73
+ # TODO: An alternate, more scalable implementation could be made
74
+ # using the Credential Report.
75
+ class AwsUserIterator < AwsBackendBase
76
+ BackendFactory.set_default_backend(self)
77
+ self.aws_client_class = Aws::IAM::Client
78
+
79
+ def fetch(criteria)
80
+ iam_client = aws_service_client
81
+
82
+ user_details = {}
83
+ if criteria.key?(:username)
84
+ begin
85
+ user_details[criteria[:username]] = iam_client.get_user(user_name: criteria[:username]).user
86
+ rescue Aws::IAM::Errors::NoSuchEntity # rubocop:disable Lint/HandleExceptions
87
+ # Swallow - a miss on search results should return an empty table
88
+ end
89
+ else
90
+ pagination_opts = {}
91
+ loop do
92
+ api_result = iam_client.list_users(pagination_opts)
93
+ api_result.users.each do |info|
94
+ user_details[info.user_name] = info
95
+ end
96
+ break unless api_result.is_truncated
97
+
98
+ pagination_opts[:marker] = api_result.marker
99
+ end
100
+ end
101
+
102
+ access_key_data = []
103
+ user_details.each_key do |username|
104
+ begin
105
+ user_keys = iam_client.list_access_keys(user_name: username)
106
+ .access_key_metadata
107
+ user_keys = user_keys.map do |metadata|
108
+ {
109
+ access_key_id: metadata.access_key_id,
110
+ username: username,
111
+ status: metadata.status,
112
+ create_date: metadata.create_date, # DateTime.parse(metadata.create_date),
113
+ }
114
+ end
115
+
116
+ # Copy in from user data
117
+ # Synthetics
118
+ user_keys.each do |key_info|
119
+ add_synthetic_fields(key_info, user_details[username])
120
+ end
121
+ access_key_data.concat(user_keys)
122
+ rescue Aws::IAM::Errors::NoSuchEntity # rubocop:disable Lint/HandleExceptions
123
+ # Swallow - a miss on search results should return an empty table
124
+ end
125
+ end
126
+ access_key_data
127
+ end
128
+
129
+ def add_synthetic_fields(key_info, user_details) # rubocop:disable Metrics/AbcSize
130
+ key_info[:id] = key_info[:access_key_id]
131
+ key_info[:active] = key_info[:status] == "Active"
132
+ key_info[:inactive] = key_info[:status] != "Active"
133
+ key_info[:created_hours_ago] = ((Time.now - key_info[:create_date]) / (60 * 60)).to_i
134
+ key_info[:created_days_ago] = (key_info[:created_hours_ago] / 24).to_i
135
+ key_info[:user_created_date] = user_details[:create_date]
136
+ key_info[:created_with_user] = (key_info[:create_date] - key_info[:user_created_date]).abs < 1.0 / 24.0
137
+
138
+ # Last used is a separate API call
139
+ iam_client = aws_service_client
140
+ last_used =
141
+ iam_client.get_access_key_last_used(access_key_id: key_info[:access_key_id])
142
+ .access_key_last_used.last_used_date
143
+ key_info[:ever_used] = !last_used.nil?
144
+ key_info[:never_used] = last_used.nil?
145
+ key_info[:last_used_time] = last_used
146
+ return unless last_used
147
+
148
+ key_info[:last_used_hours_ago] = ((Time.now - last_used) / (60 * 60)).to_i
149
+ key_info[:last_used_days_ago] = (key_info[:last_used_hours_ago] / 24).to_i
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,62 @@
1
+ require "resource_support/aws/aws_singular_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-iam"
4
+
5
+ class AwsIamGroup < Inspec.resource(1)
6
+ name "aws_iam_group"
7
+ desc "Verifies settings for AWS IAM Group"
8
+ example <<~EXAMPLE
9
+ describe aws_iam_group('mygroup') do
10
+ it { should exist }
11
+ end
12
+ EXAMPLE
13
+ supports platform: "aws"
14
+
15
+ include AwsSingularResourceMixin
16
+ attr_reader :group_name, :users
17
+
18
+ def to_s
19
+ "IAM Group #{group_name}"
20
+ end
21
+
22
+ private
23
+
24
+ def validate_params(raw_params)
25
+ validated_params = check_resource_param_names(
26
+ raw_params: raw_params,
27
+ allowed_params: [:group_name],
28
+ allowed_scalar_name: :group_name,
29
+ allowed_scalar_type: String
30
+ )
31
+
32
+ if validated_params.empty?
33
+ raise ArgumentError, "You must provide a group_name to aws_iam_group."
34
+ end
35
+
36
+ validated_params
37
+ end
38
+
39
+ def fetch_from_api
40
+ backend = AwsIamGroup::BackendFactory.create(inspec_runner)
41
+
42
+ begin
43
+ resp = backend.get_group(group_name: group_name)
44
+ @exists = true
45
+ @aws_group_struct = resp[:group]
46
+ @users = resp[:users].map(&:user_name)
47
+ rescue Aws::IAM::Errors::NoSuchEntity
48
+ @exists = false
49
+ end
50
+ end
51
+
52
+ class Backend
53
+ class AwsClientApi < AwsBackendBase
54
+ BackendFactory.set_default_backend(self)
55
+ self.aws_client_class = Aws::IAM::Client
56
+
57
+ def get_group(query)
58
+ aws_service_client.get_group(query)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,56 @@
1
+ require "resource_support/aws/aws_plural_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-iam"
4
+
5
+ class AwsIamGroups < Inspec.resource(1)
6
+ name "aws_iam_groups"
7
+ desc "Verifies settings for AWS IAM groups in bulk"
8
+ example <<~EXAMPLE
9
+ describe aws_iam_groups do
10
+ it { should exist }
11
+ end
12
+ EXAMPLE
13
+ supports platform: "aws"
14
+
15
+ include AwsPluralResourceMixin
16
+
17
+ def validate_params(resource_params)
18
+ unless resource_params.empty?
19
+ raise ArgumentError, "aws_iam_groups does not accept resource parameters."
20
+ end
21
+
22
+ resource_params
23
+ end
24
+
25
+ # Underlying FilterTable implementation.
26
+ filter = FilterTable.create
27
+ filter.register_column(:group_names, field: :group_name)
28
+ filter.install_filter_methods_on_resource(self, :table)
29
+
30
+ def to_s
31
+ "IAM Groups"
32
+ end
33
+
34
+ def fetch_from_api
35
+ backend = BackendFactory.create(inspec_runner)
36
+ @table = []
37
+ pagination_opts = {}
38
+ loop do
39
+ api_result = backend.list_groups(pagination_opts)
40
+ @table += api_result.groups.map(&:to_h)
41
+ pagination_opts = { marker: api_result.marker }
42
+ break unless api_result.is_truncated
43
+ end
44
+ end
45
+
46
+ class Backend
47
+ class AwsClientApi < AwsBackendBase
48
+ BackendFactory.set_default_backend(self)
49
+ self.aws_client_class = Aws::IAM::Client
50
+
51
+ def list_groups(query = {})
52
+ aws_service_client.list_groups(query)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,121 @@
1
+ require "resource_support/aws/aws_singular_resource_mixin"
2
+ require "resource_support/aws/aws_backend_base"
3
+ require "aws-sdk-iam"
4
+
5
+ class AwsIamPasswordPolicy < Inspec.resource(1)
6
+ name "aws_iam_password_policy"
7
+ desc "Verifies iam password policy"
8
+
9
+ example <<~EXAMPLE
10
+ describe aws_iam_password_policy do
11
+ its('requires_lowercase_characters?') { should be true }
12
+ end
13
+
14
+ describe aws_iam_password_policy do
15
+ its('requires_uppercase_characters?') { should be true }
16
+ end
17
+ EXAMPLE
18
+ supports platform: "aws"
19
+
20
+ # TODO: rewrite to avoid direct injection, match other resources, use AwsSingularResourceMixin
21
+ def initialize(conn = nil)
22
+ catch_aws_errors do
23
+ begin
24
+ if conn
25
+ # We're in a mocked unit test.
26
+ @policy = conn.iam_resource.account_password_policy
27
+ else
28
+ # Don't use the resource approach. It's a CRUD operation
29
+ # - if the policy does not exist, you get back a blank object to populate and save.
30
+ # Using the Client will throw an exception if no policy exists.
31
+ @policy = inspec_runner.backend.aws_client(Aws::IAM::Client).get_account_password_policy.password_policy
32
+ end
33
+ rescue Aws::IAM::Errors::NoSuchEntity
34
+ @policy = nil
35
+ end
36
+ end
37
+ end
38
+
39
+ # TODO: DRY up, see https://github.com/chef/inspec/issues/2633
40
+ # Copied from resource_support/aws/aws_resource_mixin.rb
41
+ def catch_aws_errors
42
+ yield
43
+ rescue Aws::Errors::MissingCredentialsError
44
+ # The AWS error here is unhelpful:
45
+ # "unable to sign request without credentials set"
46
+ Inspec::Log.error "It appears that you have not set your AWS credentials. You may set them using environment variables, or using the 'aws://region/aws_credentials_profile' target. See https://www.inspec.io/docs/reference/platforms for details."
47
+ fail_resource("No AWS credentials available")
48
+ rescue Aws::Errors::ServiceError => e
49
+ fail_resource e.message
50
+ end
51
+
52
+ # TODO: DRY up, see https://github.com/chef/inspec/issues/2633
53
+ # Copied from resource_support/aws/aws_singular_resource_mixin.rb
54
+ def inspec_runner
55
+ # When running under inspec-cli, we have an 'inspec' method that
56
+ # returns the runner. When running under unit tests, we don't
57
+ # have that, but we still have to call this to pass something
58
+ # (nil is OK) to the backend.
59
+ # TODO: remove with https://github.com/chef/inspec-aws/issues/216
60
+ # TODO: remove after rewrite to include AwsSingularResource
61
+ inspec if respond_to?(:inspec)
62
+ end
63
+
64
+ def to_s
65
+ "IAM Password-Policy"
66
+ end
67
+
68
+ def exists?
69
+ !@policy.nil?
70
+ end
71
+
72
+ #-------------------------- Properties ----------------------------#
73
+
74
+ def minimum_password_length
75
+ @policy.minimum_password_length
76
+ end
77
+
78
+ def max_password_age_in_days
79
+ raise "this policy does not expire passwords" unless expire_passwords?
80
+
81
+ @policy.max_password_age
82
+ end
83
+
84
+ def number_of_passwords_to_remember
85
+ raise "this policy does not prevent password reuse" \
86
+ unless prevent_password_reuse?
87
+
88
+ @policy.password_reuse_prevention
89
+ end
90
+
91
+ #-------------------------- Matchers ----------------------------#
92
+ %i{
93
+ require_lowercase_characters
94
+ require_uppercase_characters
95
+ require_symbols
96
+ require_numbers
97
+ expire_passwords
98
+ }.each do |matcher_stem|
99
+ # Create our predicates (for example, 'require_symbols?')
100
+ stem_with_question_mark = (matcher_stem.to_s + "?").to_sym
101
+ define_method stem_with_question_mark do
102
+ @policy.send(matcher_stem)
103
+ end
104
+ # RSpec will expose that as (for example) `be_require_symbols`.
105
+ # To undo that, we have to make a matcher alias.
106
+ stem_with_be = ("be_" + matcher_stem.to_s).to_sym
107
+ RSpec::Matchers.alias_matcher matcher_stem, stem_with_be
108
+ end
109
+
110
+ # This one has an awkward name mapping
111
+ def allow_users_to_change_passwords?
112
+ @policy.allow_users_to_change_password
113
+ end
114
+ RSpec::Matchers.alias_matcher :allow_users_to_change_passwords, :be_allow_users_to_change_passwords
115
+
116
+ # This one has custom logic and renaming
117
+ def prevent_password_reuse?
118
+ !@policy.password_reuse_prevention.nil?
119
+ end
120
+ RSpec::Matchers.alias_matcher :prevent_password_reuse, :be_prevent_password_reuse
121
+ end