Skip to content

Commit

Permalink
Add stricter validation and some normalization code for API Gateway C…
Browse files Browse the repository at this point in the history
…onfigEntries
  • Loading branch information
Andrew Stucki committed Feb 17, 2023
1 parent 0498b70 commit 6e6fe8a
Show file tree
Hide file tree
Showing 5 changed files with 553 additions and 198 deletions.
95 changes: 28 additions & 67 deletions agent/structs/config_entry_gateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,18 @@ type APIGatewayConfigEntry struct {
RaftIndex
}

func (e *APIGatewayConfigEntry) GetKind() string { return APIGateway }
func (e *APIGatewayConfigEntry) GetName() string { return e.Name }
func (e *APIGatewayConfigEntry) GetMeta() map[string]string { return e.Meta }
func (e *APIGatewayConfigEntry) GetRaftIndex() *RaftIndex { return &e.RaftIndex }
func (e *APIGatewayConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta { return &e.EnterpriseMeta }

var _ ControlledConfigEntry = (*APIGatewayConfigEntry)(nil)

func (e *APIGatewayConfigEntry) GetStatus() Status { return e.Status }
func (e *APIGatewayConfigEntry) SetStatus(status Status) { e.Status = status }
func (e *APIGatewayConfigEntry) DefaultStatus() Status { return Status{} }

func (e *APIGatewayConfigEntry) ListenerIsReady(name string) bool {
for _, condition := range e.Status.Conditions {
if !condition.Resource.IsSame(&ResourceReference{
Expand All @@ -732,34 +744,28 @@ func (e *APIGatewayConfigEntry) ListenerIsReady(name string) bool {
return true
}

func (e *APIGatewayConfigEntry) GetKind() string {
return APIGateway
}

func (e *APIGatewayConfigEntry) GetName() string {
if e == nil {
return ""
}
return e.Name
}

func (e *APIGatewayConfigEntry) GetMeta() map[string]string {
if e == nil {
return nil
}
return e.Meta
}

func (e *APIGatewayConfigEntry) Normalize() error {
for i, listener := range e.Listeners {
protocol := strings.ToLower(string(listener.Protocol))
listener.Protocol = APIGatewayListenerProtocol(protocol)
e.Listeners[i] = listener

for i, cert := range listener.TLS.Certificates {
if cert.Kind == "" {
cert.Kind = InlineCertificate
}
listener.TLS.Certificates[i] = cert
}
}

return nil
}

func (e *APIGatewayConfigEntry) Validate() error {
if err := validateConfigEntryMeta(e.Meta); err != nil {
return err
}

if err := e.validateListenerNames(); err != nil {
return err
}
Expand Down Expand Up @@ -843,34 +849,6 @@ func (e *APIGatewayConfigEntry) CanWrite(authz acl.Authorizer) error {
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
}

func (e *APIGatewayConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil {
return &RaftIndex{}
}
return &e.RaftIndex
}

func (e *APIGatewayConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}

var _ ControlledConfigEntry = (*APIGatewayConfigEntry)(nil)

func (e *APIGatewayConfigEntry) GetStatus() Status {
return e.Status
}

func (e *APIGatewayConfigEntry) SetStatus(status Status) {
e.Status = status
}

func (e *APIGatewayConfigEntry) DefaultStatus() Status {
return Status{}
}

// APIGatewayListenerProtocol is the protocol that an APIGateway listener uses
type APIGatewayListenerProtocol string

Expand Down Expand Up @@ -991,27 +969,10 @@ func (e *BoundAPIGatewayConfigEntry) IsInitializedForGateway(gateway *APIGateway
return true
}

func (e *BoundAPIGatewayConfigEntry) GetKind() string {
return BoundAPIGateway
}

func (e *BoundAPIGatewayConfigEntry) GetName() string {
if e == nil {
return ""
}
return e.Name
}

func (e *BoundAPIGatewayConfigEntry) GetMeta() map[string]string {
if e == nil {
return nil
}
return e.Meta
}

func (e *BoundAPIGatewayConfigEntry) Normalize() error {
return nil
}
func (e *BoundAPIGatewayConfigEntry) GetKind() string { return BoundAPIGateway }
func (e *BoundAPIGatewayConfigEntry) GetName() string { return e.Name }
func (e *BoundAPIGatewayConfigEntry) GetMeta() map[string]string { return e.Meta }
func (e *BoundAPIGatewayConfigEntry) Normalize() error { return nil }

func (e *BoundAPIGatewayConfigEntry) Validate() error {
allowedCertificateKinds := map[string]bool{
Expand Down
17 changes: 17 additions & 0 deletions agent/structs/config_entry_inline_certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"

"github.com/hashicorp/consul/acl"
"github.com/miekg/dns"
)

// InlineCertificateConfigEntry manages the configuration for an inline certificate
Expand Down Expand Up @@ -39,6 +40,10 @@ func (e *InlineCertificateConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
func (e *InlineCertificateConfigEntry) GetRaftIndex() *RaftIndex { return &e.RaftIndex }

func (e *InlineCertificateConfigEntry) Validate() error {
if err := validateConfigEntryMeta(e.Meta); err != nil {
return err
}

privateKeyBlock, _ := pem.Decode([]byte(e.PrivateKey))
if privateKeyBlock == nil {
return errors.New("failed to parse private key PEM")
Expand All @@ -61,6 +66,18 @@ func (e *InlineCertificateConfigEntry) Validate() error {
return err
}

// validate that each host referenced in the CN, DNSSans, and IPSans
// are valid hostnames
hosts, err := e.Hosts()
if err != nil {
return err
}
for _, host := range hosts {
if _, ok := dns.IsDomainName(host); !ok {
return fmt.Errorf("host %q must be a valid DNS hostname", host)
}
}

return nil
}

Expand Down
96 changes: 79 additions & 17 deletions agent/structs/config_entry_inline_certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,73 @@ import "testing"
const (
// generated via openssl req -x509 -sha256 -days 1825 -newkey rsa:2048 -keyout private.key -out certificate.crt
validPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0wzZeonUklhOvJ0AxcdDdCTiMwR9tsm/6IGcw9Jm50xVY+qg
5GFg1RWrQaODq7Gjqd/JDUAwtTBnQMs1yt6nbsHe2QhbD4XeqtZ+6fTv1ZpG3k8F
eB/M01xFqovczRV/ie77wd4vqoPD+AcfD8NDAFJt3htwUgGIqkQHP329Sh3TtLga
9ZMCs1MoTT+POYGUPL8bwt9R6ClNrucbH4Bs6OnX2ZFbKF75O9OHKNxWTmpDSodv
OFbFyKps3BfnPuF0Z6mj5M5yZeCjmtfS25PrsM3pMBGK5YHb0MlFfZIrIGboMbrz
9F/BMQJ64pMe43KwqHvTnbKWhp6PzLhEkPGLnwIDAQABAoIBADBEJAiONPszDu67
yU1yAM8zEDgysr127liyK7PtDnOfVXgAVMNmMcsJpZzhVF+TxKY487YAFCOb6kE7
OBYpTYla9SgVbR3js8TGQUgoKCFlowd8cvfB7gn4dEZIrjqIzB4zdYgk1Cne8JZs
qoHkWhJcx5ugEtPuXd7yp+WxT/T+6uOro06scp67NhP5t9yoAGFv5Vdb577RuzRo
Wkd9higQ9A20+GtjCY0EYxdgRviWvW7mM5/F+Lzcaui86ME+ga754gX8zgW3+NJ5
LMsz5OLSnh291Uyjmr77HWBv/xvpq01Fls0LyJcgxFVZuJs5GQz+l3otSqv4FTP6
Ua9w/YECgYEA8To3dgUK1QhzX5rwhWtlst3pItGTvmEdNzXmjgSylu7uKM13i+xg
llhp2uXrOEtuL+xtBZdeFNaijusbyqjg0xj6e4o31c19okuuDkJD5/sfQq22bvrn
gVJMGuESprIiPePrEyrXCHOdxH6eDgR2dIzAeO5vz0nnKGFAWrJJbvECgYEA3/mJ
eacXOJznw4Sa8jGWS2FtZLKxDHph7uDKMJmuG0ukb3aHJ9dMHrPleCLo8mhpoObA
hueoIbIP7swGrQx79+nZbnQpF6rMp6FAU5bF3gSrj1eWbaeh8pn9mrv4hal9USmn
orTbXMxDp3XSh7voR8Fqy5tMQqwZ+Lz74ccbw48CgYEA5cEhGdNrocPOv3x/IVRN
JLOfXX5nTaiJfxBja1imEIO5ajtoZWjaBdhn2gmqo4+UfyicHfsxrH9RjPX5HmkC
2Yys5gWbcJOr2Wxjd0k+DDFucL+rRsDKxq1vtxov/X0kh/YQ68ydynr0BTbjq04s
1I1KtOPEspYdCKS3+qpcrsECgYBtvYeVesBO9do9G0kMKC26y4bdEwzaz1ASykNn
IrWDHEH6dznr1HqwhHaHsZsvwucWdlmZAAKKWAOkfoU63uYS55qomvPTa9WQwNqS
2koi6Wjh+Al1uvAHvVncKgOwAgar8Nv5ReJBirgPYhSAexpppiRclL/93vNuw7Iq
wvMgkwKBgQC5wnb6SUUrzzKKSRgyusHM/XrjiKgVKq7lvFE9/iJkcw+BEXpjjbEe
RyD0a7PRtCfR39SMVrZp4KXVNNK5ln0WhuLvraMDwOpH9JDWHQiAhuJ3ooSwBylK
+QCLjyOtWAGZAIBRJyb1txfTXZ++dldkOjBi3bmEiadOa48ksvDsNQ==
-----END RSA PRIVATE KEY-----`
validCertificate = `-----BEGIN CERTIFICATE-----
MIIDQjCCAioCCQC6cMRYsE+ahDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQ0wCwYD
VQQLDARTdHViMRwwGgYDVQQDDBNob3N0LmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx
NzAyMTA1MloXDTI4MDIxNjAyMTA1MlowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEU3R1YjEc
MBoGA1UEAwwTaG9zdC5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANMM2XqJ1JJYTrydAMXHQ3Qk4jMEfbbJv+iBnMPSZudMVWPq
oORhYNUVq0Gjg6uxo6nfyQ1AMLUwZ0DLNcrep27B3tkIWw+F3qrWfun079WaRt5P
BXgfzNNcRaqL3M0Vf4nu+8HeL6qDw/gHHw/DQwBSbd4bcFIBiKpEBz99vUod07S4
GvWTArNTKE0/jzmBlDy/G8LfUegpTa7nGx+AbOjp19mRWyhe+TvThyjcVk5qQ0qH
bzhWxciqbNwX5z7hdGepo+TOcmXgo5rX0tuT67DN6TARiuWB29DJRX2SKyBm6DG6
8/RfwTECeuKTHuNysKh7052yloaej8y4RJDxi58CAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAHF10odRNJ7TKvcD2JPtR8wMacfldSiPcQnn+rhMUyBaKOoSrALxOev+N
L8N+RtEV+KXkyBkvT71OZzEpY9ROwqOQ/acnMdbfG0IBPbg3c/7WDD2sjcdr1zvc
U3T7WJ7G3guZ5aWCuAGgOyT6ZW8nrDa4yFbKZ1PCJkvUQ2ttO1lXmyGPM533Y2pi
SeXP6LL7z5VNqYO3oz5IJEstt10IKxdmb2gKFhHjgEmHN2gFL0jaPi4mjjaINrxq
MdqcM9IzLr26AjZ45NuI9BCcZWO1mraaQTOIb3QL5LyqaC7CRJXLYPSGARthyDhq
J3TrQE3YVrL4D9xnklT86WDnZKApJg==
-----END CERTIFICATE-----`
mismatchedCertificate = `-----BEGIN CERTIFICATE-----
MIIDQjCCAioCCQC2H6+PYz23xDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAkxBMQ0wCwYDVQQKDARUZXN0MQwwCgYD
VQQLDANGb28xHTAbBgNVBAMMFG90aGVyLmNvbnN1bC5leGFtcGxlMB4XDTIzMDIx
NzAyMTM0OVoXDTI4MDIxNjAyMTM0OVowYzELMAkGA1UEBhMCVVMxCzAJBgNVBAgM
AkNBMQswCQYDVQQHDAJMQTENMAsGA1UECgwEVGVzdDEMMAoGA1UECwwDRm9vMR0w
GwYDVQQDDBRvdGhlci5jb25zdWwuZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAO0IH/dzmWJaTPVL32xQVHivrnQk38vskW0ymILYuaismUMJ
0+xrcaTcVljU+3nKhmSW9wcYSFY02GcGWAdcw8x8xO801cna020T+DIWiYaljXT3
agrbYfULF9q+ihT6IL1D2mFa0AW1x6Bk1XAmZRSTpRBhp7iFNnCXGRK8sSSr95ge
DxaRyj/2F8t6kG+ANPkRBiPd2rRgsYQjuTLuZYBvseeJygnSF8ty1QMg6koz7kdN
bPon3Q5GFH71WNwzm9G3DWjMIu+dhpHz7rsbCnhwLB5lh1jsZBYkAMt3kiyY0g4I
ReuiVWesMe+AMG/DQZvZ5mE252QFJ92dLTeo5RcCAwEAATANBgkqhkiG9w0BAQsF
AAOCAQEAijm6blixjl+pMRAj7EajoPjU+GqhooZayJrvdwvofwcPxQYpkPuh7Uc6
l2z494b75cRzMw7wS+iW/ad8NYrfw1JwHMsUfncxs5LDO5GsKl9Krg/39goDl3wC
ywTcl00y+FMYfldNPjKDLunENmn+yPa2pKuBVQ0yOKALp+oUeJFVzRNPV5fohlBi
HjypkO0KaVmCG6P01cqCgVkNzxnX9qQYP3YXX1yt5iOcI7QcoOa5WnRhOuD8WqJ1
v3AZGYNvKyXf9E5nD0y2Cmz6t1awjFjzMlXMx6AdHrjWqxtHhYQ1xz4P4NfzK27m
cCtURSzXMgcrSeZLepBfdICf+0/0+Q==
-----END CERTIFICATE-----`
emptyCNPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAx95Opa6t4lGEpiTUogEBptqOdam2ch4BHQGhNhX/MrDwwuZQ
httBwMfngQ/wd9NmYEPAwj0dumUoAITIq6i2jQlhqTodElkbsd5vWY8R/bxJWQSo
NvVE12TlzECxGpJEiHt4W0r8pGffk+rvpljiUyCfnT1kGF3znOSjK1hRMTn6RKWC
Expand All @@ -31,7 +98,7 @@ T0+9gwKBgHDoerX7NTskg0H0t8O+iSMevdxpEWp34ZYa9gHiftTQGyrRgERCa7Gj
nZPAxKb2JoWyfnu3v7G5gZ8fhDFsiOxLbZv6UZJBbUIh1MjJISpXrForDrC2QNLX
kHrHfwBFDB3KMudhQknsJzEJKCL/KmFH6o0MvsoaT9yzEl3K+ah/
-----END RSA PRIVATE KEY-----`
validCertificate = `-----BEGIN CERTIFICATE-----
emptyCNCertificate = `-----BEGIN CERTIFICATE-----
MIICljCCAX4CCQCQMDsYO8FrPjANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV
UzAeFw0yMjEyMjAxNzUwMjVaFw0yNzEyMTkxNzUwMjVaMA0xCzAJBgNVBAYTAlVT
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx95Opa6t4lGEpiTUogEB
Expand All @@ -46,26 +113,12 @@ RahYIzNLRBTLrwadLAZkApUpZvB8qDK4knsTWFYujNsylCww2A6ajzIMFNU4GkUK
NtyHRuD+KYRmjXtyX1yHNqfGN3vOQmwavHq2R8wHYuBSc6LAHHV9vG+j0VsgMELO
qwxn8SmLkSKbf2+MsQVzLCXXN5u+D8Yv+4py+oKP4EQ5aFZuDEx+r/G/31rTthww
AAJAMaoXmoYVdgXV+CPuBb2M4XCpuzLu3bcA2PXm5ipSyIgntMKwXV7r
-----END CERTIFICATE-----`
mismatchedCertificate = `-----BEGIN CERTIFICATE-----
MIICljCCAX4CCQC49bq8e0QgLDANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJV
UzAeFw0yMjEyMjAxNzUyMzJaFw0yNzEyMTkxNzUyMzJaMA0xCzAJBgNVBAYTAlVT
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk7Are9ulVDY0IqaG5Pt/
OVuS0kmDhgVUfQBM5JDGRfIsu1ebn68kn5JGCTQ+nC8nU9QXRJS7vG6As5GWm08W
FpkOyIbHLjOhWtYCYzQ+0R+sSSoMnczgl8l6wIUIkR3Vpoy6QUsSZbvo4/xDi3Uk
1CF+JMTM2oFDLD8PNrNzW/txRyTugK36W1G1ofUhvP6EHsTjmVcZwBcLOKToov6L
Ai758MLztl1/X/90DNdZwuHC9fGIgx52Ojz3+XIocXFttr+J8xZglMCtqL4n40bh
5b1DE+hC3NHQmA+7Chc99z28baj2cU1woNk/TO+ewqpyvj+WPWwGOQt3U63ZoPaw
yQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCMF3JlrDdcSv2KYrxEp1tWB/GglI8a
JiSvrf3hePaRz59099bg4DoHzTn0ptOcOPOO9epDPbCJrUqLuPlwvrQRvll6GaW1
y3TcbnE1AbwTAjbOTgpLhvuj6IVlyNNLoKbjZqs4A8N8i6UkQ7Y8qg77lwxD3QoH
pWLwGZKJifKPa7ObVWmKj727kbU59nA2Hx+Y4qa/MyiPWxJM9Y0JsFGxSBxp4kmQ
q4ikzSWaPv/TvtV+d4mO1H44aggdNMCYIQd/5BXQzG40l+ecHnBueJyG312ax/Zp
NsYUAKQT864cGlxrnWVgT4sW/tsl9Qen7g9iAdeBAPvLO7cQjAjtc7KZ
-----END CERTIFICATE-----`
)

func TestInlineCertificate(t *testing.T) {
t.Parallel()

cases := map[string]configEntryTestcase{
"invalid private key": {
entry: &InlineCertificateConfigEntry{
Expand Down Expand Up @@ -101,6 +154,15 @@ func TestInlineCertificate(t *testing.T) {
Certificate: validCertificate,
},
},
"empty cn certificate": {
entry: &InlineCertificateConfigEntry{
Kind: InlineCertificate,
Name: "cert-five",
PrivateKey: emptyCNPrivateKey,
Certificate: emptyCNCertificate,
},
validateErr: "host \"\" must be a valid DNS hostname",
},
}
testConfigEntryNormalizeAndValidate(t, cases)
}
Loading

0 comments on commit 6e6fe8a

Please sign in to comment.