mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-06 10:21:05 +02:00
envoy: log mtls failures
This implements limited listener-based access logging for downstream transport failures, only enabled when downstream_mtls.enforcement is set to 'reject_connection'. Client certificate details and the error message will be logged. Additionally, the new key 'client-certificate' can be set in the access_log_fields list in the configuration, which will add peer certificate properties (issuer, subject, SANs) to the existing per-request http logs.
This commit is contained in:
parent
d07cb616fd
commit
783b1127c7
3 changed files with 44 additions and 20 deletions
|
@ -139,15 +139,10 @@ func listenerAccessLog() []*envoy_config_accesslog_v3.AccessLog {
|
||||||
}
|
}
|
||||||
tcp := marshalAny(
|
tcp := marshalAny(
|
||||||
&envoy_extensions_access_loggers_grpc_v3.TcpGrpcAccessLogConfig{CommonConfig: cc})
|
&envoy_extensions_access_loggers_grpc_v3.TcpGrpcAccessLogConfig{CommonConfig: cc})
|
||||||
http := marshalAny(
|
|
||||||
&envoy_extensions_access_loggers_grpc_v3.HttpGrpcAccessLogConfig{CommonConfig: cc})
|
|
||||||
return []*envoy_config_accesslog_v3.AccessLog{
|
return []*envoy_config_accesslog_v3.AccessLog{
|
||||||
{
|
{
|
||||||
Name: "envoy.access_loggers.tcp_grpc",
|
Name: "envoy.access_loggers.tcp_grpc",
|
||||||
ConfigType: &envoy_config_accesslog_v3.AccessLog_TypedConfig{TypedConfig: tcp},
|
ConfigType: &envoy_config_accesslog_v3.AccessLog_TypedConfig{TypedConfig: tcp},
|
||||||
}, {
|
|
||||||
Name: "envoy.access_loggers.http_grpc",
|
|
||||||
ConfigType: &envoy_config_accesslog_v3.AccessLog_TypedConfig{TypedConfig: http},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,7 +157,9 @@ func (b *Builder) buildMainListener(
|
||||||
li.ListenerFilters = append(li.ListenerFilters, ProxyProtocolFilter())
|
li.ListenerFilters = append(li.ListenerFilters, ProxyProtocolFilter())
|
||||||
}
|
}
|
||||||
|
|
||||||
li.AccessLog = listenerAccessLog() // XXX
|
if cfg.Options.DownstreamMTLS.Enforcement == config.MTLSEnforcementRejectConnection {
|
||||||
|
li.AccessLog = listenerAccessLog()
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Options.InsecureServer {
|
if cfg.Options.InsecureServer {
|
||||||
li.Address = buildAddress(cfg.Options.Addr, 80)
|
li.Address = buildAddress(cfg.Options.Addr, 80)
|
||||||
|
|
|
@ -2,13 +2,11 @@ package controlplane
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
envoy_data_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/data/accesslog/v3"
|
envoy_data_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/data/accesslog/v3"
|
||||||
envoy_service_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3"
|
envoy_service_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/service/accesslog/v3"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"google.golang.org/protobuf/encoding/protojson"
|
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/log"
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
)
|
)
|
||||||
|
@ -43,18 +41,18 @@ func accessLogListener(
|
||||||
ctx context.Context, msg *envoy_service_accesslog_v3.StreamAccessLogsMessage,
|
ctx context.Context, msg *envoy_service_accesslog_v3.StreamAccessLogsMessage,
|
||||||
) {
|
) {
|
||||||
for _, entry := range msg.GetTcpLogs().GetLogEntry() {
|
for _, entry := range msg.GetTcpLogs().GetLogEntry() {
|
||||||
e, _ := protojson.Marshal(entry)
|
failure := entry.GetCommonProperties().GetDownstreamTransportFailureReason()
|
||||||
log.Info(ctx).
|
if failure == "" {
|
||||||
Str("service", "envoy").
|
continue
|
||||||
Interface("log", json.RawMessage(e)).
|
}
|
||||||
Msg("listener connect (TCP log)")
|
e := log.Info(ctx).Str("service", "envoy")
|
||||||
}
|
dict := zerolog.Dict()
|
||||||
for _, entry := range msg.GetHttpLogs().GetLogEntry() {
|
populateCertEventDict(entry.GetCommonProperties().GetTlsProperties().GetPeerCertificateProperties(), dict)
|
||||||
e, _ := protojson.Marshal(entry)
|
e.Dict("client-certificate", dict)
|
||||||
log.Info(ctx).
|
e.Str("ip", entry.GetCommonProperties().GetDownstreamRemoteAddress().GetSocketAddress().GetAddress())
|
||||||
Str("service", "envoy").
|
e.Str("tls-sni-hostname", entry.GetCommonProperties().GetTlsProperties().GetTlsSniHostname())
|
||||||
Interface("log", json.RawMessage(e)).
|
e.Str("downstream-transport-failure-reason", failure)
|
||||||
Msg("listener connect (HTTP log)")
|
e.Msg("listener connection failure")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +119,34 @@ func populateLogEvent(
|
||||||
return evt.Str(string(field), entry.GetCommonProperties().GetUpstreamCluster())
|
return evt.Str(string(field), entry.GetCommonProperties().GetUpstreamCluster())
|
||||||
case log.AccessLogFieldUserAgent:
|
case log.AccessLogFieldUserAgent:
|
||||||
return evt.Str(string(field), entry.GetRequest().GetUserAgent())
|
return evt.Str(string(field), entry.GetRequest().GetUserAgent())
|
||||||
|
case log.AccessLogFieldClientCertificate:
|
||||||
|
dict := zerolog.Dict()
|
||||||
|
populateCertEventDict(entry.GetCommonProperties().GetTlsProperties().GetPeerCertificateProperties(), dict)
|
||||||
|
return evt.Dict(string(field), dict)
|
||||||
default:
|
default:
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func populateCertEventDict(cert *envoy_data_accesslog_v3.TLSProperties_CertificateProperties, dict *zerolog.Event) {
|
||||||
|
if cert.Issuer != "" {
|
||||||
|
dict.Str("issuer", cert.Issuer)
|
||||||
|
}
|
||||||
|
if cert.Subject != "" {
|
||||||
|
dict.Str("subject", cert.Subject)
|
||||||
|
}
|
||||||
|
if len(cert.SubjectAltName) > 0 {
|
||||||
|
arr := zerolog.Arr()
|
||||||
|
for _, san := range cert.SubjectAltName {
|
||||||
|
// follow openssl GENERAL_NAME_print formatting
|
||||||
|
// envoy only provides dns and uri SANs at the moment
|
||||||
|
switch san := san.GetSan().(type) {
|
||||||
|
case *envoy_data_accesslog_v3.TLSProperties_CertificateProperties_SubjectAltName_Dns:
|
||||||
|
arr.Str("DNS:" + san.Dns)
|
||||||
|
case *envoy_data_accesslog_v3.TLSProperties_CertificateProperties_SubjectAltName_Uri:
|
||||||
|
arr.Str("URI:" + san.Uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict.Array("subjectAltName", arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ const (
|
||||||
AccessLogFieldSize AccessLogField = "size"
|
AccessLogFieldSize AccessLogField = "size"
|
||||||
AccessLogFieldUpstreamCluster AccessLogField = "upstream-cluster"
|
AccessLogFieldUpstreamCluster AccessLogField = "upstream-cluster"
|
||||||
AccessLogFieldUserAgent AccessLogField = "user-agent"
|
AccessLogFieldUserAgent AccessLogField = "user-agent"
|
||||||
|
AccessLogFieldClientCertificate AccessLogField = "client-certificate"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultAccessLogFields = []AccessLogField{
|
var defaultAccessLogFields = []AccessLogField{
|
||||||
|
@ -64,6 +65,7 @@ var accessLogFieldLookup = map[AccessLogField]struct{}{
|
||||||
AccessLogFieldSize: {},
|
AccessLogFieldSize: {},
|
||||||
AccessLogFieldUpstreamCluster: {},
|
AccessLogFieldUpstreamCluster: {},
|
||||||
AccessLogFieldUserAgent: {},
|
AccessLogFieldUserAgent: {},
|
||||||
|
AccessLogFieldClientCertificate: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate returns an error if the access log field is invalid.
|
// Validate returns an error if the access log field is invalid.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue