plugins icon indicating copy to clipboard operation
plugins copied to clipboard

dns/ddclient: add AWS Route53

Open neclimdul opened this issue 3 years ago • 7 comments

Important notices Before you add a new report, we ask you kindly to acknowledge the following:

  • [x] I have read the contributing guide lines at https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md
  • [x] I have searched the existing issues, open and closed, and I'm convinced that mine is new.
  • [x] The title contains the plugin to which this issue belongs

Describe the bug re-opening #2892 because its still a bug despite the bot's opinion on the subject.

Original summary: Is your feature request related to a problem? Please describe. Legacy plugin set to be deprecated has Route53 as a provider option, but replacement plugin does not.

Describe the solution you'd like Add Route53 as a provider in the GUI.

Describe alternatives you've considered Use the legacy plugin but would like to switch to non-deprecated plugin.

To Reproduce Steps to reproduce the behavior:

  1. Go to 'ui/dyndns'
  2. Click on add button
  3. Service dropdown.

Expected behavior See Route53 or compatible option.

Screenshots image

Environment OPNsense 22.7.2-amd64 FreeBSD 13.1-RELEASE-p1 OpenSSL 1.1.1q 5 Jul 2022

neclimdul avatar Sep 14 '22 14:09 neclimdul

it's not a bug, it's a feature request, likely aimed in the wrong direction https://github.com/ddclient/ddclient/issues/422 https://github.com/awslabs/route53-dynamic-dns-with-lambda/issues/39

The plugin can only offer support for protocols handled by ddclient....

AdSchellevis avatar Sep 14 '22 15:09 AdSchellevis

Ok, I think I understand what you're saying but there's still a disconnect somewhere. Users currently see this: image

But there is no upgrade path which in my reading of things is a bug. Maybe the bug is in the legacy message, hopefully for me the "bug" is that the feature is pending... Somewhere there's a bug though.

neclimdul avatar Sep 14 '22 16:09 neclimdul

also just realized that says "before 22.7 is released" which happened and the plugin still exists so I guess this is actually on someone's radar preventing the plugin's removal :laughing:

neclimdul avatar Sep 14 '22 16:09 neclimdul

https://forum.opnsense.org/index.php?topic=26446.msg142428#msg142428 , upgrades shouldn't remove the old plugin by the way, at some point in time you just can't install it anymore from our repository on new installs (and we won't be offering any support)

AdSchellevis avatar Sep 14 '22 16:09 AdSchellevis

I see. That does mitigate things but it means if I have to rebuild or upgrade my opnsense machine I'll be stranded without the feature or a long night of hacking to manually add it back.

neclimdul avatar Sep 14 '22 16:09 neclimdul

... but it means if I have to rebuild or upgrade my opnsense machine I'll be stranded without the feature or a long night of hacking to manually add it back.

Likely yes, contributing to opensource projects (such as ddclient in this case) might help. Waiting for others to fix your problems just isn't always going to cut it (sometimes you're lucky sometimes you're not).

AdSchellevis avatar Sep 14 '22 16:09 AdSchellevis

LOL no I get that. There's only so many hours in the day to contribute though

neclimdul avatar Sep 14 '22 16:09 neclimdul

This issue has been automatically timed-out (after 180 days of inactivity).

For more information about the policies for this repository, please read https://github.com/opnsense/plugins/blob/master/CONTRIBUTING.md for further details.

If someone wants to step up and work on this issue, just let us know, so we can reopen the issue and assign an owner to it.

OPNsense-bot avatar Mar 13 '23 14:03 OPNsense-bot

Hey, I started work on this issue Friday (9/08/23). I got the signature working without anything outside of the core Perl modules. Going to get the other logic finished up here and make a PR within the next two days. Formulating the signature isn't too bad, however, I don't program in Perl really so please provide me with some guidance once things are posted.

shafferchance avatar Sep 11 '23 04:09 shafferchance

This was added to the native backend already. No need to use Perl/ddclient here. We‘re now using Amazon‘s Python glue.

fichtner avatar Sep 11 '23 04:09 fichtner

Oh I thought I posted this to the ddclient issue sorry

shafferchance-lulu avatar Sep 11 '23 04:09 shafferchance-lulu

EDIT: I just found out that the os-ddclient plugin has two backends and one of them does support AWS Route 53. So ignore the script below since using ddclient is the better option.

Apologies for posting to a closed issue. I thought to assist people affected with a workaround that I am using.

File: aws-dyndns.sh

#!/usr/bin/env bash

# This script is a modified version of the code available here: https://github.com/famzah/aws-dyndns

# Install notes:
# - SSH or open a shell on OPNsense box.
# - pkg install bash
# - mkdir /usr/local/opnsense/scripts/aws-dyndns
# - chmod a+rx /usr/local/opnsense/scripts/aws-dyndns
# - chown root:wheel /usr/local/opnsense/scripts/aws-dyndns
# - Copy this file (aws-dyndns.sh) to /usr/local/opnsense/scripts/aws-dyndns
# - chmod a+rx /usr/local/opnsense/scripts/aws-dyndns/aws-dyndns.sh
# - chown root:wheel /usr/local/opnsense/scripts/aws-dyndns/aws-dyndns.sh
# - Create a configd action file: /usr/local/opnsense/service/conf/actions.d/actions_aws-dyndns.conf
#   with the following contents:
#   ---snip---
#   [update]
#   command:/usr/local/opnsense/scripts/aws-dyndns/aws-dyndns.sh
#   parameters:
#   type:script
#   message:Update AWS Route 53 DNS record
#   description:Update AWS Route 53 DNS record
#   ---snip---
# - chmod a+r /usr/local/opnsense/service/conf/actions.d/actions_aws-dyndns.conf
# - chown root:wheel /usr/local/opnsense/service/conf/actions.d/actions_aws-dyndns.conf
# - service configd restart
# - On web console, go to System > Settings > Cron
# - Add a new job to run the "Update AWS Route 53 DNS record" command when you want

#-------------------------------------------------------------------------------
# Configuration settings:
#-------------------------------------------------------------------------------

# The AWS access key id is the shorter value (20 characters).
AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX

# The AWS secret access key is the longer value, and is more private.
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# The AWS Route 53 Zone ID
AWS_ZONE_ID=XXXXXXXXXXXXXX

# The hostname we wish to update with our public IP.
DNS_FQDN=home.example.com

# DNS TTL - The number of seconds an end-device will cache the record before
# checking with AWS again.
DNS_TTL=300

#-------------------------------------------------------------------------------

set -u

check_system_utility() {
  local TOOL
  for TOOL in "$@" ; do
    if ! type "${TOOL}" &>/dev/null ; then
      echo "Error: Required system utility is missing - ${TOOL}"
      exit 1
    fi
  done
}

validate_ip() {
  [[ $1 =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]
}

# https://stackoverflow.com/a/7385197/185257
hash_hmac_sha256() {
  local KEY="$1"
  local DATA="$2"
  echo -n "${DATA}" | openssl dgst -sha256 -hmac "${KEY}" | cut -d' ' -f2
}

# https://stackoverflow.com/a/22369607/185257
hash_hmac_sha256_keyasbinary() {
  local KEY="$1"
  local DATA="$2"
  echo -n "${DATA}" | openssl dgst -sha256 -mac HMAC -macopt "hexkey:${KEY}" | cut -d' ' -f2
}

aws_xml_payload() {
  local AWS_PAYLOAD="<ChangeResourceRecordSetsRequest xmlns=\"https://${AWS_HOST}/doc/2013-04-01/\">"
        AWS_PAYLOAD+="<ChangeBatch><Comment>DynDNS Update</Comment><Changes><Change>"
        AWS_PAYLOAD+="<Action>UPSERT</Action>"
        AWS_PAYLOAD+="<ResourceRecordSet>"
        AWS_PAYLOAD+="<Name>${DNS_FQDN}</Name>"
        AWS_PAYLOAD+="<Type>A</Type>"
        AWS_PAYLOAD+="<TTL>${DNS_TTL}</TTL>"
        AWS_PAYLOAD+="<ResourceRecords>"
        AWS_PAYLOAD+="<ResourceRecord>"
        AWS_PAYLOAD+="<Value>${NEW_IP}</Value>"
        AWS_PAYLOAD+="</ResourceRecord>"
        AWS_PAYLOAD+="</ResourceRecords>"
        AWS_PAYLOAD+="</ResourceRecordSet>"
        AWS_PAYLOAD+="</Change></Changes></ChangeBatch>"
        AWS_PAYLOAD+="</ChangeResourceRecordSetsRequest>"
  echo "${AWS_PAYLOAD}"
}

aws_xml_payload_hash() {
  local AWS_PAYLOAD="$1"
  echo -n "${AWS_PAYLOAD}" | openssl dgst -sha256 | cut -d' ' -f2
}

aws_canonical() {
  local AWS_PAYLOAD_HASH="$1"
  local AWS_CANONICAL="POST"$'\n'
        AWS_CANONICAL+="/2013-04-01/hostedzone/${AWS_ZONE_ID}/rrset/"$'\n'
        AWS_CANONICAL+=$'\n'
        AWS_CANONICAL+="host:${AWS_HOST}"$'\n'
        AWS_CANONICAL+="x-amz-date:${AWS_DATE}"$'\n'
        AWS_CANONICAL+=$'\n'
        AWS_CANONICAL+="${AWS_SIGNED_HEADERS}"$'\n'
        AWS_CANONICAL+="${AWS_PAYLOAD_HASH}"
  echo "${AWS_CANONICAL}"
}

aws_canonical_hash() {
 local AWS_CANONICAL="$1"
 echo -n "${AWS_CANONICAL}" | openssl dgst -sha256 | cut -d' ' -f2
}

aws_string_to_sign() {
  local AWS_CANONICAL_HASH="$1"
  local AWS_STRING_TO_SIGN="AWS4-HMAC-SHA256"$'\n'
        AWS_STRING_TO_SIGN+="${AWS_DATE}"$'\n'
        AWS_STRING_TO_SIGN+="${AWS_SCOPE_DATE}/${AWS_CLIENT_REGION}/${AWS_SERVICE}/aws4_request"$'\n'
        AWS_STRING_TO_SIGN+="${AWS_CANONICAL_HASH}"
  echo "${AWS_STRING_TO_SIGN}"
}

aws_signature() {
  local AWS_STRING_TO_SIGN="$1"
  local AWS_DATE_KEY AWS_DATE_REGION_KEY AWS_DATE_REGION_SERVICE_KEY
  local AWS_SIGNING_KEY AWS_SIGNATURE

  AWS_DATE_KEY="$(hash_hmac_sha256 "AWS4${AWS_SECRET_ACCESS_KEY}" "${AWS_SCOPE_DATE}")"
  AWS_DATE_REGION_KEY="$(hash_hmac_sha256_keyasbinary "${AWS_DATE_KEY}" "${AWS_CLIENT_REGION}")"
  AWS_DATE_REGION_SERVICE_KEY="$(hash_hmac_sha256_keyasbinary "${AWS_DATE_REGION_KEY}" "${AWS_SERVICE}")"
  AWS_SIGNING_KEY="$(hash_hmac_sha256_keyasbinary "${AWS_DATE_REGION_SERVICE_KEY}" 'aws4_request')"
  AWS_SIGNATURE="$(hash_hmac_sha256_keyasbinary "${AWS_SIGNING_KEY}" "${AWS_STRING_TO_SIGN}")"

  echo "${AWS_SIGNATURE}"
}

AWS_DATE=
AWS_DATE_KEY=
AWS_CLIENT_REGION="us-east-1"
AWS_SERVICE="route53"
AWS_HOST="${AWS_SERVICE}.amazonaws.com"
AWS_SIGNED_HEADERS="host;x-amz-date"

aws_via_curl() {
  local AWS_PAYLOAD AWS_PAYLOAD_HASH AWS_CANONICAL AWS_URL HEADERS
  local AWS_CANONICAL_HASH AWS_STRING_TO_SIGN AWS_SIGNATURE AWS_CREDENTIAL

  AWS_DATE="$(date -u +"%Y%m%dT%H%M%SZ")"
  AWS_SCOPE_DATE="$(echo -n "${AWS_DATE}" | cut -d'T' -f1)"
  AWS_PAYLOAD="$(aws_xml_payload)"
  AWS_PAYLOAD_HASH="$(aws_xml_payload_hash "${AWS_PAYLOAD}")"
  AWS_CANONICAL="$(aws_canonical "${AWS_PAYLOAD_HASH}")"
  AWS_CANONICAL_HASH="$(aws_canonical_hash "${AWS_CANONICAL}")"
  AWS_STRING_TO_SIGN="$(aws_string_to_sign "${AWS_CANONICAL_HASH}")"
  AWS_SIGNATURE="$(aws_signature "${AWS_STRING_TO_SIGN}")"

  AWS_CREDENTIAL="${AWS_ACCESS_KEY_ID}/${AWS_SCOPE_DATE}/${AWS_CLIENT_REGION}/${AWS_SERVICE}/aws4_request"
  AWS_URL="https://${AWS_HOST}/2013-04-01/hostedzone/${AWS_ZONE_ID}/rrset/"

  HEADERS=()
  HEADERS+=("-H" "User-Agent: bash/${BASH_VERSION} $(uname -s)/$(uname -r) OpenSSL/$(openssl version | awk '{print $2}')")
  HEADERS+=("-H" "X-Amz-Date: ${AWS_DATE}")
  HEADERS+=("-H" "Authorization: AWS4-HMAC-SHA256 Credential=${AWS_CREDENTIAL}, SignedHeaders=${AWS_SIGNED_HEADERS}, Signature=${AWS_SIGNATURE}")
  HEADERS+=("-H" "amz-sdk-invocation-id: $(uuidgen -r)")
  HEADERS+=("-H" "amz-sdk-request: attempt=1")

  logger "$(curl -Ss --data "${AWS_PAYLOAD}" "${HEADERS[@]}" -X POST "${AWS_URL}" 2>&1)"
}

#-------------------------------------------------------------------------------

check_system_utility awk cat cut curl date openssl uname uuidgen

logger "Querying OpenDNS name server to get the current IP for ${DNS_FQDN}."
OLD_IP="$(dig +short "${DNS_FQDN}" @resolver1.opendns.com)"

if [[ -n "${OLD_IP}" ]] && ! validate_ip "${OLD_IP}" ; then
  logger "Invalid current IP returned: ${OLD_IP}"
  exit 1
else
  logger "Current IP is: ${OLD_IP}"
fi

logger "Using https://checkip.amazonaws.com/ service to check public IP."
NEW_IP="$(curl -sS --max-time 5 https://checkip.amazonaws.com/)"

if [[ -n "${NEW_IP}" ]] && ! validate_ip "${NEW_IP}" ; then
  logger "Invalid response returned by service: ${NEW_IP}"
  exit 1
else
  logger "Service reports public IP as: ${NEW_IP}"
fi

if [[ "${OLD_IP}" == "${NEW_IP}" ]]; then
  logger "The AWS Route 53 DNS record for ${DNS_FQDN} does not need to be updated."
else
  logger "Updating the AWS Route 53 DNS record for: ${DNS_FQDN}; FROM: ${OLD_IP}; TO: ${NEW_IP}"
  aws_via_curl
fi

onelittlehope avatar Dec 30 '23 19:12 onelittlehope