feat: dnssec load keys from AWS Secrets Manager Signed-off-by: kcolemangt <20099734+kcolemangt@users.noreply.github.com>
169 lines
4.2 KiB
Go
169 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
|
|
}
|