blob: 7f3008a79c09f5b994d98ea1464ad092872a3a3d [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package option
import (
"encoding/json"
"fmt"
"net"
"os"
"strings"
)
import (
cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
auth "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
"github.com/envoyproxy/go-control-plane/pkg/conversion"
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
"google.golang.org/protobuf/types/known/durationpb"
pstruct "google.golang.org/protobuf/types/known/structpb"
wrappers "google.golang.org/protobuf/types/known/wrapperspb"
meshAPI "istio.io/api/mesh/v1alpha1"
networkingAPI "istio.io/api/networking/v1alpha3"
"istio.io/pkg/log"
)
import (
"github.com/apache/dubbo-go-pixiu/pilot/pkg/model"
"github.com/apache/dubbo-go-pixiu/pilot/pkg/networking/util"
authn_model "github.com/apache/dubbo-go-pixiu/pilot/pkg/security/model"
"github.com/apache/dubbo-go-pixiu/pkg/security"
)
// TransportSocket wraps UpstreamTLSContext
type TransportSocket struct {
Name string `json:"name,omitempty"`
TypedConfig *pstruct.Struct `json:"typed_config,omitempty"`
}
func keepaliveConverter(value *networkingAPI.ConnectionPoolSettings_TCPSettings_TcpKeepalive) convertFunc {
return func(*instance) (interface{}, error) {
upstreamConnectionOptions := &cluster.UpstreamConnectionOptions{
TcpKeepalive: &core.TcpKeepalive{},
}
if value.Probes > 0 {
upstreamConnectionOptions.TcpKeepalive.KeepaliveProbes = &wrappers.UInt32Value{Value: value.Probes}
}
if value.Time != nil && value.Time.Seconds > 0 {
upstreamConnectionOptions.TcpKeepalive.KeepaliveTime = &wrappers.UInt32Value{Value: uint32(value.Time.Seconds)}
}
if value.Interval != nil && value.Interval.Seconds > 0 {
upstreamConnectionOptions.TcpKeepalive.KeepaliveInterval = &wrappers.UInt32Value{Value: uint32(value.Interval.Seconds)}
}
return convertToJSON(upstreamConnectionOptions), nil
}
}
func transportSocketConverter(tls *networkingAPI.ClientTLSSettings, sniName string, metadata *model.BootstrapNodeMetadata, isH2 bool) convertFunc {
return func(*instance) (interface{}, error) {
tlsContext := tlsContextConvert(tls, sniName, metadata)
if tlsContext == nil {
return "", nil
}
if !isH2 {
tlsContext.CommonTlsContext.AlpnProtocols = nil
}
// This double conversion is to encode the typed config and get it out as struct
// so that convertToJSON properly encodes the structure. Since this is just for
// bootstrap generation this is better than having our custom structs.
tlsContextStruct, _ := conversion.MessageToStruct(util.MessageToAny(tlsContext))
transportSocket := &TransportSocket{
Name: wellknown.TransportSocketTls,
TypedConfig: tlsContextStruct,
}
return convertToJSON(transportSocket), nil
}
}
// TODO(ramaraochavali): Unify this code with cluster upstream TLS settings logic.
func tlsContextConvert(tls *networkingAPI.ClientTLSSettings, sniName string, metadata *model.BootstrapNodeMetadata) *auth.UpstreamTlsContext {
tlsContext := &auth.UpstreamTlsContext{
CommonTlsContext: &auth.CommonTlsContext{},
}
switch tls.Mode {
case networkingAPI.ClientTLSSettings_SIMPLE:
res := security.SdsCertificateConfig{
CaCertificatePath: model.GetOrDefault(metadata.TLSClientRootCert, tls.CaCertificates),
}
tlsContext.CommonTlsContext.ValidationContextType = &auth.CommonTlsContext_CombinedValidationContext{
CombinedValidationContext: &auth.CommonTlsContext_CombinedCertificateValidationContext{
DefaultValidationContext: &auth.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch(tls.SubjectAltNames)},
ValidationContextSdsSecretConfig: authn_model.ConstructSdsSecretConfig(res.GetRootResourceName()),
},
}
tlsContext.CommonTlsContext.AlpnProtocols = util.ALPNH2Only
tlsContext.Sni = tls.Sni
case networkingAPI.ClientTLSSettings_MUTUAL:
res := security.SdsCertificateConfig{
CertificatePath: model.GetOrDefault(metadata.TLSClientCertChain, tls.ClientCertificate),
PrivateKeyPath: model.GetOrDefault(metadata.TLSClientKey, tls.PrivateKey),
CaCertificatePath: model.GetOrDefault(metadata.TLSClientRootCert, tls.CaCertificates),
}
if len(res.GetResourceName()) > 0 {
tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs = append(tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs,
authn_model.ConstructSdsSecretConfig(res.GetResourceName()))
}
tlsContext.CommonTlsContext.ValidationContextType = &auth.CommonTlsContext_CombinedValidationContext{
CombinedValidationContext: &auth.CommonTlsContext_CombinedCertificateValidationContext{
DefaultValidationContext: &auth.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch(tls.SubjectAltNames)},
ValidationContextSdsSecretConfig: authn_model.ConstructSdsSecretConfig(res.GetRootResourceName()),
},
}
tlsContext.CommonTlsContext.AlpnProtocols = util.ALPNH2Only
tlsContext.Sni = tls.Sni
case networkingAPI.ClientTLSSettings_ISTIO_MUTUAL:
tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs = append(tlsContext.CommonTlsContext.TlsCertificateSdsSecretConfigs,
authn_model.ConstructSdsSecretConfig(authn_model.SDSDefaultResourceName))
tlsContext.CommonTlsContext.ValidationContextType = &auth.CommonTlsContext_CombinedValidationContext{
CombinedValidationContext: &auth.CommonTlsContext_CombinedCertificateValidationContext{
DefaultValidationContext: &auth.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch(tls.SubjectAltNames)},
ValidationContextSdsSecretConfig: authn_model.ConstructSdsSecretConfig(authn_model.SDSRootResourceName),
},
}
tlsContext.CommonTlsContext.AlpnProtocols = util.ALPNInMeshH2
tlsContext.Sni = tls.Sni
// For ISTIO_MUTUAL if custom SNI is not provided, use the default SNI name.
if len(tls.Sni) == 0 {
tlsContext.Sni = sniName
}
default:
// No TLS.
return nil
}
return tlsContext
}
func nodeMetadataConverter(metadata *model.BootstrapNodeMetadata, rawMeta map[string]interface{}) convertFunc {
return func(*instance) (interface{}, error) {
marshalString, err := marshalMetadata(metadata, rawMeta)
if err != nil {
return "", err
}
return marshalString, nil
}
}
func sanConverter(sans []string) convertFunc {
return func(*instance) (interface{}, error) {
matchers := []string{}
for _, s := range sans {
matchers = append(matchers, fmt.Sprintf(`{"exact":"%s"}`, s))
}
return "[" + strings.Join(matchers, ",") + "]", nil
}
}
func addressConverter(addr string) convertFunc {
return func(o *instance) (interface{}, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("unable to parse %s address %q: %v", o.name, addr, err)
}
if host == "$(HOST_IP)" {
// Replace host with HOST_IP env var if it is "$(HOST_IP)".
// This is to support some tracer setting (Datadog, Zipkin), where "$(HOST_IP)"" is used for address.
// Tracer address used to be specified within proxy container params, and thus could be interpreted with pod HOST_IP env var.
// Now tracer config is passed in with mesh config volumn at gateway, k8s env var interpretation does not work.
// This is to achieve the same interpretation as k8s.
hostIPEnv := os.Getenv("HOST_IP")
if hostIPEnv != "" {
host = hostIPEnv
}
}
return fmt.Sprintf("{\"address\": \"%s\", \"port_value\": %s}", host, port), nil
}
}
func jsonConverter(d interface{}) convertFunc {
return func(o *instance) (interface{}, error) {
b, err := json.Marshal(d)
return string(b), err
}
}
func durationConverter(value *durationpb.Duration) convertFunc {
return func(*instance) (interface{}, error) {
return value.AsDuration().String(), nil
}
}
// openCensusAgentContextConverter returns a converter that returns the list of
// distributed trace contexts to propagate with envoy.
func openCensusAgentContextConverter(contexts []meshAPI.Tracing_OpenCensusAgent_TraceContext) convertFunc {
allContexts := `["TRACE_CONTEXT","GRPC_TRACE_BIN","CLOUD_TRACE_CONTEXT","B3"]`
return func(*instance) (interface{}, error) {
if len(contexts) == 0 {
return allContexts, nil
}
var envoyContexts []string
for _, c := range contexts {
switch c {
// Ignore UNSPECIFIED
case meshAPI.Tracing_OpenCensusAgent_W3C_TRACE_CONTEXT:
envoyContexts = append(envoyContexts, "TRACE_CONTEXT")
case meshAPI.Tracing_OpenCensusAgent_GRPC_BIN:
envoyContexts = append(envoyContexts, "GRPC_TRACE_BIN")
case meshAPI.Tracing_OpenCensusAgent_CLOUD_TRACE_CONTEXT:
envoyContexts = append(envoyContexts, "CLOUD_TRACE_CONTEXT")
case meshAPI.Tracing_OpenCensusAgent_B3:
envoyContexts = append(envoyContexts, "B3")
}
}
return convertToJSON(envoyContexts), nil
}
}
func convertToJSON(v interface{}) string {
if v == nil {
return ""
}
b, err := json.Marshal(v)
if err != nil {
log.Error(err.Error())
return ""
}
return string(b)
}
// marshalMetadata combines type metadata and untyped metadata and marshals to json
// This allows passing arbitrary metadata to Envoy, while still supported typed metadata for known types
func marshalMetadata(metadata *model.BootstrapNodeMetadata, rawMeta map[string]interface{}) (string, error) {
b, err := json.Marshal(metadata)
if err != nil {
return "", err
}
var output map[string]interface{}
if err := json.Unmarshal(b, &output); err != nil {
return "", err
}
// Add all untyped metadata
for k, v := range rawMeta {
// Do not override fields, as we may have made modifications to the type metadata
// This means we will only add "unknown" fields here
if _, f := output[k]; !f {
output[k] = v
}
}
res, err := json.Marshal(output)
if err != nil {
return "", err
}
return string(res), nil
}