mirror of
				https://github.com/coredns/coredns.git
				synced 2025-11-03 02:33:21 -05:00 
			
		
		
		
	feat: dnssec load keys from AWS Secrets Manager Signed-off-by: kcolemangt <20099734+kcolemangt@users.noreply.github.com>
		
			
				
	
	
		
			170 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package dnssec
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"crypto"
 | 
						|
	"crypto/ecdsa"
 | 
						|
	"crypto/rsa"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/coredns/coredns/request"
 | 
						|
 | 
						|
	"github.com/aws/aws-sdk-go-v2/config"
 | 
						|
	"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
 | 
						|
	"github.com/miekg/dns"
 | 
						|
	"golang.org/x/crypto/ed25519"
 | 
						|
)
 | 
						|
 | 
						|
// DNSKEY holds a DNSSEC public and private key used for on-the-fly signing.
 | 
						|
type DNSKEY struct {
 | 
						|
	K   *dns.DNSKEY
 | 
						|
	D   *dns.DS
 | 
						|
	s   crypto.Signer
 | 
						|
	tag uint16
 | 
						|
}
 | 
						|
 | 
						|
// SecretKeyData represents the structure of the DNS keys stored in AWS Secrets Manager.
 | 
						|
type SecretKeyData struct {
 | 
						|
	Key     string `json:"key"`
 | 
						|
	Private string `json:"private"`
 | 
						|
}
 | 
						|
 | 
						|
// ParseKeyFile read a DNSSEC keyfile as generated by dnssec-keygen or other
 | 
						|
// utilities. It adds ".key" for the public key and ".private" for the private key.
 | 
						|
func ParseKeyFile(pubFile, privFile string) (*DNSKEY, error) {
 | 
						|
	f, e := os.Open(filepath.Clean(pubFile))
 | 
						|
	if e != nil {
 | 
						|
		return nil, e
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
	k, e := dns.ReadRR(f, pubFile)
 | 
						|
	if e != nil {
 | 
						|
		return nil, e
 | 
						|
	}
 | 
						|
 | 
						|
	f, e = os.Open(filepath.Clean(privFile))
 | 
						|
	if e != nil {
 | 
						|
		return nil, e
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
 | 
						|
	dk, ok := k.(*dns.DNSKEY)
 | 
						|
	if !ok {
 | 
						|
		return nil, errors.New("no public key found")
 | 
						|
	}
 | 
						|
	p, e := dk.ReadPrivateKey(f, privFile)
 | 
						|
	if e != nil {
 | 
						|
		return nil, e
 | 
						|
	}
 | 
						|
 | 
						|
	if s, ok := p.(*rsa.PrivateKey); ok {
 | 
						|
		return &DNSKEY{K: dk, D: dk.ToDS(dns.SHA256), s: s, tag: dk.KeyTag()}, nil
 | 
						|
	}
 | 
						|
	if s, ok := p.(*ecdsa.PrivateKey); ok {
 | 
						|
		return &DNSKEY{K: dk, D: dk.ToDS(dns.SHA256), s: s, tag: dk.KeyTag()}, nil
 | 
						|
	}
 | 
						|
	if s, ok := p.(ed25519.PrivateKey); ok {
 | 
						|
		return &DNSKEY{K: dk, D: dk.ToDS(dns.SHA256), s: s, tag: dk.KeyTag()}, nil
 | 
						|
	}
 | 
						|
	return &DNSKEY{K: dk, D: dk.ToDS(dns.SHA256), s: nil, tag: 0}, errors.New("no private key found")
 | 
						|
}
 | 
						|
 | 
						|
// ParseKeyFromAWSSecretsManager retrieves and parses a DNSSEC key pair from AWS Secrets Manager.
 | 
						|
func ParseKeyFromAWSSecretsManager(secretID string) (*DNSKEY, error) {
 | 
						|
	// Load the AWS SDK configuration
 | 
						|
	cfg, err := config.LoadDefaultConfig(context.TODO())
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Create a Secrets Manager client
 | 
						|
	client := secretsmanager.NewFromConfig(cfg)
 | 
						|
 | 
						|
	// Retrieve the secret value
 | 
						|
	input := &secretsmanager.GetSecretValueInput{
 | 
						|
		SecretId: &secretID,
 | 
						|
	}
 | 
						|
	result, err := client.GetSecretValue(context.TODO(), input)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Parse the secret string into SecretKeyData
 | 
						|
	var secretData SecretKeyData
 | 
						|
	err = json.Unmarshal([]byte(*result.SecretString), &secretData)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Parse the public key
 | 
						|
	rr, err := dns.NewRR(secretData.Key)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	dk, ok := rr.(*dns.DNSKEY)
 | 
						|
	if !ok {
 | 
						|
		return nil, errors.New("invalid public key format")
 | 
						|
	}
 | 
						|
 | 
						|
	// Parse the private key
 | 
						|
	p, err := dk.ReadPrivateKey(strings.NewReader(secretData.Private), secretID)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Create the DNSKEY structure
 | 
						|
	var s crypto.Signer
 | 
						|
	var tag uint16
 | 
						|
	switch key := p.(type) {
 | 
						|
	case *rsa.PrivateKey:
 | 
						|
		s = key
 | 
						|
		tag = dk.KeyTag()
 | 
						|
	case *ecdsa.PrivateKey:
 | 
						|
		s = key
 | 
						|
		tag = dk.KeyTag()
 | 
						|
	case ed25519.PrivateKey:
 | 
						|
		s = key
 | 
						|
		tag = dk.KeyTag()
 | 
						|
	default:
 | 
						|
		return nil, errors.New("unsupported key type")
 | 
						|
	}
 | 
						|
 | 
						|
	return &DNSKEY{K: dk, D: dk.ToDS(dns.SHA256), s: s, tag: tag}, nil
 | 
						|
}
 | 
						|
 | 
						|
// getDNSKEY returns the correct DNSKEY to the client. Signatures are added when do is true.
 | 
						|
func (d Dnssec) getDNSKEY(state request.Request, zone string, do bool, server string) *dns.Msg {
 | 
						|
	keys := make([]dns.RR, len(d.keys))
 | 
						|
	for i, k := range d.keys {
 | 
						|
		keys[i] = dns.Copy(k.K)
 | 
						|
		keys[i].Header().Name = zone
 | 
						|
	}
 | 
						|
	m := new(dns.Msg)
 | 
						|
	m.SetReply(state.Req)
 | 
						|
	m.Answer = keys
 | 
						|
	if !do {
 | 
						|
		return m
 | 
						|
	}
 | 
						|
 | 
						|
	incep, expir := incepExpir(time.Now().UTC())
 | 
						|
	if sigs, err := d.sign(keys, zone, 3600, incep, expir, server); err == nil {
 | 
						|
		m.Answer = append(m.Answer, sigs...)
 | 
						|
	}
 | 
						|
	return m
 | 
						|
}
 | 
						|
 | 
						|
// Return true if, and only if, this is a zone key with the SEP bit unset. This implies a ZSK (rfc4034 2.1.1).
 | 
						|
func (k DNSKEY) isZSK() bool {
 | 
						|
	return k.K.Flags&(1<<8) == (1<<8) && k.K.Flags&1 == 0
 | 
						|
}
 | 
						|
 | 
						|
// Return true if, and only if, this is a zone key with the SEP bit set. This implies a KSK (rfc4034 2.1.1).
 | 
						|
func (k DNSKEY) isKSK() bool {
 | 
						|
	return k.K.Flags&(1<<8) == (1<<8) && k.K.Flags&1 == 1
 | 
						|
}
 |