This repository has been archived by the owner on Jul 11, 2023. It is now read-only.
/
xdsutil.go
291 lines (256 loc) · 11.2 KB
/
xdsutil.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
279
280
281
282
283
284
285
286
287
288
289
290
291
package envoy
import (
"fmt"
"net"
"strings"
xds_accesslog_filter "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
xds_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
xds_accesslog "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/stream/v3"
xds_auth "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
structpb "github.com/golang/protobuf/ptypes/struct"
"github.com/golang/protobuf/ptypes/wrappers"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/wrapperspb"
configv1alpha2 "github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
"github.com/openservicemesh/osm/pkg/constants"
"github.com/openservicemesh/osm/pkg/envoy/secrets"
"github.com/openservicemesh/osm/pkg/errcode"
"github.com/openservicemesh/osm/pkg/identity"
"github.com/openservicemesh/osm/pkg/service"
)
const (
// TransportProtocolTLS is the TLS transport protocol used in Envoy configurations
TransportProtocolTLS = "tls"
// OutboundPassthroughCluster is the outbound passthrough cluster name
OutboundPassthroughCluster = "passthrough-outbound"
// AccessLoggerName is name used for the envoy access loggers.
AccessLoggerName = "envoy.access_loggers.stream"
)
// ALPNInMesh indicates that the proxy is connecting to an in-mesh destination.
// It is set as a part of configuring the UpstreamTLSContext.
var ALPNInMesh = []string{"osm"}
// GetAddress creates an Envoy Address struct.
func GetAddress(address string, port uint32) *xds_core.Address {
return &xds_core.Address{
Address: &xds_core.Address_SocketAddress{
SocketAddress: &xds_core.SocketAddress{
Protocol: xds_core.SocketAddress_TCP,
Address: address,
PortSpecifier: &xds_core.SocketAddress_PortValue{
PortValue: port,
},
},
},
}
}
// GetTLSParams creates Envoy TlsParameters struct.
func GetTLSParams(sidecarSpec configv1alpha2.SidecarSpec) *xds_auth.TlsParameters {
minVersionInt := xds_auth.TlsParameters_TlsProtocol_value[sidecarSpec.TLSMinProtocolVersion]
maxVersionInt := xds_auth.TlsParameters_TlsProtocol_value[sidecarSpec.TLSMaxProtocolVersion]
tlsMinVersion := xds_auth.TlsParameters_TlsProtocol(minVersionInt)
tlsMaxVersion := xds_auth.TlsParameters_TlsProtocol(maxVersionInt)
tlsParams := &xds_auth.TlsParameters{
TlsMinimumProtocolVersion: tlsMinVersion,
TlsMaximumProtocolVersion: tlsMaxVersion,
}
if len(sidecarSpec.CipherSuites) > 0 {
tlsParams.CipherSuites = sidecarSpec.CipherSuites
}
if len(sidecarSpec.ECDHCurves) > 0 {
tlsParams.EcdhCurves = sidecarSpec.ECDHCurves
}
return tlsParams
}
// GetAccessLog creates an Envoy AccessLog struct.
func GetAccessLog() []*xds_accesslog_filter.AccessLog {
accessLog, err := anypb.New(getStdoutAccessLog())
if err != nil {
log.Error().Err(err).Str(errcode.Kind, errcode.GetErrCodeWithMetric(errcode.ErrMarshallingXDSResource)).
Msgf("Error marshalling AccessLog object")
return nil
}
return []*xds_accesslog_filter.AccessLog{{
Name: AccessLoggerName,
ConfigType: &xds_accesslog_filter.AccessLog_TypedConfig{
TypedConfig: accessLog,
}},
}
}
func getStdoutAccessLog() *xds_accesslog.StdoutAccessLog {
accessLogger := &xds_accesslog.StdoutAccessLog{
AccessLogFormat: &xds_accesslog.StdoutAccessLog_LogFormat{
LogFormat: &xds_core.SubstitutionFormatString{
Format: &xds_core.SubstitutionFormatString_JsonFormat{
JsonFormat: &structpb.Struct{
Fields: map[string]*structpb.Value{
"start_time": pbStringValue(`%START_TIME%`),
"method": pbStringValue(`%REQ(:METHOD)%`),
"path": pbStringValue(`%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%`),
"protocol": pbStringValue(`%PROTOCOL%`),
"response_code": pbStringValue(`%RESPONSE_CODE%`),
"response_code_details": pbStringValue(`%RESPONSE_CODE_DETAILS%`),
"time_to_first_byte": pbStringValue(`%RESPONSE_DURATION%`),
"upstream_cluster": pbStringValue(`%UPSTREAM_CLUSTER%`),
"response_flags": pbStringValue(`%RESPONSE_FLAGS%`),
"bytes_received": pbStringValue(`%BYTES_RECEIVED%`),
"bytes_sent": pbStringValue(`%BYTES_SENT%`),
"duration": pbStringValue(`%DURATION%`),
"upstream_service_time": pbStringValue(`%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%`),
"x_forwarded_for": pbStringValue(`%REQ(X-FORWARDED-FOR)%`),
"user_agent": pbStringValue(`%REQ(USER-AGENT)%`),
"request_id": pbStringValue(`%REQ(X-REQUEST-ID)%`),
"requested_server_name": pbStringValue("%REQUESTED_SERVER_NAME%"),
"authority": pbStringValue(`%REQ(:AUTHORITY)%`),
"upstream_host": pbStringValue(`%UPSTREAM_HOST%`),
},
},
},
},
},
}
return accessLogger
}
func pbStringValue(v string) *structpb.Value {
return &structpb.Value{
Kind: &structpb.Value_StringValue{
StringValue: v,
},
}
}
// getCommonTLSContext returns a CommonTlsContext type for a given 'tlsSDSCert' and 'peerValidationSDSCert' pair.
// 'tlsSDSCert' determines the SDS Secret config used to present the TLS certificate.
// 'peerValidationSDSCert' determines the SDS Secret configs used to validate the peer TLS certificate. A nil value
// is used to indicate peer certificate validation should be skipped, and is used when mTLS is disabled (ex. with TLS
// based ingress).
// 'sidecarSpec' is the sidecar section of MeshConfig.
func getCommonTLSContext(tlsSDSCert secrets.SDSCert, peerValidationSDSCert *secrets.SDSCert, sidecarSpec configv1alpha2.SidecarSpec) *xds_auth.CommonTlsContext {
commonTLSContext := &xds_auth.CommonTlsContext{
TlsParams: GetTLSParams(sidecarSpec),
TlsCertificateSdsSecretConfigs: []*xds_auth.SdsSecretConfig{{
// Example ==> Name: "service-cert:NameSpaceHere/ServiceNameHere"
Name: tlsSDSCert.String(),
SdsConfig: GetADSConfigSource(),
}},
}
// For TLS (non-mTLS) based validation, the client certificate should not be validated and the
// 'peerValidationSDSCert' will be set to nil to indicate this.
if peerValidationSDSCert != nil {
commonTLSContext.ValidationContextType = &xds_auth.CommonTlsContext_ValidationContextSdsSecretConfig{
ValidationContextSdsSecretConfig: &xds_auth.SdsSecretConfig{
// Example ==> Name: "root-cert<type>:NameSpaceHere/ServiceNameHere"
Name: peerValidationSDSCert.String(),
SdsConfig: GetADSConfigSource(),
},
}
}
return commonTLSContext
}
// GetDownstreamTLSContext creates a downstream Envoy TLS Context to be configured on the upstream for the given upstream's identity
// Note: ServiceIdentity must be in the format "name.namespace" [https://github.com/openservicemesh/osm/issues/3188]
func GetDownstreamTLSContext(upstreamIdentity identity.ServiceIdentity, mTLS bool, sidecarSpec configv1alpha2.SidecarSpec) *xds_auth.DownstreamTlsContext {
upstreamSDSCert := secrets.SDSCert{
Name: secrets.GetSecretNameForIdentity(upstreamIdentity),
CertType: secrets.ServiceCertType,
}
var downstreamPeerValidationSDSCert *secrets.SDSCert
if mTLS {
// The downstream peer validation SDS cert points to a cert with the name 'upstreamIdentity' only
// because we use a single DownstreamTlsContext for all inbound traffic to the given upstream with the identity 'upstreamIdentity'.
// This single DownstreamTlsContext is used to validate all allowed inbound SANs. The
// 'RootCertTypeForMTLSInbound' cert type is used for in-mesh downstreams.
downstreamPeerValidationSDSCert = &secrets.SDSCert{
Name: secrets.GetSecretNameForIdentity(upstreamIdentity),
CertType: secrets.RootCertTypeForMTLSInbound,
}
} else {
// When 'mTLS' is disabled, the upstream should not validate the certificate presented by the downstream.
// This is used for HTTPS ingress with mTLS disabled.
downstreamPeerValidationSDSCert = nil
}
tlsConfig := &xds_auth.DownstreamTlsContext{
CommonTlsContext: getCommonTLSContext(upstreamSDSCert, downstreamPeerValidationSDSCert, sidecarSpec),
// When RequireClientCertificate is enabled trusted CA certs must be provided via ValidationContextType
RequireClientCertificate: &wrappers.BoolValue{Value: mTLS},
}
return tlsConfig
}
// GetUpstreamTLSContext creates an upstream Envoy TLS Context for the given downstream identity and upstream service pair
// Note: ServiceIdentity must be in the format "name.namespace" [https://github.com/openservicemesh/osm/issues/3188]
func GetUpstreamTLSContext(downstreamIdentity identity.ServiceIdentity, upstreamSvc service.MeshService, sidecarSpec configv1alpha2.SidecarSpec) *xds_auth.UpstreamTlsContext {
downstreamSDSCert := secrets.SDSCert{
Name: secrets.GetSecretNameForIdentity(downstreamIdentity),
CertType: secrets.ServiceCertType,
}
upstreamPeerValidationSDSCert := &secrets.SDSCert{
Name: upstreamSvc.String(),
CertType: secrets.RootCertTypeForMTLSOutbound,
}
commonTLSContext := getCommonTLSContext(downstreamSDSCert, upstreamPeerValidationSDSCert, sidecarSpec)
// Advertise in-mesh using UpstreamTlsContext.CommonTlsContext.AlpnProtocols
commonTLSContext.AlpnProtocols = ALPNInMesh
tlsConfig := &xds_auth.UpstreamTlsContext{
CommonTlsContext: commonTLSContext,
// The Sni field is going to be used to do FilterChainMatch in getInboundMeshHTTPFilterChain()
// The "Sni" field below of an incoming request will be matched against a list of server names
// in FilterChainMatch.ServerNames
Sni: upstreamSvc.ServerName(),
}
return tlsConfig
}
// GetADSConfigSource creates an Envoy ConfigSource struct.
func GetADSConfigSource() *xds_core.ConfigSource {
return &xds_core.ConfigSource{
ConfigSourceSpecifier: &xds_core.ConfigSource_Ads{
Ads: &xds_core.AggregatedConfigSource{},
},
ResourceApiVersion: xds_core.ApiVersion_V3,
}
}
// GetEnvoyServiceNodeID creates the string for Envoy's "--service-node" CLI argument for the Kubernetes sidecar container Command/Args
func GetEnvoyServiceNodeID(nodeID, workloadKind, workloadName string) string {
items := []string{
"$(POD_UID)",
"$(POD_NAMESPACE)",
"$(POD_IP)",
"$(SERVICE_ACCOUNT)",
nodeID,
"$(POD_NAME)",
workloadKind,
workloadName,
}
return strings.Join(items, constants.EnvoyServiceNodeSeparator)
}
// ParseEnvoyServiceNodeID parses the given Envoy service node ID and returns the encoded metadata
func ParseEnvoyServiceNodeID(serviceNodeID string) (*PodMetadata, error) {
chunks := strings.Split(serviceNodeID, constants.EnvoyServiceNodeSeparator)
if len(chunks) < 5 {
return nil, fmt.Errorf("invalid envoy service node id format")
}
meta := &PodMetadata{
UID: chunks[0],
Namespace: chunks[1],
IP: chunks[2],
ServiceAccount: identity.K8sServiceAccount{Name: chunks[3], Namespace: chunks[1]},
EnvoyNodeID: chunks[4],
}
if len(chunks) >= 8 {
meta.Name = chunks[5]
meta.WorkloadKind = chunks[6]
meta.WorkloadName = chunks[7]
}
return meta, nil
}
// GetCIDRRangeFromStr converts the given CIDR as a string to an XDS CidrRange object
func GetCIDRRangeFromStr(cidr string) (*xds_core.CidrRange, error) {
ip, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
prefixLen, _ := ipNet.Mask.Size()
return &xds_core.CidrRange{
AddressPrefix: ip.String(),
PrefixLen: &wrapperspb.UInt32Value{
Value: uint32(prefixLen),
},
}, nil
}