mirror of
https://github.com/status-im/consul.git
synced 2025-01-18 17:52:17 +00:00
5fb9df1640
* Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
169 lines
4.6 KiB
Go
169 lines
4.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package structs
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
"github.com/hashicorp/consul/version"
|
|
)
|
|
|
|
// InlineCertificateConfigEntry manages the configuration for an inline certificate
|
|
// with the given name.
|
|
type InlineCertificateConfigEntry struct {
|
|
// Kind of config entry. This will be set to structs.InlineCertificate.
|
|
Kind string
|
|
|
|
// Name is used to match the config entry with its associated inline certificate.
|
|
Name string
|
|
|
|
// Certificate is the public certificate component of an x509 key pair encoded in raw PEM format.
|
|
Certificate string
|
|
// PrivateKey is the private key component of an x509 key pair encoded in raw PEM format.
|
|
PrivateKey string
|
|
|
|
Meta map[string]string `json:",omitempty"`
|
|
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
|
|
RaftIndex
|
|
}
|
|
|
|
func (e *InlineCertificateConfigEntry) GetKind() string { return InlineCertificate }
|
|
func (e *InlineCertificateConfigEntry) GetName() string { return e.Name }
|
|
func (e *InlineCertificateConfigEntry) Normalize() error { return nil }
|
|
func (e *InlineCertificateConfigEntry) GetMeta() map[string]string { return e.Meta }
|
|
func (e *InlineCertificateConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
|
|
return &e.EnterpriseMeta
|
|
}
|
|
func (e *InlineCertificateConfigEntry) GetRaftIndex() *RaftIndex { return &e.RaftIndex }
|
|
|
|
// Envoy will silently reject any RSA keys that are less than 2048 bytes long
|
|
// https://github.com/envoyproxy/envoy/blob/main/source/extensions/transport_sockets/tls/context_impl.cc#L238
|
|
const MinKeyLength = 2048
|
|
|
|
func (e *InlineCertificateConfigEntry) Validate() error {
|
|
err := validateConfigEntryMeta(e.Meta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
privateKeyBlock, _ := pem.Decode([]byte(e.PrivateKey))
|
|
if privateKeyBlock == nil {
|
|
return errors.New("failed to parse private key PEM")
|
|
}
|
|
|
|
err = validateKeyLength(privateKeyBlock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
certificateBlock, _ := pem.Decode([]byte(e.Certificate))
|
|
if certificateBlock == nil {
|
|
return errors.New("failed to parse certificate PEM")
|
|
}
|
|
|
|
// make sure we have a valid x509 certificate
|
|
_, err = x509.ParseCertificate(certificateBlock.Bytes)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse certificate: %w", err)
|
|
}
|
|
|
|
// validate that the cert was generated with the given private key
|
|
_, err = tls.X509KeyPair([]byte(e.Certificate), []byte(e.PrivateKey))
|
|
if err != nil {
|
|
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
|
|
}
|
|
|
|
func validateKeyLength(privateKeyBlock *pem.Block) error {
|
|
if privateKeyBlock.Type != "RSA PRIVATE KEY" {
|
|
return nil
|
|
}
|
|
|
|
key, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
keyBitLen := key.N.BitLen()
|
|
|
|
if version.IsFIPS() {
|
|
return fipsLenCheck(keyBitLen)
|
|
}
|
|
|
|
return nonFipsLenCheck(keyBitLen)
|
|
}
|
|
|
|
func nonFipsLenCheck(keyLen int) error {
|
|
// ensure private key is of the correct length
|
|
if keyLen < MinKeyLength {
|
|
return errors.New("key length must be at least 2048 bits")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func fipsLenCheck(keyLen int) error {
|
|
if keyLen != 2048 && keyLen != 3072 && keyLen != 4096 {
|
|
return errors.New("key length invalid: only RSA lengths of 2048, 3072, and 4096 are allowed in FIPS mode")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *InlineCertificateConfigEntry) Hosts() ([]string, error) {
|
|
certificateBlock, _ := pem.Decode([]byte(e.Certificate))
|
|
if certificateBlock == nil {
|
|
return nil, errors.New("failed to parse certificate PEM")
|
|
}
|
|
|
|
certificate, err := x509.ParseCertificate(certificateBlock.Bytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse certificate: %w", err)
|
|
}
|
|
|
|
hosts := []string{certificate.Subject.CommonName}
|
|
|
|
for _, name := range certificate.DNSNames {
|
|
hosts = append(hosts, name)
|
|
}
|
|
|
|
for _, ip := range certificate.IPAddresses {
|
|
hosts = append(hosts, ip.String())
|
|
}
|
|
|
|
return hosts, nil
|
|
}
|
|
|
|
func (e *InlineCertificateConfigEntry) CanRead(authz acl.Authorizer) error {
|
|
var authzContext acl.AuthorizerContext
|
|
e.FillAuthzContext(&authzContext)
|
|
return authz.ToAllowAuthorizer().MeshReadAllowed(&authzContext)
|
|
}
|
|
|
|
func (e *InlineCertificateConfigEntry) CanWrite(authz acl.Authorizer) error {
|
|
var authzContext acl.AuthorizerContext
|
|
e.FillAuthzContext(&authzContext)
|
|
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
|
|
}
|