Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CHEF-85 auth-token-refresh-retry-for-inspec-aws #970

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ This InSpec resource pack uses the AWS Ruby SDK v3 and provides the required res

### AWS Credentials

#### auto-refreshing credentials -
The AWS API call session will be terminated if an api(control) takes more than 12 hours
or whatever expiration duration is provided,disrupting the entire scanning process.
However, if auto-refresh is enabled, the token will be automatically refreshed 5 minutes prior to its expiration,
preventing the session from being terminated.

```Note - If Execution time is not more than 12 hours No need to proceed with auto refreshing credentials```

Valid AWS credentials are required, see [AWS Documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/intro-structure.html#intro-structure-principal)

There are multiple ways to set the AWS credentials, as shown below:

#### 1) Environment Variables
#### 1.1) Environment Variables without auto refresh

Set your AWS credentials in a `.envrc` file or export them in your shell. (See example [.envrc file](.envrc_example))

Expand All @@ -26,7 +35,52 @@ Set your AWS credentials in a `.envrc` file or export them in your shell. (See e
export AWS_AVAILABILITY_ZONE="eu-west-3a"
```

##### 2) Configuration File

#### 1.2) Environment Variables with auto refreshing credentials

Set your AWS credentials in a `.envrc` file or export them in your shell. (See example [.envrc file](.envrc_example))

```bash
# Example configuration
export AWS_ACCESS_KEY_ID="AKIAJUMP347SLS66IGCQ"
export AWS_SECRET_ACCESS_KEY="vD2lfoNvPdwsofqyuO9jRuWUkZIMqisdfeFmkHTy7ON+w"
export AWS_REGION="eu-west-3"
export AWS_AVAILABILITY_ZONE="eu-west-3a"
export AWS_ROLE_ARN="arn:aws:iam::112758395563:role/DUMMYRole"
export AWS_TOKEN_EXPIRATION_DURATION="901"
export AWS_ROLE_SESSION_NAME="DUMMY_aws_role_for_session"

#### AWS_ROLE_ARN (Required)-
The Amazon Resource Name (ARN) of the role that the app should assume.
To create the AWS_ROLE_ARN, which is in the format of "arn:aws:iam::account:role/role-name-with-path,"
you must use IAM policies in the AWS Console. You can create a role with limited access for specific purposes,
such as scanning only S3Access.
For example, a role can be created with the following format: "arn:aws:iam::123456789012:role/S3Access."
#### AWS_TOKEN_EXPIRATION_DURATION (Optional)-
Duration, which specifies the duration of the temporary security credentials.
Use the DurationSeconds parameter to specify the duration of the role session from 900 seconds (15 minutes) up to
the maximum session duration setting for the role. If you do not pass this parameter, the temporary credentials expire in one hour.

#### AWS_ROLE_SESSION_NAME (Required)-
Use this string value to identify the session when a role is used by different principals.
For security purposes, administrators can view this field in AWS CloudTrail logs to help identify who performed an
action in AWS. Your administrator might require that you specify your IAM user name as the session name when you assume the role.


#### 1.2.1 Create an AWS IAM Role ARN (Amazon Resource Name), you need to follow these steps:
1. Open the AWS Management Console and sign in to your AWS account.
2. Open the Identity and Access Management (IAM) console.
3. In the left-hand navigation pane, click on "Roles".
4. Click on the "Create Role" button.
5. Select the type of trusted entity that will assume this role. You can choose between "AWS service", "Another AWS account", or "Web identity".
6. Select the specific permissions that you want to grant to this role by attaching policies.
7. Give your role a name and click on "Create Role".
8. Once the role is created, you will be able to see its ARN in the IAM console. The ARN will look something like this: arn:aws:iam::account-id:role/role-name.
9. You can use this ARN to reference the role in other AWS services and grant it permissions to perform actions on your behalf.



#### 2) Configuration File

Set your AWS credentials in `~/.aws/config` and `~/.aws/credentials` file. (See example [aws configure credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html))

Expand Down
39 changes: 31 additions & 8 deletions libraries/aws_backend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
# Class to manage the AWS connection, instantiates all required clients for inspec resources
#
class AwsConnection
attr_reader :refresh_token_thread

def initialize(params)
params = {} if params.nil?
# Special case for AWS, let's allow all resources to specify parameters that propagate to the client init
Expand All @@ -81,8 +83,30 @@ def initialize(params)

def aws_client(klass)
# TODO: make this a dict with keys of klass.to_s.to_sym such that we can send different args per client in cases such as EC2 instance that use multiple different clients
return @cache[klass.to_s.to_sym] ||= klass.new(@client_args) if @client_args
@cache[klass.to_s.to_sym] ||= klass.new
if !ENV["AWS_ROLE_ARN"].nil? && !ENV["AWS_ROLE_SESSION_NAME"].nil?
assume_role_options = {
client: Aws::STS::Client.new,
role_arn: ENV["AWS_ROLE_ARN"],
role_session_name: ENV["AWS_ROLE_SESSION_NAME"],
}
if !ENV["AWS_TOKEN_EXPIRATION_DURATION"].nil?
assume_role_options[:duration_seconds] = ENV["AWS_TOKEN_EXPIRATION_DURATION"].to_i
end

assume_role_credentials = Aws::AssumeRoleCredentials.new(assume_role_options)
if @client_args
args = {
credentials: assume_role_credentials,
}
final_args = args.merge(@client_args)
end
return @cache[klass.to_s.to_sym] ||= klass.new(final_args) if @client_args
@cache[klass.to_s.to_sym] ||= klass.new(credentials: assume_role_credentials)

else
return @cache[klass.to_s.to_sym] ||= klass.new(@client_args) if @client_args
@cache[klass.to_s.to_sym] ||= klass.new
end
end

def aws_resource(klass, args)
Expand Down Expand Up @@ -366,6 +390,7 @@ def initialize(opts)

@resource_data = opts[:resource_data].presence&.to_h
end

@aws = AwsConnection.new(client_args)
# N.B. if/when we migrate AwsConnection to train, can update above and inject args via:
# inspec.backend.aws_client(Aws::EC2::Resource,opts)
Expand All @@ -380,6 +405,10 @@ def initialize(opts)
@aws.aws_client(stub[:client]).stub_responses(stub[:method], stub[:data])
end
end

def stop_refresh_token_thread
@aws.stop_refresh_token_thread
end
# rubocop:enable Lint/MissingSuper

# Ensure required parameters have been set to perform backend operations.
Expand Down Expand Up @@ -498,11 +527,9 @@ def method_missing(method_name, *args, &block)

# This is to make RuboCop happy.
# Disabling Useless method definition detection as there is an issue with rubocop
# rubocop:disable Lint/UselessMethodDefinition
def respond_to_missing?(*several_variants)
super
end
# rubocop:enable Lint/UselessMethodDefinition

private

Expand Down Expand Up @@ -713,11 +740,9 @@ def method_missing(method_name, *args, &block)

# This is to make RuboCop happy.
# Disabling Useless method definition detection as there is an issue with rubocop
# rubocop:disable Lint/UselessMethodDefinition
def respond_to_missing?(*several_variants)
super
end
# rubocop:enable Lint/UselessMethodDefinition

def to_s
"Property is missing! The following are available: #{item.keys.map(&:to_s)}"
Expand Down Expand Up @@ -753,11 +778,9 @@ def method_missing(method_name, *args, &block)

# This is to make RuboCop happy.
# Disabling Useless method definition detection as there is an issue with rubocop
# rubocop:disable Lint/UselessMethodDefinition
def respond_to_missing?(*several_variants)
super
end
# rubocop:enable Lint/UselessMethodDefinition

def to_s
nil
Expand Down