Skip to content
Merged
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
118 changes: 118 additions & 0 deletions REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

### Functions

* [`extlib::aws::rds::db_instances`](#extlib--aws--rds--db_instances): Wraps Amazon RDS DescribeDBInstances to return detailed information on one or all RDS database instances.
* [`extlib::aws::rds_master_secret`](#extlib--aws--rds_master_secret): Convenience wrapper function for retrieving the AWS managed master secret of an RDS database instance.
* [`extlib::aws::region`](#extlib--aws--region): Returns the AWS region of the host running this function, read from its EC2 instance metadata (IMDS).
* [`extlib::aws::secretsmanager::secret_value`](#extlib--aws--secretsmanager--secret_value): Retrieves and parses an AWS Secrets Manager secret
* [`extlib::cache_data`](#extlib--cache_data): Retrieves data from a cache file, or creates it with supplied data if the file doesn't exist
* [`extlib::cidr_to_netmask`](#extlib--cidr_to_netmask): Converts an CIDR address of the form 192.168.0.1/24 into its netmask.
* [`extlib::cidr_to_network`](#extlib--cidr_to_network): Converts a CIDR address of the form 2001:DB8::/32 or 192.0.2.0/24 into their network address (also known as net address)
Expand Down Expand Up @@ -44,6 +48,120 @@ Based on https://github.com/mmckinst/puppet-hash2stuff/blob/master/lib/puppet/pa

## Functions

### <a name="extlib--aws--rds--db_instances"></a>`extlib::aws::rds::db_instances`

Type: Ruby 4.x API

This function queries the Amazon RDS API to retrieve information on RDS
database instances.

Currently, it only supports querying the instances using the IAM role
permissions of the EC2 instance running the function, (usually your
puppetserver unless the function call is `Deferred`), and it only supports
querying the instances in the same account as it is being run.

#### `extlib::aws::rds::db_instances(Optional[String[1]] $db_instance_identifier, Optional[Variant[Undef, String[1]]] $region)`

This function queries the Amazon RDS API to retrieve information on RDS
database instances.

Currently, it only supports querying the instances using the IAM role
permissions of the EC2 instance running the function, (usually your
puppetserver unless the function call is `Deferred`), and it only supports
querying the instances in the same account as it is being run.

Returns: `Variant[Array[Hash],Hash]` Returns a hash containing the DB instance data, or an Array of such hashes if the `db_instance_identifier` parameter was not specified.

##### `db_instance_identifier`

Data type: `Optional[String[1]]`

The RDS instance identifier or ARN of the DB instance. If omitted, returns an Array containing details of _all_ instances.

##### `region`

Data type: `Optional[Variant[Undef, String[1]]]`

The AWS region as used when creating the API client. If omitted (or explicitly passed `undef`), the region will be automatically looked up from the metadata of the EC2 instance running the function.

### <a name="extlib--aws--rds_master_secret"></a>`extlib::aws::rds_master_secret`

Type: Puppet Language

Convenience wrapper function for retrieving the AWS managed master secret of an RDS database instance.

#### `extlib::aws::rds_master_secret(String[1] $db_instance_identifier, Optional[String[1]] $region = undef)`

The extlib::aws::rds_master_secret function.

Returns: `Hash` The DB instance master secret hash, containing details such as the `username` and `password` depending on RDS instance type.

##### `db_instance_identifier`

Data type: `String[1]`

The RDS instance identifier or ARN

##### `region`

Data type: `Optional[String[1]]`

Optionally specify your AWS region. If not given, the `extlib::aws::region` function will be used to fetch the region.

### <a name="extlib--aws--region"></a>`extlib::aws::region`

Type: Ruby 4.x API

This function is primarily intended to be used internally by other
`extlib::aws` functions. It takes no parameters but depends on the EC2
Instance metadata service (IMDS) being `enabled`, (ie on your EC2 based
puppetserver or your agent if run as a `Deferred` function.)

#### `extlib::aws::region()`

This function is primarily intended to be used internally by other
`extlib::aws` functions. It takes no parameters but depends on the EC2
Instance metadata service (IMDS) being `enabled`, (ie on your EC2 based
puppetserver or your agent if run as a `Deferred` function.)

Returns: `String[1]` Returns an AWS region.

### <a name="extlib--aws--secretsmanager--secret_value"></a>`extlib::aws::secretsmanager::secret_value`

Type: Ruby 4.x API

This function queries the Amazon SecretsManager API to retrieve a secret
based on the ARN provided.

Currently, it only supports querying the instances using the IAM role
permissions of the EC2 instance running the function, (usually your
puppetserver unless the function call is `Deferred`), and it only supports
fetching secrets from the same account as the function is being run.

#### `extlib::aws::secretsmanager::secret_value(String[1] $secret_arn, Optional[Variant[Undef, String[1]]] $region)`

This function queries the Amazon SecretsManager API to retrieve a secret
based on the ARN provided.

Currently, it only supports querying the instances using the IAM role
permissions of the EC2 instance running the function, (usually your
puppetserver unless the function call is `Deferred`), and it only supports
fetching secrets from the same account as the function is being run.

Returns: `Variant[Sensitive[String[1]], Hash, Sensitive[Hash]]` Returns the secret. For plain text secrets, the function will return a `Sensitive[String]`. For key:value secrets, the secret JSON will be decoded. If the secret contains a `password` field, this will be returned as a `Sensitive[String]` within the `Hash` returned. If there isn't a `password` field, the complete hash will be returned wrapped in `Sensitive`.

##### `secret_arn`

Data type: `String[1]`

The ARN of the secret to fetch.

##### `region`

Data type: `Optional[Variant[Undef, String[1]]]`

The AWS region as used when creating the API client. If omitted (or explicitly passed `undef`), the region will be automatically looked up from the metadata of the EC2 instance running the function.

### <a name="extlib--cache_data"></a>`extlib::cache_data`

Type: Ruby 4.x API
Expand Down
22 changes: 22 additions & 0 deletions functions/aws/rds_master_secret.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# @summary Convenience wrapper function for retrieving the AWS managed master secret of an RDS database instance.
#
# @param db_instance_identifier
# The RDS instance identifier or ARN
# @param region
# Optionally specify your AWS region. If not given, the `extlib::aws::region` function will be used to fetch the region.
# @return The DB instance master secret hash, containing details such as the `username` and `password` depending on RDS instance type.
function extlib::aws::rds_master_secret (
String[1] $db_instance_identifier,
Optional[String[1]] $region = undef,
) >> Hash {
$rds_db_instance = extlib::aws::rds::db_instances($db_instance_identifier, $region)
$rds_master_user_secret = $rds_db_instance['master_user_secret']

unless $rds_master_user_secret =~ Hash {
fail("RDS DB instance '${db_instance_identifier}' has no AWS managed master secret (master_user_secret).")
}

unless $rds_master_user_secret['secret_status'] == 'active' { fail('rds_master_user_secret was not in state `active`') }

extlib::aws::secretsmanager::secret_value($rds_master_user_secret['secret_arn'], $region)
}
55 changes: 55 additions & 0 deletions lib/puppet/functions/extlib/aws/rds/db_instances.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

# @summary Wraps Amazon RDS DescribeDBInstances to return detailed information on one or all RDS database instances.
#
# This function queries the Amazon RDS API to retrieve information on RDS
# database instances.
#
# Currently, it only supports querying the instances using the IAM role
# permissions of the EC2 instance running the function, (usually your
# puppetserver unless the function call is `Deferred`), and it only supports
# querying the instances in the same account as it is being run.
Puppet::Functions.create_function(:'extlib::aws::rds::db_instances') do
# @param db_instance_identifier The RDS instance identifier or ARN of the DB instance. If omitted, returns an Array containing details of _all_ instances.
# @param region The AWS region as used when creating the API client. If omitted (or explicitly passed `undef`), the region will be automatically looked up from the metadata of the EC2 instance running the function.
# @return [Variant[Array[Hash],Hash]] Returns a hash containing the DB instance data, or an Array of such hashes if the `db_instance_identifier` parameter was not specified.
dispatch :db_instances do
optional_param 'String[1]', :db_instance_identifier
optional_param 'Variant[Undef, String[1]]', :region
return_type 'Variant[Array[Hash],Hash]'
end

require 'json'

def db_instances(db_instance_identifier = nil, region = nil)
begin
require 'aws-sdk-rds'
rescue LoadError => e
raise Puppet::Error, "extlib::aws::rds::db_instances requires the 'aws-sdk-rds' gem. (#{e.message})"
end

region ||= call_function('extlib::aws::region')
client = Aws::RDS::Client.new(region: region)

begin
resp = client.describe_db_instances(db_instance_identifier: db_instance_identifier)
instances = resp.each_page.flat_map(&:db_instances)
rescue Aws::RDS::Errors::DBInstanceNotFound => e
raise Puppet::Error, "RDS DB instance '#{db_instance_identifier}' not found: #{e.message}"
rescue Aws::Errors::ServiceError => e
raise Puppet::Error, "Error describing RDS DB instance(s): #{e.message}"
end

if db_instance_identifier
# We should have *exactly* one db instance returned, so this is a sanity check.
raise Puppet::DevError, "RDS DB instance '#{db_instance_identifier}' not found?!" if instances.size != 1

# JSON.parse(JSON.dump(x)) is a convenient way to recursively convert all symbols into normal strings.
JSON.parse(JSON.dump(instances.first.to_h))
else
instances.map do |instance|
JSON.parse(JSON.dump(instance.to_h))
end
end
end
end
44 changes: 44 additions & 0 deletions lib/puppet/functions/extlib/aws/region.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

# @summary Returns the AWS region of the host running this function, read from its EC2 instance metadata (IMDS).
#
# This function is primarily intended to be used internally by other
# `extlib::aws` functions. It takes no parameters but depends on the EC2
# Instance metadata service (IMDS) being `enabled`, (ie on your EC2 based
# puppetserver or your agent if run as a `Deferred` function.)
Puppet::Functions.create_function(:'extlib::aws::region') do
# The host's region is constant for the life of the process, so we only ever
# query IMDS once and memoize the result process-wide.
@cached_region = nil

class << self
attr_accessor :cached_region
end

# @return [String[1]] Returns an AWS region.
dispatch :region do
return_type 'String[1]'
end

def region
self.class.cached_region ||= lookup_region
end

def lookup_region
begin
require 'aws-sdk-core'
rescue LoadError => e
raise Puppet::Error, "extlib::aws::region requires the 'aws-sdk-core' gem. (#{e.message})"
end

begin
value = Aws::EC2Metadata.new.get('/latest/meta-data/placement/region')
rescue StandardError => e
raise Puppet::Error, "Unable to read AWS region from EC2 instance metadata: #{e.message}"
end

raise Puppet::Error, 'EC2 instance metadata returned an empty region.' if value.nil? || value.strip.empty?

value.strip
end
end
65 changes: 65 additions & 0 deletions lib/puppet/functions/extlib/aws/secretsmanager/secret_value.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

# @summary Retrieves and parses an AWS Secrets Manager secret
#
# This function queries the Amazon SecretsManager API to retrieve a secret
# based on the ARN provided.
#
# Currently, it only supports querying the instances using the IAM role
# permissions of the EC2 instance running the function, (usually your
# puppetserver unless the function call is `Deferred`), and it only supports
# fetching secrets from the same account as the function is being run.
Puppet::Functions.create_function(:'extlib::aws::secretsmanager::secret_value') do
# @param secret_arn The ARN of the secret to fetch.
# @param region The AWS region as used when creating the API client. If omitted (or explicitly passed `undef`), the region will be automatically looked up from the metadata of the EC2 instance running the function.
# @return [Variant[Sensitive[String[1]], Hash, Sensitive[Hash]]] Returns the secret. For plain text secrets, the function will return a `Sensitive[String]`. For key:value secrets, the secret JSON will be decoded. If the secret contains a `password` field, this will be returned as a `Sensitive[String]` within the `Hash` returned. If there isn't a `password` field, the complete hash will be returned wrapped in `Sensitive`.
dispatch :secret_value do
param 'String[1]', :secret_arn
optional_param 'Variant[Undef, String[1]]', :region
return_type 'Variant[Sensitive[String[1]], Hash, Sensitive[Hash]]'
end

require 'json'

def secret_value(secret_arn, region = nil)
begin
require 'aws-sdk-secretsmanager'
rescue LoadError => e
raise Puppet::Error, "extlib::aws::secretsmanager::secret_value requires the 'aws-sdk-secretsmanager' gem. (#{e.message})"
end

region ||= call_function('extlib::aws::region')
client = Aws::SecretsManager::Client.new(region: region)

begin
resp = client.get_secret_value(secret_id: secret_arn)
rescue Aws::SecretsManager::Errors::ResourceNotFoundException => e
raise Puppet::Error, "Secret '#{secret_arn}' not found: #{e.message}"
rescue Aws::Errors::ServiceError => e
raise Puppet::Error, "Error retrieving secret '#{secret_arn}': #{e.message}"
end

payload = resp.secret_string
raise Puppet::Error, "Secret '#{secret_arn}' has no SecretString (binary secrets are not supported)" if payload.nil?

begin
data = JSON.parse(payload)
rescue JSON::ParserError
data = nil
end

# Anything that isn't a JSON object (a plain string, or a JSON scalar such
# as a number or boolean) is treated as a 'normal' string secret which we
# wrap in Sensitive and return.
return Puppet::Pops::Types::PSensitiveType::Sensitive.new(payload) unless data.is_a?(Hash)

# Either wrap a `password` field if it exists, or the whole Hash otherwise
if data.key?('password')
data['password'] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(data['password'])
else
data = Puppet::Pops::Types::PSensitiveType::Sensitive.new(data)
end

data
end
end
Loading