mirror of
https://github.com/coredns/coredns.git
synced 2025-11-01 10:43:17 -04:00
feat: dnssec load keys from AWS Secrets Manager (#6618)
feat: dnssec load keys from AWS Secrets Manager Signed-off-by: kcolemangt <20099734+kcolemangt@users.noreply.github.com>
This commit is contained in:
@@ -16,7 +16,7 @@ This plugin can only be used once per Server Block.
|
||||
|
||||
~~~
|
||||
dnssec [ZONES... ] {
|
||||
key file KEY...
|
||||
key file|aws_secretsmanager KEY...
|
||||
cache_capacity CAPACITY
|
||||
}
|
||||
~~~
|
||||
@@ -49,6 +49,26 @@ used.
|
||||
* generated public key `Kexample.org+013+45330.key`
|
||||
* generated private key `Kexample.org+013+45330.private`
|
||||
|
||||
* `key aws_secretsmanager` indicates that **KEY** secret(s) should be read from AWS Secrets Manager. Secret
|
||||
names or ARNs may be used. After generating the keys as described in the `key file` section, you can
|
||||
store them in AWS Secrets Manager using the following AWS CLI v2 command:
|
||||
|
||||
```sh
|
||||
aws secretsmanager create-secret --name "Kexample.org.+013+45330" \
|
||||
--description "DNSSEC keys for example.org" \
|
||||
--secret-string "$(jq -n --arg key "$(cat Kexample.org.+013+45330.key)" \
|
||||
--arg private "$(cat Kexample.org.+013+45330.private)" \
|
||||
'{key: $key, private: $private}')"
|
||||
```
|
||||
|
||||
This command reads the contents of the `.key` and `.private` files, constructs a JSON object, and stores it
|
||||
as a new secret in AWS Secrets Manager with the specified name and description. CoreDNS will then fetch
|
||||
the key data from AWS Secrets Manager when using the `key aws_secretsmanager` directive.
|
||||
|
||||
[AWS SDK for Go V2](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials) is used
|
||||
for authentication with AWS Secrets Manager. Make sure the provided AWS credentials have the necessary
|
||||
permissions (e.g., `secretsmanager:GetSecretValue`) to access the specified secrets in AWS Secrets Manager.
|
||||
|
||||
* `cache_capacity` indicates the capacity of the cache. The dnssec plugin uses a cache to store
|
||||
RRSIGs. The default for **CAPACITY** is 10000.
|
||||
|
||||
@@ -75,6 +95,18 @@ example.org {
|
||||
}
|
||||
~~~
|
||||
|
||||
Sign responses for `example.org` with the key stored in AWS Secrets Manager under the secret name
|
||||
"Kexample.org.+013+45330".
|
||||
|
||||
~~~
|
||||
example.org {
|
||||
dnssec {
|
||||
key aws_secretsmanager Kexample.org.+013+45330
|
||||
}
|
||||
whoami
|
||||
}
|
||||
~~~
|
||||
|
||||
Sign responses for a kubernetes zone with the key "Kcluster.local+013+45129.key".
|
||||
|
||||
~~~
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
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"
|
||||
)
|
||||
@@ -23,6 +28,12 @@ type DNSKEY struct {
|
||||
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) {
|
||||
@@ -63,6 +74,69 @@ func ParseKeyFile(pubFile, privFile string) (*DNSKEY, error) {
|
||||
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))
|
||||
|
||||
@@ -141,6 +141,19 @@ func keyParse(c *caddy.Controller) ([]*DNSKEY, error) {
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
} else if value == "aws_secretsmanager" {
|
||||
ks := c.RemainingArgs()
|
||||
if len(ks) == 0 {
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
|
||||
for _, k := range ks {
|
||||
k, err := ParseKeyFromAWSSecretsManager(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user