-
Notifications
You must be signed in to change notification settings - Fork 280
/
mtls.go
278 lines (241 loc) · 8.26 KB
/
mtls.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package config
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"os"
"regexp"
envoy_tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
envoy_matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
"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"
)
// SANType represents a certificate Subject Alternative Name type.
type SANType string
const (
// SANTypeDNS represents a DNS name.
SANTypeDNS SANType = "dns"
// SANTypeEmail represents an email address.
SANTypeEmail SANType = "email"
// SANTypeIPAddress represents an IP address.
SANTypeIPAddress SANType = "ip_address"
// SANTypeURI represents a URI.
SANTypeURI SANType = "uri"
)
// 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"`
// MatchSubjectAltNames is a list of SAN match expressions. When non-empty,
// a client certificate must contain at least one Subject Alternative Name
// that matches at least one of the expessions.
MatchSubjectAltNames []SANMatcher `mapstructure:"match_subject_alt_names" yaml:"match_subject_alt_names,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
} else if _, err := cryptutil.ParseCRLs(crl); err != nil {
return fmt.Errorf("CRL: %w", err)
}
switch s.Enforcement {
case "",
MTLSEnforcementPolicy,
MTLSEnforcementPolicyWithDefaultDeny,
MTLSEnforcementRejectConnection: // OK
default:
return errors.New("unknown enforcement option")
}
for i := range s.MatchSubjectAltNames {
if err := s.MatchSubjectAltNames[i].validate(); err != nil {
return err
}
}
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 ""
}
}
// SANMatcher represents a Subject Alternative Name string matcher condition. A
// certificate satisfies this condition if it contains at least one SAN of the
// given type that matches the regular expression as a full string match.
type SANMatcher struct {
Type SANType
Pattern string
}
func (s *SANMatcher) validate() error {
if s.envoyType() == envoy_tls.SubjectAltNameMatcher_SAN_TYPE_UNSPECIFIED {
return fmt.Errorf("unknown SAN type %q", s.Type)
}
if _, err := regexp.Compile(s.Pattern); err != nil {
return fmt.Errorf("couldn't parse pattern %q: %w", s.Pattern, err)
}
return nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (s *SANMatcher) UnmarshalJSON(b []byte) error {
var m map[string]string
if err := json.Unmarshal(b, &m); err != nil {
return err
} else if len(m) != 1 {
return errors.New("unsupported SAN matcher format: expected {type: pattern}")
}
for k, v := range m {
s.Type = SANType(k)
s.Pattern = v
}
return nil
}
// MarshalJSON implements the json.Marshaler interface.
func (s *SANMatcher) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{string(s.Type): s.Pattern})
}
// ToEnvoyProto rerturns a representation of this matcher as an Envoy
// SubjectAltNameMatcher proto.
func (s *SANMatcher) ToEnvoyProto() *envoy_tls.SubjectAltNameMatcher {
return &envoy_tls.SubjectAltNameMatcher{
SanType: s.envoyType(),
Matcher: &envoy_matcher.StringMatcher{
MatchPattern: &envoy_matcher.StringMatcher_SafeRegex{
SafeRegex: &envoy_matcher.RegexMatcher{
EngineType: &envoy_matcher.RegexMatcher_GoogleRe2{},
Regex: s.Pattern,
},
},
},
}
}
func (s *SANMatcher) envoyType() envoy_tls.SubjectAltNameMatcher_SanType {
switch s.Type {
case SANTypeDNS:
return envoy_tls.SubjectAltNameMatcher_DNS
case SANTypeEmail:
return envoy_tls.SubjectAltNameMatcher_EMAIL
case SANTypeIPAddress:
return envoy_tls.SubjectAltNameMatcher_IP_ADDRESS
case SANTypeURI:
return envoy_tls.SubjectAltNameMatcher_URI
default:
return envoy_tls.SubjectAltNameMatcher_SAN_TYPE_UNSPECIFIED
}
}