dynamic53

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2024 License: MIT Imports: 19 Imported by: 0

README

dynamic53 - Dynamic DNS for AWS Route 53

GitHub License Go Reference

A DNS update client that runs as a daemon and outputs structured logs. Comes with a tool to generate a restrictive identity-based IAM policy based on its configuration.

Installation

Install the dynamic53 IAM policy generator:

go install github.com/Preston12321/dynamic53/cmd/d53policy@latest

Install the dynamic53 daemon:

go install github.com/Preston12321/dynamic53/cmd/dynamic53@latest

Configuration

The config file format looks like the below example:

# Tells dynamic53 to skip all API calls to AWS and simply print a log line
# instead. Only really useful for development testing
skipUpdate: true # Optional, defaults to false

# Configures the frequency at which dynamic53 polls for changes to its public
# IPv4 address and sends those updates to Route 53. You can disable jitter by
# setting it to 0s, but that's bad manners, so you probably shouldn't
polling:
  interval: 5m
  maxJitter: 1s

zones:
    # The name field is optional when id is set, but will be sanity checked
    # against the name returned by AWS before making changes to the zone
  - name: example.com
    id: Z0123456789ABCDEFGHIJ
    # You can list up to 1000 records to manage per zone. Any A records that
    # don't exist will be created. The TTL on each record is set to the polling
    # interval configured above
    records:
      - example.com
      - test.example.com
    # Zones can be identified by either their ID or their name, but it's
    # recommended to always use the ID. If a zone is missing an ID, the
    # d53policy tool can't generate an IAM policy for it
  - id: Z012345GHIJ6789ABCDEF
    records:
      - foo.example.net

How to use

Just pass the path to a config file and you're off to the races!

Generate a strictly-scoped identity-based IAM policy to grant an AWS user or role permissions to manage the DNS zones and records specified in the given config:

>> d53policy -config my-config.yaml > policy.json
>> cat policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowListingZones",
            "Effect": "Allow",
            "Action": "route53:ListHostedZonesByName",
            "Resource": "*"
        },
        {
            "Sid": "AllowGettingZonesAndChanges",
            "Effect": "Allow",
            "Action": [
                "route53:GetChange",
                "route53:GetHostedZone"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/Z0123456789ABCDEFGHIJ",
                "arn:aws:route53:::hostedzone/Z012345GHIJ6789ABCDEF",
                "arn:aws:route53:::change/*"
            ]
        },
        {
            "Sid": "AllowEditingRecords0",
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/Z0123456789ABCDEFGHIJ"
            ],
            "Condition": {
                "ForAllValues:StringEquals": {
                    "route53:ChangeResourceRecordSetsNormalizedRecordNames": [
                        "example.com",
                        "test.example.com"
                    ],
                    "route53:ChangeResourceRecordSetsRecordTypes": [
                        "A"
                    ],
                    "route53:ChangeResourceRecordSetsActions": [
                        "CREATE",
                        "UPSERT"
                    ]
                }
            }
        }
        {
            "Sid": "AllowEditingRecords1",
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/Z012345GHIJ6789ABCDEF"
            ],
            "Condition": {
                "ForAllValues:StringEquals": {
                    "route53:ChangeResourceRecordSetsNormalizedRecordNames": [
                        "foo.example.net"
                    ],
                    "route53:ChangeResourceRecordSetsRecordTypes": [
                        "A"
                    ],
                    "route53:ChangeResourceRecordSetsActions": [
                        "CREATE",
                        "UPSERT"
                    ]
                }
            }
        }
    ]
}

Attach the policy generated by d53policy to the AWS user or role that will be used by the dynamic53 daemon.

Start the dynamic53 daemon:

>> dynamic53 -config my-config.yaml
{"level":"info","pollingInterval":"5m0s","time":"2024-09-24T22:37:40-05:00","message":"Starting dynamic53 daemon"}
{"level":"info","ipv4":"52.94.76.112","time":"2024-09-24T22:37:41-05:00","message":"Retrieved current public address"}
{"level":"info","ipv4":"52.94.76.112","zoneId":"Z012345GHIJ6789ABCDEF","zoneName":"","time":"2024-09-24T22:37:41-05:00","message":"Skipping hosted zone update"}
{"level":"info","ipv4":"52.94.76.112","zoneId":"Z0123456789ABCDEFGHIJ","zoneName":"example.com","time":"2024-09-24T22:37:41-05:00","message":"Skipping hosted zone update"}
...

Using as a library

The functionality of this module is available as a Go library:

go get github.com/Preston12321/dynamic53

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrTooManyRecords error = fmt.Errorf("hosted zones with more than %d resource records are unsupported", MaxRecordsPerZone)

ErrTooManyRecords signifies that a hosted zone contains more resource records than the supported limit, MaxRecordsPerZone.

View Source
var IAM_POLICY_TEMPLATE string
View Source
var MaxRecordsPerZone int32 = 300

MaxRecordsPerZone is the maximum number of records per zone supported by dynamic53. Neither the configuration passed to dynamic53 nor the actual hosted zone in Route 53 may contain more records than this limit. Its value comes from the maximum number of records that the Route 53 API is willing to return for a single ListResourceRecordSets request.

Determining the set of records that need to be sent on a given update pass requires pulling down the entire list of records in that zone in order to diff it with the desired state. If dynamic53 were to support pagination of the ListResourceRecordSets API during this operation, it could result in up to 34 requests, given the maximum number of records allowed by AWS in a hosted zone is 10,000. To keep this simple and performant, pagination is not supported and the MaxRecordsPerZone limit is enforced.

Functions

func GenerateIAMPolicy

func GenerateIAMPolicy(cfg DaemonConfig) (string, error)

GenerateIAMPolicy returns a JSON string representing an identity-based AWS IAM policy granting the necessary permissions for a dynamic53 client to manage the zones and records specified in the given configuration.

func ShortenDNSName

func ShortenDNSName(name string) string

ShortenDNSName returns the given DNS name without a trailing dot.

func ShortenZoneId

func ShortenZoneId(id string) string

ShortenZoneId returns the given hosted zone ID without its optional prefix.

Types

type AddressClient

type AddressClient struct {
	Url string
	// contains filtered or unexported fields
}

AddressClient retrieves information about the host's IP address.

func NewAddressClient

func NewAddressClient(url string) AddressClient

func (AddressClient) GetPublicIPv4

func (c AddressClient) GetPublicIPv4(ctx context.Context) (net.IP, error)

GetPublicIPv4 attempts to determine the current public IPv4 address of the host by making a request to an external third-party API.

type Daemon

type Daemon struct {
	// Config controls the dynamic53 daemon's behavior.
	Config DaemonConfig
	// contains filtered or unexported fields
}

Daemon manages Route53 resources.

func NewDaemon

func NewDaemon(config DaemonConfig, route53Client *route53.Client) *Daemon

func (*Daemon) Start

func (d *Daemon) Start(ctx context.Context)

Start begins the dynamic53 daemon's poll-and-update loop.

type DaemonConfig

type DaemonConfig struct {
	// SkipUpdate specifies that a daemon should skip sending Route 53 updates
	// to the AWS API, printing a log message for each configured zone instead.
	SkipUpdate bool `yaml:"skipUpdate"`

	// Polling contains the configuration for IP address polling.
	Polling PollingConfig `yaml:"polling"`

	// Zones is a slice containing the configuration for each Route 53 hosted
	// zone that should be managed by dynamic53.
	Zones []ZoneConfig `yaml:"zones"`
}

DaemonConfig holds the top-level configuration data for the dynamic53 daemon.

func LoadDaemonConfig

func LoadDaemonConfig(from io.Reader) (*DaemonConfig, error)

LoadDaemonConfig reads and parses a configuration from the given io.Reader.

func (DaemonConfig) Validate

func (c DaemonConfig) Validate() error

type PollingConfig

type PollingConfig struct {
	// Interval is the interval at which the daemon should poll for changes to
	// the host's public IP address.
	Interval time.Duration `yaml:"interval"`

	// MaxJitter is the maximum amount of time the daemon should randomly choose
	// to wait before polling on a given iteration. Set to zero to disable
	// jitter (this is bad practice, so don't do it unless you have good
	// reason).
	MaxJitter time.Duration `yaml:"maxJitter"`

	// Url is the resource that dynamic53 should poll to determine the host's
	// IPv4 address. If empty, dynamic53 defaults to the ipinfo.org API.
	Url string `yaml:"url"`
}

PollingConfig holds the configuration date for the dynamic53 daemon's IP address polling behavior.

func (PollingConfig) Validate

func (c PollingConfig) Validate() error

type ZoneConfig

type ZoneConfig struct {
	// Name is the name given to the Route53 hosted zone. Either Name or Id is
	// required. If Id is specified, this field is ignored.
	Name string `yaml:"name"`

	// Id is the AWS-assigned ID of the Route53 hosted zone. Either Name or Id
	// is required. Overrides the Name field if present.
	Id string `yaml:"id"`

	// Records is a slice containing the DNS A records that dynamic53 should
	// manage in this hosted zone.
	Records []string `yaml:"records"`
}

ZoneConfig holds the configuration data for a single Route 53 hosted zone.

func (ZoneConfig) Validate

func (c ZoneConfig) Validate() error

type ZoneUtility

type ZoneUtility struct {
	// contains filtered or unexported fields
}

ZoneUtility provides a high-level set of convenience functions to manage a hosted zone.

func NewZoneUtility

func NewZoneUtility(route53Client *route53.Client) ZoneUtility

func (ZoneUtility) ApplyChangeBatch

func (u ZoneUtility) ApplyChangeBatch(ctx context.Context, zone *types.HostedZone, batch *types.ChangeBatch) error

ApplyChangeBatch applies the given changes to the hosted zone, blocking until those changes have completed.

func (ZoneUtility) GetChangesForZone

func (u ZoneUtility) GetChangesForZone(ctx context.Context, zone *types.HostedZone, records []string, ttl int64, ipv4 net.IP) (*types.ChangeBatch, error)

GetChangesForZone computes the changes necessary for all specified A records in the given hosted zone to reflect the specified IPv4 address with the given TTL.

func (ZoneUtility) HostedZoneFromConfig

func (u ZoneUtility) HostedZoneFromConfig(ctx context.Context, zone ZoneConfig) (*types.HostedZone, error)

HostedZoneFromConfig retrieves informaton on the hosted zone specified by the given configuration. If the Id is defined, the lookup is done based on that, with a cross-check of the configured Name if it is also defined. Otherwise, the Name is used for the lookup.

func (ZoneUtility) HostedZoneFromId

func (u ZoneUtility) HostedZoneFromId(ctx context.Context, id string) (*types.HostedZone, error)

HostedZoneFromId retrieves informaton on the hosted zone with the given id.

func (ZoneUtility) HostedZoneFromName

func (u ZoneUtility) HostedZoneFromName(ctx context.Context, dnsName string) (*types.HostedZone, error)

HostedZoneFromName retrieves informaton on the hosted zone with the given name.

func (ZoneUtility) VerifyChangeHasPropagated

func (u ZoneUtility) VerifyChangeHasPropagated(ctx context.Context, changeId string) (bool, error)

VerifyChangeHasPropagated returns a boolean describing whether the hosted zone change corresponding to the given changeId has completed propagation.

func (ZoneUtility) WaitForChange

func (u ZoneUtility) WaitForChange(ctx context.Context, changeId string) error

WaitForChange blocks until the hosted zone change corresponding to the given changeId has completed.

Directories

Path Synopsis
cmd
d53policy command
dynamic53 command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL