cookbooks/aws-parallelcluster-environment/recipes/config/directory_service.rb (159 lines of code) (raw):
# frozen_string_literal: true
#
# Cookbook:: aws-parallelcluster
# Recipe:: directory_service
#
# Copyright:: 2013-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the
# License. A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
# OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and
# limitations under the License.
require 'uri'
return if node['cluster']["directory_service"]["enabled"] == 'false'
return if node['cluster']['node_type'] == 'ComputeFleet' && node['cluster']['directory_service']['disabled_on_compute_nodes'] == 'true'
sssd_conf_path = "/etc/sssd/sssd.conf"
shared_directory_service_dir = "#{node['cluster']['shared_dir']}/directory_service"
login_node_shared_directory_service_dir = "#{node['cluster']['shared_dir_login_nodes']}/directory_service"
shared_sssd_conf_path = "#{shared_directory_service_dir}/sssd.conf"
login_node_shared_sssd_conf_path = "#{login_node_shared_directory_service_dir}/sssd.conf"
case node['cluster']['node_type']
when 'HeadNode'
region = node['cluster']['region']
# DomainName
# We can assume that DomainName can only be a FQDN or the domain section in a LDAP Distinguished Name.
# We can assume it because the CLI is in charge of validating it.
FQDN_PATTERN = /^([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+)*$/.freeze
domain_name = node['cluster']['directory_service']['domain_name']
ldap_search_base =
if domain_name =~ FQDN_PATTERN
domain_name.split('.').map { |v| "DC=#{v}" }.join(',')
else
domain_name
end
# Domain Address
domain_addresses = node['cluster']['directory_service']['domain_addr'].split(",")
# If a domain address does not include a protocol, ldaps is assumed for it.
ldap_uri_components = domain_addresses.map do |domain_address|
if URI.parse(domain_address).scheme
domain_address
else
Chef::Log.info("No protocol specified for domain address #{domain_address}. Assuming ldaps.")
"ldaps://#{domain_address}"
end
end
# Password
# The Active Directory Password can be:
# 1. A secret in Secrets Manager, e.g. arn:aws:secretsmanager:eu-west-1:12345678910:secret:PasswordName
# 2. A parameter in SSM, e.g. arn:aws:ssm:eu-west-1:12345678910:parameter/PasswordName
password_secret_arn_str = node['cluster']['directory_service']['password_secret_arn']
password_secret_arn = parse_arn(password_secret_arn_str)
password_secret_service = password_secret_arn[:service]
password_secret_resource = password_secret_arn[:resource]
ldap_password =
if kitchen_test? && !node['interact_with_secretmanager']
"fake-secret"
elsif password_secret_service == "secretsmanager" && password_secret_resource.start_with?("secret")
shell_out!("aws secretsmanager get-secret-value --secret-id #{password_secret_arn_str} --region #{region} --query 'SecretString' --output text").stdout.strip
elsif password_secret_service == "ssm" && password_secret_resource.start_with?("parameter")
(parameter_name = password_secret_resource.split("/")[1])
shell_out!("aws ssm get-parameter --name #{parameter_name} --region #{region} --with-decryption --query 'Parameter.Value' --output text").stdout.strip
else
raise "The secret #{password_secret_arn_str} is not supported"
end
# Head node writes the sssd.conf file and contacts the secret manager to retrieve the LDAP password.
# Then the sssd.conf file is shared through shared_sssd_conf_path to compute nodes.
# Only contacting the secret manager from head node avoids giving permission to compute nodes to contact the secret manager.
# Configure SSSD domain properties
domain_properties = {
# Mandatory properties that must not be overridden by the user.
'id_provider' => 'ldap',
'ldap_schema' => 'AD',
# Mandatory properties that are meant to be set via dedicated cluster config properties,
# but that can also be overridden via DirectoryService/AdditionalSssdConfigs.
'ldap_uri' => ldap_uri_components.join(','),
'ldap_search_base' => ldap_search_base,
'ldap_default_bind_dn' => node['cluster']['directory_service']['domain_read_only_user'],
'ldap_default_authtok' => ldap_password,
'ldap_tls_reqcert' => node['cluster']['directory_service']['ldap_tls_req_cert'],
# Optional properties for which we provide a default value,
# that are not meant to be set via dedicated cluster config properties,
# but that can be overridden by the user via DirectoryService/AdditionalSssdConfigs.
'cache_credentials' => 'True',
'default_shell' => '/bin/bash',
'fallback_homedir' => '/home/%u',
'ldap_id_mapping' => 'True',
'ldap_referrals' => 'False',
'use_fully_qualified_names' => 'False',
}
# Optional properties that are meant to be set via dedicated cluster config properties.
# - ldap_tls_ca_cert
# - ldap_access_filter
# - access_provider only if ldap_access_filter is specified
unless node['cluster']['directory_service']['ldap_tls_ca_cert'].eql?('NONE')
domain_properties['ldap_tls_cacert'] = node['cluster']['directory_service']['ldap_tls_ca_cert']
end
unless node['cluster']['directory_service']['ldap_access_filter'].eql?('NONE')
domain_properties['access_provider'] = 'ldap'
domain_properties['ldap_access_filter'] = node['cluster']['directory_service']['ldap_access_filter']
end
# Optional properties that are meant to be set via DirectoryService/AdditionalSssdConfigs
if node['cluster']['directory_service']['additional_sssd_configs']
domain_properties.merge!(node['cluster']['directory_service']['additional_sssd_configs'])
end
# Write sssd.conf file
template sssd_conf_path do
source 'directory_service/sssd.conf.erb'
owner 'root'
group 'root'
mode '0600'
variables(
domain_properties: domain_properties
)
sensitive true
end unless on_docker?
# Share the sssd.conf file to shared directory
directory shared_directory_service_dir do
owner 'root'
group 'root'
mode '0600'
recursive true
end
unless node['cluster']['directory_service']['disabled_on_compute_nodes'] == 'true'
execute 'Copy sssd.conf from head node to the shared folder' do
user 'root'
command "cp #{sssd_conf_path} #{shared_sssd_conf_path}"
sensitive true
end unless on_docker?
end
# Share the sssd.conf file with login nodes
directory login_node_shared_directory_service_dir do
owner 'root'
group 'root'
mode '0600'
recursive true
end
execute 'Copy sssd.conf from head node to the shared folder' do
user 'root'
command "cp #{sssd_conf_path} #{login_node_shared_sssd_conf_path}"
sensitive true
end unless on_docker?
bash 'Enable SSH password authentication on head node' do
user 'root'
code <<-AD
sed -ri 's/\s*PasswordAuthentication\s+no$/PasswordAuthentication yes/g' /etc/ssh/sshd_config
AD
end unless on_docker?
# Create directory for tools related to the directory service
directory_service_scripts_path = "#{node['cluster']['scripts_dir']}/directory_service"
directory directory_service_scripts_path do
owner 'root'
group 'root'
mode '0744'
recursive true
end
template "#{directory_service_scripts_path}/update_directory_service_password.sh" do
source 'directory_service/update_directory_service_password.sh.erb'
owner 'root'
group 'root'
mode '0744'
variables(
secret_arn: password_secret_arn_str,
region: region,
shared_sssd_conf_path: shared_sssd_conf_path
)
sensitive true
end
include_recipe 'aws-parallelcluster-environment::configure_pam_ssh_keygen'
when 'LoginNode'
# Login nodes copy sssd.conf from nodes_shared_dir.
execute 'Copy sssd.conf from the shared folder to the login node' do
user 'root'
command "cp #{login_node_shared_sssd_conf_path} #{sssd_conf_path}"
sensitive true
end unless redhat_on_docker?
bash 'Enable SSH password authentication on login node' do
user 'root'
code <<-AD
sed -ri 's/\s*PasswordAuthentication\s+no$/PasswordAuthentication yes/g' /etc/ssh/sshd_config
AD
end unless on_docker?
include_recipe 'aws-parallelcluster-environment::configure_pam_ssh_keygen'
when 'ComputeFleet'
# Compute nodes copy sssd.conf from shared dir.
execute 'Copy sssd.conf from the shared folder to the compute node' do
user 'root'
command "cp #{shared_sssd_conf_path} #{sssd_conf_path}"
sensitive true
end unless on_docker?
else
raise "node_type must be HeadNode, LoginNode or ComputeFleet"
end
system_authentication "Configure system authentication" do
action :configure
end
# Restart modified services
%w(sssd sshd).each do |daemon|
service daemon do
action :restart
sensitive true
end
end unless on_docker?