pomerium/config/mtls.go
Kenneth Jenkins 0715579fe1 config: extra CA and CRL validation
Return an error from DownstreamMTLSSettings.validate() if both CA and
CAFile are populated, or if both CRL and CRLFile are populated.
2023-08-10 14:49:36 -07:00

178 lines
5.2 KiB
Go

package config
import (
"context"
"encoding/base64"
"errors"
"fmt"
"os"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc/config"
)
// MTLSEnforcement represents a client certificate enforcement behavior.
type MTLSEnforcement string
const (
// MTLSEnforcementPolicy specifies no default client certificate
// enforcement: any requirements must be explicitly specified in a policy.
MTLSEnforcementPolicy MTLSEnforcement = "policy"
// MTLSEnforcementPolicyWithDefaultDeny specifies that client certificate
// requirements will be enforced by route policy, with a default
// invalid_client_certificate deny rule added to each policy.
MTLSEnforcementPolicyWithDefaultDeny MTLSEnforcement = "policy_with_default_deny"
// MTLSEnforcementRejectConnection specifies that client certificate
// requirements will be enforced by rejecting any connection attempts
// without a trusted certificate.
MTLSEnforcementRejectConnection MTLSEnforcement = "reject_connection"
)
// DownstreamMTLSSettings specify the downstream client certificate requirements.
type DownstreamMTLSSettings struct {
// CA is the base64-encoded certificate authority (or bundle of certificate
// authorities) that should serve as the trust root(s). These will be
// advertised in the initial TLS handshake.
CA string `mapstructure:"ca" yaml:"ca"`
// CAFile is the path to a file containing the certificate authority (or
// bundle of certificate authorities) that should serve as the trust
// root(s). These will be advertised in the initial TLS handshake.
CAFile string `mapstructure:"ca_file" yaml:"ca_file"`
// CRL is the base64-encoded certificate revocation list (or bundle of
// CRLs) to use when validating client certificates.
CRL string `mapstructure:"crl" yaml:"crl,omitempty"`
// CRLFile is the path to a file containing the certificate revocation
// list (or bundle of CRLs) to use when validating client certificates.
CRLFile string `mapstructure:"crl_file" yaml:"crl_file,omitempty"`
// Enforcement indicates the behavior applied to requests without a valid
// client certificate.
Enforcement MTLSEnforcement `mapstructure:"enforcement" yaml:"enforcement,omitempty"`
// MaxVerifyDepth is the maximum allowed depth of a certificate trust chain
// (not counting the leaf certificate). The value 0 indicates no maximum.
MaxVerifyDepth *uint32 `mapstructure:"max_verify_depth" yaml:"max_verify_depth,omitempty"`
}
// GetCA returns the certificate authority (or nil if unset).
func (s *DownstreamMTLSSettings) GetCA() ([]byte, error) {
if s.CA != "" {
ca, err := base64.StdEncoding.DecodeString(s.CA)
if err != nil {
return nil, fmt.Errorf("CA: %w", err)
}
return ca, nil
}
if s.CAFile != "" {
ca, err := os.ReadFile(s.CAFile)
if err != nil {
return nil, fmt.Errorf("CA file: %w", err)
}
return ca, nil
}
return nil, nil
}
// GetCRL returns the certificate revocation list bundle (or nil if unset).
func (s *DownstreamMTLSSettings) GetCRL() ([]byte, error) {
if s.CRL != "" {
crl, err := base64.StdEncoding.DecodeString(s.CRL)
if err != nil {
return nil, fmt.Errorf("CRL: %w", err)
}
return crl, nil
}
if s.CRLFile != "" {
crl, err := os.ReadFile(s.CRLFile)
if err != nil {
return nil, fmt.Errorf("CRL file: %w", err)
}
return crl, nil
}
return nil, nil
}
// GetEnforcement returns the enforcement behavior to apply.
func (s *DownstreamMTLSSettings) GetEnforcement() MTLSEnforcement {
if s.Enforcement == "" {
return MTLSEnforcementPolicyWithDefaultDeny
}
return s.Enforcement
}
// GetMaxVerifyDepth returns the maximum certificate chain depth. The value 0
// indicates no maximum.
func (s *DownstreamMTLSSettings) GetMaxVerifyDepth() uint32 {
if s.MaxVerifyDepth == nil {
return 1
}
return *s.MaxVerifyDepth
}
func (s *DownstreamMTLSSettings) validate() error {
if s.CA != "" && s.CAFile != "" {
return errors.New("cannot set both ca and ca_file")
} else if _, err := s.GetCA(); err != nil {
return err
}
if s.CRL != "" && s.CRLFile != "" {
return errors.New("cannot set both crl and crl_file")
}
crl, err := s.GetCRL()
if err != nil {
return err
}
if len(crl) > 0 {
if _, err := cryptutil.DecodeCRL(crl); err != nil {
return fmt.Errorf("CRL: %w", err)
}
}
switch s.Enforcement {
case "",
MTLSEnforcementPolicy,
MTLSEnforcementPolicyWithDefaultDeny,
MTLSEnforcementRejectConnection: // OK
default:
return errors.New("unknown enforcement option")
}
return nil
}
func (s *DownstreamMTLSSettings) applySettingsProto(
ctx context.Context, p *config.DownstreamMtlsSettings,
) {
if p == nil {
return
}
set(&s.CA, p.Ca)
set(&s.CRL, p.Crl)
s.Enforcement = mtlsEnforcementFromProtoEnum(ctx, p.Enforcement)
}
func mtlsEnforcementFromProtoEnum(
ctx context.Context, mode *config.MtlsEnforcementMode,
) MTLSEnforcement {
if mode == nil {
return ""
}
switch *mode {
case config.MtlsEnforcementMode_POLICY:
return MTLSEnforcementPolicy
case config.MtlsEnforcementMode_POLICY_WITH_DEFAULT_DENY:
return MTLSEnforcementPolicyWithDefaultDeny
case config.MtlsEnforcementMode_REJECT_CONNECTION:
return MTLSEnforcementRejectConnection
default:
log.Error(ctx).Msgf("unknown mTLS enforcement mode %s", mode)
return ""
}
}