awslabs / route53-dynamic-dns-with-lambda Goto Github PK
View Code? Open in Web Editor NEWA Dynamic DNS system built with API Gateway, Lambda & Route 53.
License: Apache License 2.0
A Dynamic DNS system built with API Gateway, Lambda & Route 53.
License: Apache License 2.0
Hi,
I've an issue with this error message running the bash script :
sh dyn53exec.sh home.example.com. temp "XXX3sd7yh9.execute-api.eu-west-1.amazonaws.com/prod"
{"return_message": "Validation hashes do not match.", "return_status": "fail"}
The config file in S3 has the same shared key: "temp"
After cloning the repo to a cloud9 instance I ran
cdk bootstrap which worked as expected. I then tried
cdk deploy and received the following error:
"[Error at /DyndnsStack/dyndns_fn/Resource] AwsSolutions-L1: The non-container Lambda function is not configured to use the latest runtime version. Use the latest available runtime for the targeted language to avoid technical debt. Runtimes specific to a language or framework version are deprecated when the version reaches end of life. This rule only applies to non-container Lambda functions.
Found errors"
It seems that they just released 3.12 in December. If I update dyndns_stack.py and add:
runtime=lambda_.Runtime.PYTHON_3_12 instead of PYTHON_3_11 then the deploy works.
Nice to have only. I think the Setup_Instructions.md file would benefit from being explicit about the need to publish new versions after updates. I think users not too familiar with Lambda might get in trouble here, in case they need to update the code a few times due to typos or whatever.
Great project! Thanks!
Hello,
when I use the "route53-ddns.yml" and try to deploy it via CloudFormation I get a lot of "Template validation errors".
The positions of the yml file in the AWS error message are the same which are also highlighted when I parse the file in the Online YAML Parser.
Therefore I suppose that the yml file on GitHub has some syntax errors.
Example (row 555):
<!-- blob contrib key: blob_contributors:v21:2e1505b693b28bbccdb187580bf493e6 -->
--> Blank character after colon is not accepted
Best regards,
Daniel
I currently run an older version of this project, which supported public IP, specified IP address, or interface.
--ip-source public | IP | INTERFACE
This arguments defines how to get the IP we update to.
public - use the public IP of the device (default)
IP - use a specific IP passed as argument
INTERFACE - use the IP of an interface passed as argument eg: eth0 eth0.1 or eth0:1
In this current version, within the lambda subdir, index.py has the following comment:
# To disable internal IP find the block of text:
# '# Comment out the following 4 lines to disable internal IP'
# and do that ;)
However the code does not contain this. Would this be a feature request to add back in local IP addressing?
I'm trying to implement this solution on a new Linux2023 AWS system. I was able to successfully complete the "cdk bootstrap" after figuring out all the permissions needed. Now on "cdk deploy" after step 3/8 the stack installation fails and reverts. I've tried changing the nvm version, adding permissions, checking roles..... what could be the problem and how to identify it?
DyndnsStack: creating CloudFormation changeset...
3:49:57 PM | CREATE_FAILED | AWS::Lambda::Function | dyndnsfnA1D8B956
Properties validation failed for resource dyndnsfnA1D8B956 with message:
#/FunctionName: expected minLength: 1, actual: 0
❌ DyndnsStack failed: Error: The stack named DyndnsStack failed creation, it may need to be manually deleted from the AWS console: ROLLBACK_COMPLETE: Properties validation failed for resource dyndnsfnA1D8B956 with message:
#/FunctionName: expected minLength: 1, actual: 0
at FullCloudFormationDeployment.monitorDeployment (/home/ec2-user/.nvm/versions/node/v18.17.1/lib/node_modules/aws-cdk/lib/index.js:443:10236)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Object.deployStack2 [as deployStack] (/home/ec2-user/.nvm/versions/node/v18.17.1/lib/node_modules/aws-cdk/lib/index.js:446:153718)
at async /home/ec2-user/.nvm/versions/node/v18.17.1/lib/node_modules/aws-cdk/lib/index.js:446:137166
❌ Deployment failed: Error: The stack named DyndnsStack failed creation, it may need to be manually deleted from the AWS console: ROLLBACK_COMPLETE: Properties validation failed for resource dyndnsfnA1D8B956 with message:
#/FunctionName: expected minLength: 1, actual: 0
at FullCloudFormationDeployment.monitorDeployment (/home/ec2-user/.nvm/versions/node/v18.17.1/lib/node_modules/aws-cdk/lib/index.js:443:10236)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Object.deployStack2 [as deployStack] (/home/ec2-user/.nvm/versions/node/v18.17.1/lib/node_modules/aws-cdk/lib/index.js:446:153718)
at async /home/ec2-user/.nvm/versions/node/v18.17.1/lib/node_modules/aws-cdk/lib/index.js:446:137166
The stack named DyndnsStack failed creation, it may need to be manually deleted from the AWS console: ROLLBACK_COMPLETE: Properties validation failed for resource dyndnsfnA1D8B956 with message:
#/FunctionName: expected minLength: 1, actual: 0
Hi,
I'm getting a KeyError in lambda_function.py and dynamic_dns_lambda_client.sh is not returning anything.
Any idea where things may have gone wrong?
u'': KeyError
Traceback (most recent call last):
File "/var/task/lambda_function.py", line 243, in lambda_handler
return_dict = run_set_mode(set_hostname, validation_hash, source_ip)
File "/var/task/lambda_function.py", line 133, in run_set_mode
record_config_set = full_config[set_hostname]
KeyError: u''
Hi Sean,
I am wondered with your solution! Just one thing, when i execute locally the script 'route53-ddns-client.sh' works fine, but when i shedule on root crontab, variable 'myPublicIP' don't assign propper values:
myPublicIP=$(curl -q --$ipVersion -s -H "x-api-key: $apiKey" "$myAPIURL?mode=get" | jq -r '.return_message //empty')
And the script fails:
[ -z "$myIp" ] && fail "Couldn't find your public IP"
Because root cron does not load env for 'curl' or 'jq'. You need to add absolute paths for each command. Absolute paths can be different for each OS.
Also maybe packages 'curl' or 'jq' are not present and system fails. Is recomended add some control code like this:
if which curl | grep '/curl' >/dev/null 2>&1
then
printf "=== Found curl command\n"
curl_path=which curl | awk '{print $1}'
else
printf "==== Missing curl command\n"
exit 1
fi 2>/dev/null
if which jq | grep '/jq' >/dev/null 2>&1
then
printf "=== Found jq command\n"
jq_path=which jq | awk '{print $1}'
else
printf "==== Missing jq command\n"
exit 1
fi 2>/dev/null
And remplace all 'curl' calls for variable $curl_path. The same for 'jq' command.
Hi there,
I really like the lab. I ran into an issue while testing the api with the host sharedsecretkey and apiendpointurl. The message is about missing permissions to perform route53:ListResource.
I followed the tutorial and created the role policy with the suggested permissions.
Below is the full stack trace.
Thanks for helping.
{"stackTrace": [["/var/task/lambda_function.py", 243, "lambda_handler", "return_dict = run_set_mode(set_hostname, valid
ation_hash, source_ip)"], ["/var/task/lambda_function.py", 173, "run_set_mode", "'')"], ["/var/task/lambda_function.py"
, 66, "route53_client", "MaxItems='2'"], ["/var/runtime/botocore/client.py", 253, "_api_call", "return self._make_api_c
all(operation_name, kwargs)"], ["/var/runtime/botocore/client.py", 543, "_make_api_call", "raise error_class(parsed_res
ponse, operation_name)"]], "errorType": "ClientError", "errorMessage": "An error occurred (AccessDenied) when calling t
he ListResourceRecordSets operation: User: arn:aws:sts::519768181415:assumed-role/dynamic_dns_lambda_execution_role/dyn
amic_dns_lambda is not authorized to perform: route53:ListResourceRecordSets on resource: arn:aws:route53:::hostedzone/
ZFZVNFRHXMOIV"}
Hello,
I noticed that the 3 components of the hashed string are pasted one after the other.
I imagined that someone without the knowledge of the shared key would be able to insert an information into the DNS :
Query that the attacker sees -
IP 111.111.111.111 - https://MY_API_ID.execute-api.us-west-2.amazonaws.com/prod?mode=set&hostname=host1.dyn.example.com&hash=96772404892f24ada64bbc4b92a0949b25ccc703270b1f6a51602a1059815535
Query that the attacker is able to execute -
IP 111.111.111.11 - https://MY_API_ID.execute-api.us-west-2.amazonaws.com/prod?mode=set&hostname=1host1.dyn.example.com&hash=96772404892f24ada64bbc4b92a0949b25ccc703270b1f6a51602a1059815535
I admit that is very unlikely, but I thought it was worth mentioning.
In order to prevent this case, I would suggest to reorder the 3 components this way : $IP$shared_secret$hostname
Best Regards,
Yohann Sillam
Is this compatible with the standard DNS clients you would find in things like routers and DVRs?
Not sure if dynamo supported pay per request when you wrote this, but how do you feel about changing the billing mode to
BillingMode: PAY_PER_REQUEST
If you are happy can make a pull request
username:~/environment/route53-dynamic-dns-with-lambda (master) $ python3 newrecord.py
Traceback (most recent call last):
File "newrecord.py", line 2, in <module>
import boto3
ModuleNotFoundError: No module named 'boto3'
username:~/environment/route53-dynamic-dns-with-lambda (master) $ pip3 install boto3
I see that the example uses a separate hosted zone as a subdomain delegated from the "main" hosted zone. Is there any reson why not to use the initial hosted zone? In the example why not use "example.com" hosted zone and avoid the 'dance' creating a new hosted zone and delegating a record?
Hi, thank you for your work. I was able to get this installed in the enterprise, but having to spin up a separate Ubuntu VM for the function was a pain point. It would be nice to have a version with a simplified hash that is static and doesn't include the IP address. This would allow a single curl to be used instead of a bash script, lending support to the Windows task scheduler and simple Unix hosts without jq installed.
When trying to deploy a version of the Python3 branch using the provided yml I get the following error after the 1hr timeout:
Logial ID: setupSourceToS3
Status: CREATE_FAILED
Status reason: Custom Resource failed to stabilize in expected time
Any ideas?
Is it possible to use a wildcard in the config file?
For example:
"*.dyn.example.com."
When I used your cloud formation template, I was getting the client to hit the stack as intended, and I got back a success message, but when I checked, the route 53 entry remained unchanged, as well as the set IP in DDB. The only item updated was the last_accessed field.
I assumed the issue was in my implementation, and began to reverse engineer how you intended it to function. When I looked at how lambda interacts with DDB and R53 to check and change records, I found a bug.
Line 291 of the DDNS Lambda index.py sets the action as a variable, Line 301 redefines it. Executing the action instead of setting it within a variable corrects the issue I experienced.
`` return_status = route53_set(
'set_record',
aws_region,
route_53_zone_id,
set_hostname,
route_53_record_ttl,
route_53_record_type,
set_ip)
# Update DynamoDB with IP and update timestamp
ddb_config(set_hostname, route_53_record_type, set_ip, source_ip, 'write')
return_status = 'success'
return_message = 'Your hostname record ' + set_hostname +
' has been set to ' + set_ip
return {'return_status': return_status,
'return_message': return_message}
Lambda was created with a timeout of 3 seconds, causing every request to fail with a 502 status.
e94af20e-24ef-4a6e-8d4b-db09a9a705b0 Task timed out after 3.02 seconds
After manualy increasing timeout to 10 seconds, requests were all successful, finishing in just under 4 seconds
22f4200d-856a-41b5-9276-29d48412c78f Duration: 3757.18 ms Billed Duration: 3758 ms Memory Size: 128 MB Max Memory Used: 81 MB
It would be really nice to be able to support ddclient, so far I was not able to use your solution and had opted to make my own API Gateway / Lambda solution to mimic dyndns2 (https://help.dyn.com/remote-access-api/perform-update/) protocol so my retail router (EdgeRouter 4) could easily support my custom API just changing the server parameter of ddclient. But if this solution had a working ddclient config I think your solution would probably be better.
The code in this repository is broken because it is not up-to-date with the latest lambda cloudformation. I found the following fork that seems to work: https://github.com/gfitzp/route53-dynamic-dns-with-lambda
Is it possible to merge this fix into this main repo, please? It took me hours to find this solution...
Thanks!
Peter
Would it be possible to change this template to allow record changes in multiple hosted zones?
Two use cases I can think of:
Hey, thanks for all your work here in getting this put together. I used v1 unsuccessfully but had much better luck with v2. I was however running into an issue getting the script to find my public IP. This is running on a MacOS Mojave work station and my public IP resides two hops upstream. I replaced line 163 with "myPublicIP=$(curl ifconfig.me)" which was successful in retrieving the IP from my internet router. Additionally, the very last piece of selection logic fails to get the intended echo. I deleted it so the script runs without any validation message as opposed to the error.
I cannot successfully invoke the Bash client. It always fails with a
route53-ddns-client.sh: Request failed:
error
Digging deeper and invoking curl against the API directly, yields a more detailed error:
{"stackTrace": [["/var/task/index.py", 355, "lambda_handler", "return_dict = run_set_mode(set_hostname, record_type, validation_hash, source_ip, set_ip)"], ["/var/task/index.py", 213, "run_set_mode", "lock_record = record_config_set['Item']['lock_record']"]], "errorType": "KeyError", "errorMessage": "'lock_record'"}
Modifying the Bash script to not pipe the error through JQ yields the exact same error.
The AWS Console was used to set everything up and the chosen options were:
Is it something I missed in the setup or invocation of the client?
Hi, thanks for developing this tool. It works well and was easy to set up the service.
Finding out how to easily update the entries was a little bit more difficult, since the docs didn't mention anything but the basic curl commands needed.
(Edit: oh wait, the introduction clearly mentions "A bash reference client route53-ddns-client.sh is included".. that's my bad for skipping straight to the setup guide)
Fortunately the route53-ddns-client.sh was right there, and it worked well. To easily deploy that to my server, I tried to find a Docker container using that script, but didn't find one, so I decided to make and publish my own, which can be found here:
https://hub.docker.com/r/bartvhdev/route53-ddns-client
Just wanted to let you know, in case this can be of use to anybody else.
Hello,
It seems that some (not all) requests to the lambda function get base64 encoded, breaking the function and causing a 502 error.
I've done some digging and found that requests that work are like this:
{
...
"body": "{\"execution_mode\": \"get\"}",
"isBase64Encoded": "False"
}
But sometimes instead i get this:
{
...
"body": "J3tleGVjdXRpb25fbW9kZTpnZXR9Jw==",
"isBase64Encoded": "True"
}
If i add some code to decode that base64, it returns invalid json:
'{execution_mode:get}'
Thanks so much for this! This is awesome and some of the easiest steps to follow I've ever went through.
Unless I missed it, nothing in the documentation state to remove the "https://" from the Invoke URL. I merely went on my way and kept getting a silent failure until I looked around at what was going on. Perhaps add error handling there or update the documentation.
Happy to work on this if you want. Let me know how you want it handled.
username:~/environment/route53-dynamic-dns-with-lambda (master)$ ./dyndns.sh -m set -u https://<endpoint>.lambda-url.us-east-1.on.aws/ -h example.dyn.domain.com -s shared_secret
./dyndns.sh: line 62: shasum: command not found
{"return_status": "fail", "return_message": "You must pass a valid sha256 hash in the hash= argument."},{"status_code":"400"}
username:~/environment/route53-dynamic-dns-with-lambda (master)$ yum update
username:~/environment/route53-dynamic-dns-with-lambda (master)$ yum install perl-Digest-SHA
I was testing out the dynamic dns functionality provided by this project and it worked smoothly, kudos to you guys!
But now I am done testing it out and I would like to delete it-- and this is where I think I did something wrong.
I had noticed this project created a DynamoDB and a Lambda function. I deleted those first and then the cloud-9 project and its EC2 instance with the assumption that that would be "enough".
But then I noticed it ALSO created a cloudformation stack. To be honest, I hadn't read carefully and just assumed the project used AWS cli to set everything up conveniently from a cloud-9 project. Sorry to say, I don't really know anything about cloudformation (other than it's a very complex tool to setup AWS infrastructure).
When I try to delete the cloudformation stack, it gives a "DELETE_FAILED" error with some message about a role:
Role arn:aws:iam::999999999999:role/cdk-hnb9999ds-cfn-exec-role-999999999-us-east-2 is invalid or cannot be assumed
Here's a screenshot of the application manager:
Looking in the application manager, I see that it STILL lists a DynamoDB table and Lambda function. When I click on their links, these resources are no longer present (I had deleted them after all). There's also an IAM role in there, I can't tell if it refers to the same role in the error message (it's not listed as an arn URL).
I tried finding the IAM role listed in the error message (could not), tried switching to admin and root role to delete (didn't work). I also found this information, but I wasn't able to identify the right role from looking at the arn url in the message. I ended up just selecting "AWSCloudFormationFullAccess" role for my user (also didn't work).
I can't find a way to delete this stack and now I am stuck. It's not a big deal but I don't like having stuff like this hanging around.
So 3 questions:
Thank you for your patience !!
Hello,
I have deployed the Version 2 on my Aws account.
Everything went well apart of the lambda function which is not responding.
i have this error when trying to reach the api.
Failed to connect to ########.execute-api.eu-west-2.amazonaws.com port 80: Connection refused
how can I manage to have the https service state or logs of the lambda function?
Hi,
I followed the guide step by step but I'm getting the following permission error. It looks like there's a problem between the IAM role and the route53 resource:
{"stackTrace": [["/var/task/lambda_function.py", 243, "lambda_handler", "return_dict = run_set_mode(set_hostname, validation_hash, source_ip)"], ["/var/task/lambda_function.py", 173, "run_set_mode", "'')"], ["/var/task/lambda_function.py", 66, "route53_client", "MaxItems='2'"], ["/var/runtime/botocore/client.py", 251, "_api_call", "return self._make_api_call(operation_name, kwargs)"], ["/var/runtime/botocore/client.py", 537, "_make_api_call", "raise ClientError(parsed_response, operation_name)"]], "errorType": "ClientError", "errorMessage": "An error occurred (AccessDenied) when calling the ListResourceRecordSets operation: User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/dynamic_dns_lambda_execution_role/dynamic_dns_lambda is not authorized to perform: route53:ListResourceRecordSets on resource: arn:aws:route53:::hostedzone/xxxxxxxxxxxxxx"}
Can you please shed some light on why this is happening?
Thanks,
Marco
When I try to run the client script, I see this error in cloudwatch:
'execution_mode': KeyError
Traceback (most recent call last):
File "/var/task/lambda_function.py", line 220, in lambda_handler
execution_mode = event['execution_mode']
KeyError: 'execution_mode'
I am trying to use the client script in Mac OS (High Sierra) and the hash generated is wrong. On debugging I figured it is due to using of echo -n to generate the hash, replacing echo -n with printf fixes the issues.
Seems that AWS Lambda Mapping Templates has changed after you created the document. This step:
Click the pencil next to ‘Input Passthrough’
does not work anymore since 'Input Passthrough’ is not there.There is this option instead:
Generate template: Method Request passthrough
Text field is prefilled but if you empty it and copy-paste ‘api_mapping_template’ file into it, then it seems to work.
Thanks for the good instructions!
Hi there,
while having this up and running since quite a time, I recently took over this setup in our corporation.
For us it seemed, that the ddns-client.sh quitted with the following output (redacted)
route53-ddns-client.sh: Updating subdomain.domain.tld. to IP 111.111.111.111
route53-ddns-client.sh: Request failed:
It worked for quite a while, but I don't know when it stopped working exactly.
I checked config and codebase, no changes or adaptions notified.
Then I took a look into the CloudWatch logs for the lambda and found the following stacktrace:
Unicode-objects must be encoded before hashing: TypeError
Traceback (most recent call last):
File "/var/task/lambda_function.py", line 258, in lambda_handler
return_dict = run_set_mode(set_hostname, validation_hash, source_ip, internal_ip)
File "/var/task/lambda_function.py", line 168, in run_set_mode
source_ip + set_hostname + shared_secret).hexdigest()
TypeError: Unicode-objects must be encoded before hashing
I then went ahead and patched line 167/168 by changing it to
calculated_hash = hashlib.sha256(( source_ip + set_hostname + shared_secret).encode()).hexdigest()
resulting in working updates again.
We don't have any special characters in our hostname btw.
Anybody else, who encountered this issue?
Thanks a lot 👍
Following through the steps, I may have missed something, but when I ran the client script at the end, I was getting "There was an issue finding or reading the S3 config file." I made the JSON config public, and that corrected the problem, but I am betting that's not the correct action, since that makes the secret publicly available. Is there a step I missed?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.