Add r53 command
This commit is contained in:
+175
@@ -0,0 +1,175 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json/jsontext"
|
||||||
|
"encoding/json/v2"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/config"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/route53"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/route53/types"
|
||||||
|
|
||||||
|
"code.chimeric.al/dwrz/r53/pkg/digip"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ttl = flag.Int64("ttl", 300, "TTL of the DNS record; seconds")
|
||||||
|
zone = flag.String("zone", "", "hosted zone id")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
var log = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
|
AddSource: true,
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Setup the main context.
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := run(ctx, log); err != nil {
|
||||||
|
log.Error("failed", slog.Any("error", err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(ctx context.Context, log *slog.Logger) error {
|
||||||
|
if *zone == "" {
|
||||||
|
return fmt.Errorf("missing hosted zone id")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the AWS configuration.
|
||||||
|
awscfg, err := config.LoadDefaultConfig(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to retrieve AWS config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Route53 client.
|
||||||
|
var svc = route53.NewFromConfig(awscfg)
|
||||||
|
|
||||||
|
// Get the IP address.
|
||||||
|
var ip4, ip6 string
|
||||||
|
|
||||||
|
// Dig with OpenDNS.
|
||||||
|
if ip, err := digip.OpenDNS(ctx, digip.A); err != nil {
|
||||||
|
log.ErrorContext(
|
||||||
|
ctx,
|
||||||
|
"failed to get opendns ipv4 ip",
|
||||||
|
slog.Any("error", err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ip4 = ip.String()
|
||||||
|
}
|
||||||
|
if ip, err := digip.OpenDNS(ctx, digip.AAAA); err != nil {
|
||||||
|
log.ErrorContext(
|
||||||
|
ctx,
|
||||||
|
"failed to get opendns ipv6 ip",
|
||||||
|
slog.Any("error", err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ip6 = ip.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback on Google.
|
||||||
|
if ip4 == "" {
|
||||||
|
if ip, err := digip.Google(ctx, digip.V4); err != nil {
|
||||||
|
log.ErrorContext(
|
||||||
|
ctx,
|
||||||
|
"failed to get google ipv4 ip",
|
||||||
|
slog.Any("error", err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ip4 = ip.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ip6 == "" {
|
||||||
|
if ip, err := digip.Google(ctx, digip.V6); err != nil {
|
||||||
|
log.ErrorContext(
|
||||||
|
ctx,
|
||||||
|
"failed to get google ipv6 ip",
|
||||||
|
slog.Any("error", err),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ip6 = ip.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort if we don't have any IP address.
|
||||||
|
if ip4 == "" && ip6 == "" {
|
||||||
|
return fmt.Errorf("failed to retrieve public ip")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the domains.
|
||||||
|
for _, domain := range flag.Args() {
|
||||||
|
// Assemble the change(s).
|
||||||
|
var changes []types.Change
|
||||||
|
if ip4 != "" {
|
||||||
|
change := types.Change{
|
||||||
|
Action: types.ChangeActionUpsert,
|
||||||
|
ResourceRecordSet: &types.ResourceRecordSet{
|
||||||
|
Name: &domain,
|
||||||
|
ResourceRecords: []types.ResourceRecord{
|
||||||
|
{Value: &ip4},
|
||||||
|
},
|
||||||
|
TTL: ttl,
|
||||||
|
Type: types.RRTypeA,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
changes = append(changes, change)
|
||||||
|
}
|
||||||
|
if ip6 != "" {
|
||||||
|
change := types.Change{
|
||||||
|
Action: types.ChangeActionUpsert,
|
||||||
|
ResourceRecordSet: &types.ResourceRecordSet{
|
||||||
|
Name: &domain,
|
||||||
|
ResourceRecords: []types.ResourceRecord{
|
||||||
|
{Value: &ip6}},
|
||||||
|
TTL: ttl,
|
||||||
|
Type: types.RRTypeAaaa,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
changes = append(changes, change)
|
||||||
|
}
|
||||||
|
|
||||||
|
input := &route53.ChangeResourceRecordSetsInput{
|
||||||
|
ChangeBatch: &types.ChangeBatch{
|
||||||
|
Changes: changes,
|
||||||
|
Comment: aws.String("DDNS"),
|
||||||
|
},
|
||||||
|
HostedZoneId: zone,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := svc.ChangeResourceRecordSets(ctx, input)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update route 53: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outcome, err := json.Marshal(result, jsontext.WithIndent(" "))
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorContext(
|
||||||
|
ctx,
|
||||||
|
"failed to unmarshal response",
|
||||||
|
slog.Any("error", err),
|
||||||
|
)
|
||||||
|
|
||||||
|
outcome = []byte(result.ChangeInfo.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.DebugContext(
|
||||||
|
ctx,
|
||||||
|
"updated route 53 records",
|
||||||
|
slog.String("domain", domain),
|
||||||
|
slog.Any("outcome", outcome),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user