| @ -0,0 +1,62 @@ | |||
| ## Using INWX DNS for LetsEncrypt domain validation | |||
| ### Install Requirements | |||
| The INWX API Python3 script requires two Python packages: | |||
| ```bash | |||
| pip3 install INWX.Domrobot tldextract | |||
| ``` | |||
| You could install it for the user running getssl, or you could create a python3 venv. | |||
| ```bash | |||
| # install python3 venv apt packages | |||
| sudo apt install python3 python3-venv | |||
| # Create venv | |||
| python3 -m venv venv | |||
| # activate venv | |||
| source venv/bin/activate | |||
| # install requirements | |||
| pip3 install INWX.Domrobot tldextract | |||
| ``` | |||
| If you are installing the Python packages in venv, you should make sure that you either | |||
| you either enable the venv before running getssl, or you | |||
| add the venv to the ``DNS_ADD_COMMAND'' and ``DNS_DEL_COMMAND'' commands. | |||
| See example below. | |||
| ### Enabling the scripts | |||
| Set the following options in `getssl.cfg` (either global or domain-specific): | |||
| ``` | |||
| VALIDATE_VIA_DNS="true" | |||
| DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_inwx.py" | |||
| DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_inwx.py" | |||
| ``` | |||
| If you are using a python3 venv as described above, this is an example of how to include it: | |||
| ``` | |||
| VALIDATE_VIA_DNS="true" | |||
| DNS_ADD_COMMAND="/path/to/venv/bin/python3 /usr/share/getssl/dns_scripts/dns_add_inwx.py" | |||
| DNS_DEL_COMMAND="/path/to/venv/bin/python3 /usr/share/getssl/dns_scripts/dns_del_inwx.py" | |||
| ``` | |||
| *Obviously the "/path/to/venv" needs to be replaced with the actual path to your venv, e.g. "/home/getssl/venv".* | |||
| ### Authentication | |||
| Your INWX credentials will be used to authenticate to INWX. | |||
| If you are using a second factor, please have a look at the [INWX Domrobot Pthon3 Client](https://github.com/inwx/python-client) as it is currently not implemented in the inwx api script. | |||
| Set the following options in the domain-specific `getssl.cfg` or make sure these enviroment variables are present. | |||
| ``` | |||
| export INWX_USERNAME="your_inwx_username" | |||
| export INWX_PASSWORD="..." | |||
| ``` | |||
| @ -0,0 +1,37 @@ | |||
| # Using Route53 BASH scripts for LetsEncrypt domain validation. | |||
| ## Quick guide to setting up getssl for domain validation of Route53 DNS domains. | |||
| There a few prerequisites to using getssl with Route53 DNS: | |||
| 1. You will need to set up an IAM user with the necessary permissions to modify resource records in the hosted zone. | |||
| - route53:ListHostedZones | |||
| - route53:ChangeResourceRecordSets | |||
| 1. You will need the AWS CLI Client installed on your machine. | |||
| 1. You will need to configure the client for the IAM user that has permission to modify the resource records. | |||
| With those in hand, the installation procedure is: | |||
| 1. Open your config file (the global file in ~/.getssl/getssl.cfg | |||
| or the per-account file in ~/.getssl/example.net/getssl.cfg) | |||
| 1. Set the following options: | |||
| - VALIDATE_VIA_DNS="true" | |||
| - DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_route53" | |||
| - DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_route53" | |||
| The AWS CLI profile to use (will use _default_ if not specified) | |||
| - export AWS*CLI_PROFILE="\_profile name*" | |||
| 1. Set any other options that you wish (per the standard | |||
| directions.) Use the test CA to make sure that | |||
| everything is setup correctly. | |||
| That's it. getssl example.net will now validate with DNS. | |||
| There are additional options, which are documented in `dns_route53 -h` | |||
| @ -0,0 +1,93 @@ | |||
| #!/usr/bin/env python3 | |||
| # -*- coding: utf-8 -*- | |||
| """ | |||
| Script to Set TXT Record at INWX using the API | |||
| This script requires the pip packages INWX.Domrobot and tldextract | |||
| This script is using enviroment variables to get inwx credentials | |||
| """ | |||
| import sys | |||
| import os | |||
| import argparse | |||
| from INWX.Domrobot import ApiClient | |||
| import tldextract | |||
| # Create Parser-Objekt | |||
| parser = argparse.ArgumentParser( | |||
| description='Using the INWX API to change DNS TXT Records for the ACME DNS-01 Challange', | |||
| epilog= "The environment variables 'INWX_USERNAME' and 'INWX_PASSWORD' are required too") | |||
| # Adding Args | |||
| parser.add_argument('fulldomain', type=str, help='The full domain to add TXT Record.') | |||
| parser.add_argument('token', type=str, help='The ACME DNS-01 token.') | |||
| parser.add_argument('--debug', action='store_true', help='Enable debug mode.') | |||
| # Parsing Args | |||
| args = parser.parse_args() | |||
| INWX_FULLDOMAIN = args.fulldomain | |||
| ACME_TOKEN = args.token | |||
| DEBUG = args.debug | |||
| # Parsing ENV | |||
| INWX_USERNAME = os.getenv('INWX_USERNAME', '') | |||
| INWX_PASSWORD = os.getenv('INWX_PASSWORD', '') | |||
| # Splitting Domain | |||
| domain = tldextract.extract(INWX_FULLDOMAIN) | |||
| INWX_SUBDOMAIN = domain.subdomain | |||
| INWX_DOMAIN = f"{domain.domain}.{domain.suffix}" | |||
| # Check if either environment variable is empty and handle the error | |||
| if not INWX_USERNAME or not INWX_PASSWORD: | |||
| print("Error: The following environment variables are required and cannot be empty:") | |||
| if not INWX_USERNAME: | |||
| print(" - INWX_USERNAME: Your INWX account username.") | |||
| if not INWX_PASSWORD: | |||
| print(" - INWX_PASSWORD: Your INWX account password.") | |||
| sys.exit(1) | |||
| if DEBUG: | |||
| print(f'FQDN: {INWX_FULLDOMAIN}') | |||
| print(f'Domain: {INWX_DOMAIN}') | |||
| print(f'Subdomain: {INWX_SUBDOMAIN}') | |||
| print(f'Token: {ACME_TOKEN}') | |||
| print(f'User: {INWX_USERNAME}') | |||
| print(f'Password: {INWX_PASSWORD}') | |||
| # By default the ApiClient uses the test api (OT&E). | |||
| # If you want to use the production/live api we have a | |||
| # constant named API_LIVE_URL in the ApiClient class. | |||
| # Just set api_url=ApiClient.API_LIVE_URL and you're good. | |||
| # api_client = ApiClient(api_url=ApiClient.API_OTE_URL, debug_mode=DEBUG) | |||
| api_client = ApiClient(api_url=ApiClient.API_LIVE_URL, debug_mode=DEBUG) | |||
| # If you have 2fa enabled, take a look at the documentation of the ApiClient#login method | |||
| # to get further information about the login, especially the shared_secret parameter. | |||
| login_result = api_client.login(INWX_USERNAME, INWX_PASSWORD) | |||
| # login was successful | |||
| if login_result['code'] == 1000: | |||
| # Make an api call and save the result in a variable. | |||
| # We want to create a new nameserver record, so we call the api method nameserver.createRecord. | |||
| # See https://www.inwx.de/en/help/apidoc/f/ch02s15.html#nameserver.createRecord for parameters | |||
| # ApiClient#call_api returns the api response as a dict. | |||
| if INWX_SUBDOMAIN == '': | |||
| domain_entry_result = api_client.call_api(api_method='nameserver.createRecord', method_params={'domain': INWX_DOMAIN, 'name': '_acme-challenge', 'type': 'TXT', 'content': ACME_TOKEN}) # pylint: disable=C0301 | |||
| else: | |||
| domain_entry_result = api_client.call_api(api_method='nameserver.createRecord', method_params={'domain': INWX_DOMAIN, 'name': f'_acme-challenge.{INWX_SUBDOMAIN}', 'type': 'TXT', 'content': ACME_TOKEN}) # pylint: disable=C0301 | |||
| # With or without successful check, we perform a logout. | |||
| api_client.logout() | |||
| # validating return code | |||
| if domain_entry_result['code'] == 2302: | |||
| sys.exit(f"{domain_entry_result['msg']}.\nTry nameserver.updateRecord or nameserver.deleteRecord instead") # pylint: disable=C0301 | |||
| elif domain_entry_result['code'] == 1000: | |||
| if DEBUG: | |||
| print(domain_entry_result['msg']) | |||
| sys.exit() | |||
| else: | |||
| sys.exit(domain_entry_result) | |||
| else: | |||
| sys.exit('Api login error. Code: ' + str(login_result['code']) + ' Message: ' + login_result['msg']) # pylint: disable=C0301 | |||
| @ -0,0 +1,18 @@ | |||
| #!/bin/bash | |||
| # Add token to Route53 dns using dns_route53 bash version | |||
| fulldomain="$1" | |||
| token="$2" | |||
| [ -z "$ROUTE53_SCRIPT" ] && ROUTE53_SCRIPT="/usr/share/getssl/dns_scripts/dns_route53" | |||
| [[ "$ROUTE53_SCRIPT" =~ ^~ ]] && \ | |||
| eval 'ROUTE53_SCRIPT=`readlink -nf ' $ROUTE53_SCRIPT '`' | |||
| if [ ! -x "$ROUTE53_SCRIPT" ]; then | |||
| echo "$ROUTE53_SCRIPT: not found. Please install, softlink or set ROUTE53_SCRIPT to its full path" | |||
| echo "See ROUTE53-README.txt for complete instructions." | |||
| exit 3 | |||
| fi | |||
| $ROUTE53_SCRIPT -q add "${fulldomain}." "${token}" | |||
| @ -0,0 +1,106 @@ | |||
| #!/usr/bin/env python3 | |||
| # -*- coding: utf-8 -*- | |||
| """ | |||
| Script to remove TXT Record at INWX using the API | |||
| This script requires the pip packages INWX.Domrobot and tldextract | |||
| This script is using enviroment variables to get inwx credentials | |||
| """ | |||
| import sys | |||
| import os | |||
| import argparse | |||
| from INWX.Domrobot import ApiClient | |||
| import tldextract | |||
| # Create Parser-Objekt | |||
| parser = argparse.ArgumentParser( | |||
| description='Using the INWX API to remove DNS TXT Records for the ACME DNS-01 Challange', | |||
| epilog= "The environment variables 'INWX_USERNAME' and 'INWX_PASSWORD' are required too") | |||
| # Adding Args | |||
| parser.add_argument('fulldomain', type=str, help='The full domain to add TXT Record.') | |||
| parser.add_argument('token', type=str, help='The ACME DNS-01 token.') | |||
| parser.add_argument('--debug', action='store_true', help='Enable debug mode.') | |||
| # Parsing Args | |||
| args = parser.parse_args() | |||
| INWX_FULLDOMAIN = args.fulldomain | |||
| ACME_TOKEN = args.token | |||
| DEBUG = args.debug | |||
| # Parsing ENV | |||
| INWX_USERNAME = os.getenv('INWX_USERNAME', '') | |||
| INWX_PASSWORD = os.getenv('INWX_PASSWORD', '') | |||
| # Splitting Domain | |||
| domain = tldextract.extract(INWX_FULLDOMAIN) | |||
| INWX_SUBDOMAIN = domain.subdomain | |||
| INWX_DOMAIN = f"{domain.domain}.{domain.suffix}" | |||
| # Check if either environment variable is empty and handle the error | |||
| if not INWX_USERNAME or not INWX_PASSWORD: | |||
| print("Error: The following environment variables are required and cannot be empty:") | |||
| if not INWX_USERNAME: | |||
| print(" - INWX_USERNAME: Your INWX account username.") | |||
| if not INWX_PASSWORD: | |||
| print(" - INWX_PASSWORD: Your INWX account password.") | |||
| sys.exit(1) | |||
| if DEBUG: | |||
| print(f'FQDN: {INWX_FULLDOMAIN}') | |||
| print(f'Domain: {INWX_DOMAIN}') | |||
| print(f'Subdomain: {INWX_SUBDOMAIN}') | |||
| print(f'Token: {ACME_TOKEN}') | |||
| print(f'User: {INWX_USERNAME}') | |||
| print(f'Password: {INWX_PASSWORD}') | |||
| # By default the ApiClient uses the test api (OT&E). | |||
| # If you want to use the production/live api we have a | |||
| # constant named API_LIVE_URL in the ApiClient class. | |||
| # Just set api_url=ApiClient.API_LIVE_URL and you're good. | |||
| # api_client = ApiClient(api_url=ApiClient.API_OTE_URL, debug_mode=DEBUG) | |||
| api_client = ApiClient(api_url=ApiClient.API_LIVE_URL, debug_mode=DEBUG) | |||
| # If you have 2fa enabled, take a look at the documentation of the ApiClient#login method | |||
| # to get further information about the login, especially the shared_secret parameter. | |||
| login_result = api_client.login(INWX_USERNAME, INWX_PASSWORD) | |||
| # login was successful | |||
| if login_result['code'] == 1000: | |||
| # Make an api call and save the result in a variable. | |||
| # We want to get a the id of the _acme-challange TXT record, | |||
| # so we call the api method nameserver.info. | |||
| # See https://www.inwx.de/en/help/apidoc/f/ch02s15.html#nameserver.info for parameters | |||
| # ApiClient#call_api returns the api response as a dict. | |||
| if INWX_SUBDOMAIN == '': | |||
| domain_info_result = api_client.call_api(api_method='nameserver.info', method_params={'domain': INWX_DOMAIN, 'name': '_acme-challenge', 'type': 'TXT', 'content': ACME_TOKEN}) # pylint: disable=C0301 | |||
| else: | |||
| domain_info_result = api_client.call_api(api_method='nameserver.info', method_params={'domain': INWX_DOMAIN, 'name': f'_acme-challenge.{INWX_SUBDOMAIN}', 'type': 'TXT', 'content': ACME_TOKEN}) # pylint: disable=C0301 | |||
| if 'record' not in domain_info_result['resData']: | |||
| api_client.logout() | |||
| sys.exit(f'No DNS TXT Entry found for _acme-challenge.{INWX_FULLDOMAIN}.') | |||
| else: | |||
| for row in domain_info_result['resData']['record']: | |||
| domain_delete_result = api_client.call_api(api_method='nameserver.deleteRecord', method_params={'id': row['id']}) # pylint: disable=C0301 | |||
| if domain_delete_result['code'] == 1000: | |||
| if DEBUG: | |||
| print(domain_delete_result['msg']) | |||
| else: | |||
| api_client.logout() | |||
| sys.exit(domain_delete_result) | |||
| # With or without successful check, we perform a logout. | |||
| api_client.logout() | |||
| # validating return code | |||
| if domain_info_result['code'] == 2302: | |||
| sys.exit(f"{domain_info_result['msg']}.\nTry nameserver.updateRecord or nameserver.deleteRecord instead") # pylint: disable=C0301 | |||
| elif domain_info_result['code'] == 1000: | |||
| if DEBUG: | |||
| print(domain_info_result['msg']) | |||
| sys.exit() | |||
| else: | |||
| sys.exit(domain_info_result) | |||
| else: | |||
| sys.exit('Api login error. Code: ' + str(login_result['code']) + ' Message: ' + login_result['msg']) # pylint: disable=C0301 | |||
| @ -0,0 +1,18 @@ | |||
| #!/bin/bash | |||
| # Delete token from Route53 dns using dns_route53 bash version | |||
| fulldomain="$1" | |||
| token="$2" | |||
| [ -z "$ROUTE53_SCRIPT" ] && ROUTE53_SCRIPT="/usr/share/getssl/dns_scripts/dns_route53" | |||
| [[ "$ROUTE53_SCRIPT" =~ ^~ ]] && \ | |||
| eval 'ROUTE53_SCRIPT=`readlink -nf ' $ROUTE53_SCRIPT '`' | |||
| if [ ! -x "$ROUTE53_SCRIPT" ]; then | |||
| echo "$ROUTE53_SCRIPT: not found. Please install, softlink or set ROUTE53_SCRIPT to its full path" | |||
| echo "See ROUTE53-README.txt for complete instructions." | |||
| exit 3 | |||
| fi | |||
| $ROUTE53_SCRIPT -q del "${fulldomain}." "${token}" | |||
| @ -0,0 +1,199 @@ | |||
| #!/usr/bin/env bash | |||
| VERSION="1.0" | |||
| PROG="$(basename "$0")" | |||
| QUIET=n | |||
| while getopts 'dhp:t:z:i:qv' opt; do | |||
| case $opt in | |||
| d) DEBUG="Y" ;; | |||
| p) AWS_CLI_PROFILE="$OPTARG" ;; | |||
| q) QUIET= ;; | |||
| v) echo "dns_route53 version $VERSION"; exit 0 ;; | |||
| z) ROUTE53_HOSTED_ZONE_NAME="$OPTARG" ;; | |||
| i) ROUTE53_HOSTED_ZONE_ID="$OPTARG" ;; | |||
| *) | |||
| cat <<EOF | |||
| Usage | |||
| $PROG [-dt -q] add name data [ttl] | |||
| $PROG [-dt -h -p "aws-profile-name" -q] del name data | |||
| Add or delete TXT records from Route53 Hosted Zone | |||
| You must have the AWS CLI installed and a profile configured for this script to work. | |||
| The IAM user that the profile uses requires the following action permissions in AWS: | |||
| - route53:ListHostedZones - Not necessary if zone ID is available to this script | |||
| - route53:ChangeResourceRecordSets | |||
| With getssl, this script is called from the dns_add_route53 and | |||
| dns_del_route53 wrapper scripts. | |||
| Arguments: | |||
| add - add the specified record to the domain | |||
| del - remove the specified record from the domain | |||
| name is the fully qualified record name to create the challenge for e.g. www.example.org. Note that trailing '.' is necessary. Also note that _acme-challenge. will automatically be prepended by this script | |||
| data is the record data, e.g. "myverificationtoken" | |||
| ttl is optional and will default to 120 if not specified | |||
| If it is necessary to turn on debugging externally, define | |||
| ROUTE53_DEBUG="y" (any non-null string will do). | |||
| For minimal trace output (to override -q), define ROUTE53_TRACE="y". | |||
| Options | |||
| -d Provide debugging output - all requests and responses | |||
| -h This help. | |||
| -i: The hosted zone ID | |||
| -p: The AWS CLI profile to use. Will use default if not specified | |||
| -q: Quiet - omit normal success messages | |||
| -z: The hosted zone name. Will be used to determine the zone ID if ID was not provided | |||
| All output, except for this help text, is to stderr. | |||
| Environment variables | |||
| ROUTE53_SCRIPT location of this script | |||
| ROUTE53_HOSTED_ZONE_NAME The name of the hosted zone name. If not specified, then the name will be determined from the record name provided to this script | |||
| ROUTE53_HOSTED_ZONE_ID The id of the hosted zone to be used instead of trying to automatically determine the ID | |||
| AWS_CLI_PROFILE the aws cli profile to use if not using default | |||
| BUGS | |||
| Report any issues to https://github.com/xyide/getssl/issues | |||
| EOF | |||
| exit 0 | |||
| ;; | |||
| esac | |||
| done | |||
| shift $((OPTIND-1)) | |||
| if [ -z "$AWS_CLI_PROFILE" ]; then | |||
| echo "AWS_CLI_PROFILE not defined. Using default" >&2 | |||
| AWS_CLI_PROFILE=default | |||
| fi | |||
| op="$1" | |||
| if ! [[ "$op" =~ ^(add|del)$ ]]; then | |||
| echo "Operation must be \"add\" or \"del\"" >&2 | |||
| exit 3 | |||
| fi | |||
| name="$2" | |||
| if [ -z "$name" ]; then | |||
| echo "'name' parameter is required, see -h" >&2 | |||
| exit 3 | |||
| fi | |||
| data="$3" | |||
| if [ -z "$data" ]; then | |||
| echo "'data' parameter is required, see -h" >&2 | |||
| exit 3 | |||
| fi | |||
| if [ "$op" = 'del' ]; then | |||
| ttl=120 | |||
| elif [ -z "$5" ]; then | |||
| ttl="120" | |||
| elif ! [[ "$5" =~ ^[0-9]+$ ]]; then | |||
| echo "TTL $5 is not numeric" >&2 | |||
| exit 3 | |||
| elif [ "$5" -lt 120 ]; then | |||
| [ -n "$VERB" ] && \ | |||
| echo "$5 is too small. Using TTL of 120 instead" >&2 | |||
| ttl="120" | |||
| else | |||
| ttl="$5" | |||
| fi | |||
| # end processing parameters | |||
| [ -n "$DEBUG" ] && \ | |||
| echo "$PROG: $op $name \"$data\" $ttl" >&2 | |||
| # Determine what actual hosted zone to use. | |||
| HOSTED_ZONE_NAME=$ROUTE53_HOSTED_ZONE_NAME | |||
| HOSTED_ZONE_ID=$ROUTE53_HOSTED_ZONE_ID | |||
| RR_NAME="_acme-challenge.${name}" | |||
| RR_VALUE="${data}" | |||
| # Function to parse through the segments in the supplied name | |||
| # to determine the zone and its id | |||
| function determine_hosted_zone_name_and_id() { | |||
| TMP_NAME=$name | |||
| TMP_RR_NAME= | |||
| while [[ "$TMP_NAME" =~ ^([^.]+)\.([^.]+.*) ]]; do | |||
| if [ -n "${TMP_RR_NAME}" ]; then | |||
| TMP_RR_NAME="${TMP_RR_NAME}."; | |||
| fi | |||
| TMP_RR_NAME="${TMP_RR_NAME}${BASH_REMATCH[1]}" | |||
| testdomain="${BASH_REMATCH[2]}" | |||
| [ -n "$DEBUG" ] && echo "Testing hosted zone ${testdomain}" | |||
| TMP_NAME=$testdomain | |||
| if [[ ! "$TMP_NAME" =~ [^.]+\.[^.]+ ]]; then | |||
| [ -n "$DEBUG" ] && echo "No segments left" | |||
| exit 1 | |||
| fi | |||
| TMP_ZONE_ID=$(aws --profile=${AWS_CLI_PROFILE} route53 list-hosted-zones --query "HostedZones[?Name=='${testdomain}'].Id | [0]" | sed -e 's/^"//' -e 's/"$//') | |||
| if [ "${TMP_ZONE_ID}" != "null" ]; then | |||
| [ -n "$DEBUG" ] && echo "Found hosted zone ${testdomain}" | |||
| HOSTED_ZONE_NAME=${testdomain} | |||
| HOSTED_ZONE_ID=$TMP_ZONE_ID | |||
| break | |||
| fi | |||
| done | |||
| } | |||
| # If zone ID is specified, then use it to determine the hosted zone name | |||
| if [ -n "${HOSTED_ZONE_ID}" ]; then | |||
| HOSTED_ZONE_NAME=$(aws --profile=${AWS_CLI_PROFILE} route53 list-hosted-zones --query "HostedZones[?Id=='${ZONE_ID}'].Name | [0]" | sed -e 's/^"//' -e 's/"$//') | |||
| # If zone name is specified, then use it to get the zone id | |||
| elif [ -n "${HOSTED_ZONE_NAME}" ]; then | |||
| HOSTED_ZONE_ID=$(aws --profile=${AWS_CLI_PROFILE} route53 list-hosted-zones --query "HostedZones[?Name=='${HOSTED_ZONE_NAME}'].Id | [0]" | sed -e 's/^"//' -e 's/"$//') | |||
| else | |||
| determine_hosted_zone_name_and_id | |||
| fi | |||
| if [ -z "${HOSTED_ZONE_ID}" ]; then | |||
| echo "Hosted zone id not specified or determined" >&2 | |||
| exit 3 | |||
| fi | |||
| if [ "$op" = "add" ]; then | |||
| ACTION="UPSERT" | |||
| elif [ "$op" = "del" ]; then | |||
| ACTION="DELETE" | |||
| else | |||
| echo "Unsupported Operation: $op" >&2 | |||
| fi | |||
| CHANGE_BATCH=' | |||
| { | |||
| "Comment": "GetSSL LetsEncrypt DNS Challenge", | |||
| "Changes": [{ | |||
| "Action" : "'"$ACTION"'", | |||
| "ResourceRecordSet" : { | |||
| "Name" : "'"$RR_NAME"'", | |||
| "Type" : "TXT", | |||
| "TTL" : '${ttl}', | |||
| "ResourceRecords" : [{ | |||
| "Value" : "\"'$RR_VALUE'\"" | |||
| }] | |||
| } | |||
| }] | |||
| } | |||
| ' | |||
| [ -n "$DEBUG" ] && echo "${CHANGE_BATCH}" >&2 | |||
| aws \ | |||
| --profile=${AWS_CLI_PROFILE} \ | |||
| route53 \ | |||
| change-resource-record-sets \ | |||
| --hosted-zone-id=${HOSTED_ZONE_ID} \ | |||
| --change-batch "${CHANGE_BATCH}" | |||
| exit $? | |||