XDS Server
XDS Server
istiod 中 xds Server 分为Secure/insecure 两种
initSecureDiscoveryService
对于启用tls的DiscoveryService需要先初始化spiffe验证器,对应的方法为setPeerCertVerifier
setPeerCertVerifier
func (s *Server) setPeerCertVerifier(tlsOptions TLSOptions) error {
if tlsOptions.CaCertFile == "" && s.CA == nil && features.SpiffeBundleEndpoints == "" {
// Running locally without configured certs - no TLS mode
return nil
}
s.peerCertVerifier = spiffe.NewPeerCertVerifier()
var rootCertBytes []byte
var err error
// 判断是否手动指定
if tlsOptions.CaCertFile != "" {
if rootCertBytes, err = ioutil.ReadFile(tlsOptions.CaCertFile); err != nil {
return err
}
} else {
// 加载RA cert
if s.RA != nil {
rootCertBytes = append(rootCertBytes, s.RA.GetCAKeyCertBundle().GetRootCertPem()...)
}
// 加载CA cert
if s.CA != nil {
rootCertBytes = append(rootCertBytes, s.CA.GetCAKeyCertBundle().GetRootCertPem()...)
}
}
if len(rootCertBytes) != 0 {
// 根据信任域添加添加CA证书到certPools/generalCertPool
err := s.peerCertVerifier.AddMappingFromPEM(spiffe.GetTrustDomain(), rootCertBytes)
if err != nil {
log.Errorf("Add Root CAs into peerCertVerifier failed: %v", err)
return fmt.Errorf("add root CAs into peerCertVerifier failed: %v", err)
}
}
if features.SpiffeBundleEndpoints != "" {
certMap, err := spiffe.RetrieveSpiffeBundleRootCertsFromStringInput(
features.SpiffeBundleEndpoints, []*x509.Certificate{})
if err != nil {
return err
}
s.peerCertVerifier.AddMappings(certMap)
}
return nil
}
在initSecureDiscoveryService初始化grpc server时通过该验证器进行验证客户端身份
cfg := &tls.Config{
GetCertificate: s.getIstiodCertificate,
ClientAuth: tls.VerifyClientCertIfGiven,
ClientCAs: s.peerCertVerifier.GetGeneralCertPool(),
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
err := s.peerCertVerifier.VerifyPeerCert(rawCerts, verifiedChains)
if err != nil {
log.Infof("Could not verify certificate: %v", err)
}
return err
},
}
具体验证客户端cert的逻辑如下
func (v *PeerCertVerifier) VerifyPeerCert(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if len(rawCerts) == 0 {
// Peer doesn't present a certificate. Just skip. Other authn methods may be used.
return nil
}
var peerCert *x509.Certificate
intCertPool := x509.NewCertPool()
for id, rawCert := range rawCerts {
cert, err := x509.ParseCertificate(rawCert)
if err != nil {
return err
}
if id == 0 {
peerCert = cert
} else {
intCertPool.AddCert(cert)
}
}
if len(peerCert.URIs) != 1 {
return fmt.Errorf("peer certificate does not contain 1 URI type SAN, detected %d", len(peerCert.URIs))
}
//根据证书的URI获取信任域
trustDomain, err := GetTrustDomainFromURISAN(peerCert.URIs[0].String())
if err != nil {
return err
}
// 根据信任域获取对应的根证书
rootCertPool, ok := v.certPools[trustDomain]
if !ok {
return fmt.Errorf("no cert pool found for trust domain %s", trustDomain)
}
// 验证客户端证书
_, err = peerCert.Verify(x509.VerifyOptions{
Roots: rootCertPool,
Intermediates: intCertPool,
})
return err
}
注册handler s.XDSServer.Register(s.secureGrpcServer)
StreamAggregatedResources 实现了envoy ADS接口
func (s *DiscoveryServer) StreamAggregatedResources(stream discovery.AggregatedDiscoveryService_StreamAggregatedResourcesServer) error {
// 检查服务器是否准备好接受客户端并处理新请求.当前准备就绪意味着缓存已同步,因此可以正确构建集群.如果不进行此检查,则下面的InitContext()调用将使用空配置初始化,从而导致重新连接的Envoy失去配置.这是除了添加caches之外的另一项安全检查。已将逻辑同步到就绪探针以处理kube-proxy ip表更新延迟的情况。
if !s.IsServerReady() {
return errors.New("server is not ready to serve discovery information")
}
ctx := stream.Context()
peerAddr := "0.0.0.0"
// 获取节点信息
if peerInfo, ok := peer.FromContext(ctx); ok {
peerAddr = peerInfo.Addr.String()
}
// 对节点身份进行验证
ids, err := s.authenticate(ctx)
if err != nil {
return err
}
if ids != nil {
adsLog.Debugf("Authenticated XDS: %v with identity %v", peerAddr, ids)
} else {
adsLog.Debuga("Unauthenticated XDS: ", peerAddr)
}
// 初始化上下文,因为启动了insecure,不确定哪一个先收到请求
if err = s.globalPushContext().InitContext(s.Env, nil, nil); err != nil {
adsLog.Warnf("Error reading config %v", err)
return err
}
con := newConnection(peerAddr, stream)
con.Identities = ids
// 从客户端接收信息
var receiveError error
reqChannel := make(chan *discovery.DiscoveryRequest, 1)
go s.receive(con, reqChannel, &receiveError)
for {
select {
// 读取请求
case req, ok := <-reqChannel:
if !ok {
return receiveError
}
// 返回信息给客户端
err := s.processRequest(req, con)
if err != nil {
return err
}
// 获取推送channnel的数据,eventhandler控制器将根据资源变更发送数据到pushChannel
case pushEv := <-con.pushChannel:
err := s.pushConnection(con, pushEv)
pushEv.done()
if err != nil {
return err
}
case <-con.stop:
return nil
}
}
}
客户端身份认证
func (s *DiscoveryServer) authenticate(ctx context.Context) ([]string, error) {
if !features.XDSAuth {
return nil, nil
}
// 当前仅检查该请求是否具有使用我们的密钥签名的证书.受标志保护以避免破坏升级-应该在公开XDS的多集群/网格扩展中启用。
peerInfo, ok := peer.FromContext(ctx)
if !ok {
return nil, errors.New("invalid context")
}
// 不是TLS连接,我们将不执行身份验证,
if _, ok := peerInfo.AuthInfo.(credentials.TLSInfo); !ok {
return nil, nil
}
authFailMsgs := []string{}
// 遍历认证器,任何一个验证通过则认为通过
for _, authn := range s.Authenticators {
u, err := authn.Authenticate(ctx)
if u != nil && u.Identities != nil && err == nil {
return u.Identities, nil
}
....
}
具体逻辑参见18节
处理请求的连接
func (s *DiscoveryServer) processRequest(req *discovery.DiscoveryRequest, con *Connection) error {
if s.StatusReporter != nil {
s.StatusReporter.RegisterEvent(con.ConID, req.TypeUrl, req.ResponseNonce)
}
if !s.shouldRespond(con, req) {
return nil
}
push := s.globalPushContext()
// 推送对应数据给客户端
return s.pushXds(con, push, versionInfo(), con.Watched(req.TypeUrl), &model.PushRequest{Full: true})
}
主动推送
func (s *DiscoveryServer) pushConnection(con *Connection, pushEv *Event) error {
pushRequest := pushEv.pushRequest
// 全量推送更新节点当前的信息
if pushRequest.Full {
// Update Proxy with current information.
s.updateProxy(con.proxy, pushRequest.Push)
}
// 判断是否需要推送
if !ProxyNeedsPush(con.proxy, pushEv) {
adsLog.Debugf("Skipping push to %v, no updates required", con.ConID)
// 只有全量推送增加版本,增量推送不更新版本
if pushRequest.Full {
reportAllEvents(s.StatusReporter, con.ConID, pushRequest.Push.Version, nil)
}
return nil
}
currentVersion := versionInfo()
// 向所有生成器发送推送,每个生成器负责确定推送事件是否需要推送
for _, w := range getPushResources(con.proxy.WatchedResources) {
err := s.pushXds(con, pushRequest.Push, currentVersion, w, pushRequest)
if err != nil {
return err
}
}
if pushRequest.Full {
// 像unwatch资源报告所有事件.watch的资源将通过pushXds或ack报告。
reportAllEvents(s.StatusReporter, con.ConID, pushRequest.Push.Version, con.proxy.WatchedResources)
}
proxiesConvergeDelay.Record(time.Since(pushRequest.Start).Seconds())
return nil
}
func (s *DiscoveryServer) pushXds(con *Connection, push *model.PushContext,
currentVersion string, w *model.WatchedResource, req *model.PushRequest) error {
if w == nil {
return nil
}
//根据请求url 确定Generator
gen := s.findGenerator(w.TypeUrl, con)
if gen == nil {
return nil
}
t0 := time.Now()
// 生成resource
cl := gen.Generate(con.proxy, push, w, req)
if cl == nil {
//如果没有内容推送,返回ACK
if s.StatusReporter != nil {
s.StatusReporter.RegisterEvent(con.ConID, w.TypeUrl, push.Version)
}
return nil
}
defer func() { recordPushTime(w.TypeUrl, time.Since(t0)) }()
resp := &discovery.DiscoveryResponse{
TypeUrl: w.TypeUrl,
VersionInfo: currentVersion,
Nonce: nonce(push.Version),
Resources: cl,
}
// 返回数据
err := con.send(resp)
if err != nil {
recordSendError(w.TypeUrl, con.ConID, err)
return err
}
if _, f := SkipLogTypes[w.TypeUrl]; !f {
adsLog.Infof("%s: PUSH for node:%s resources:%d", v3.GetShortType(w.TypeUrl), con.proxy.ID, len(cl))
}
return nil
}
cds
func (c CdsGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource, req *model.PushRequest) model.Resources {
if !cdsNeedsPush(req, proxy) {
return nil
}
rawClusters := c.Server.ConfigGenerator.BuildClusters(proxy, push)
resources := model.Resources{}
for _, c := range rawClusters {
resources = append(resources, util.MessageToAny(c))
}
return resources
}
eds
func (eds *EdsGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource, req *model.PushRequest) model.Resources {
if !edsNeedsPush(req.ConfigsUpdated) {
return nil
}
var edsUpdatedServices map[string]struct{}
if !req.Full {
edsUpdatedServices = model.ConfigNamesOfKind(req.ConfigsUpdated, gvk.ServiceEntry)
}
resources := make([]*any.Any, 0)
empty := 0
cached := 0
regenerated := 0
for _, clusterName := range w.ResourceNames {
if edsUpdatedServices != nil {
_, _, hostname, _ := model.ParseSubsetKey(clusterName)
if _, ok := edsUpdatedServices[string(hostname)]; !ok {
// Cluster was not updated, skip recomputing. This happens when we get an incremental update for a
// specific Hostname. On connect or for full push edsUpdatedServices will be empty.
continue
}
}
builder := NewEndpointBuilder(clusterName, proxy, push)
if marshalledEndpoint, f := eds.Server.Cache.Get(builder); f {
resources = append(resources, marshalledEndpoint)
cached++
} else {
l := eds.Server.generateEndpoints(builder)
if l == nil {
continue
}
regenerated++
if len(l.Endpoints) == 0 {
empty++
}
resource := util.MessageToAny(l)
resources = append(resources, resource)
eds.Server.Cache.Add(builder, resource)
}
}
if len(edsUpdatedServices) == 0 {
adsLog.Infof("EDS: PUSH for node:%s resources:%d empty:%v cached:%v/%v",
proxy.ID, len(resources), empty, cached, cached+regenerated)
} else {
adsLog.Debugf("EDS: PUSH INC for node:%s clusters:%d empty:%v cached:%v/%v",
proxy.ID, len(resources), empty, cached, cached+regenerated)
}
return resources
}
lds
func (l LdsGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource, req *model.PushRequest) model.Resources {
if !ldsNeedsPush(req) {
return nil
}
listeners := l.Server.ConfigGenerator.BuildListeners(proxy, push)
resources := model.Resources{}
for _, c := range listeners {
resources = append(resources, util.MessageToAny(c))
}
return resources
}
nds
func (n NdsGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource, req *model.PushRequest) model.Resources {
if !ndsNeedsPush(req) {
return nil
}
nt := n.Server.ConfigGenerator.BuildNameTable(proxy, push)
if nt == nil {
return nil
}
resources := model.Resources{util.MessageToAny(nt)}
return resources
}
rds
func (c RdsGenerator) Generate(proxy *model.Proxy, push *model.PushContext, w *model.WatchedResource, req *model.PushRequest) model.Resources {
if !rdsNeedsPush(req) {
return nil
}
rawRoutes := c.Server.ConfigGenerator.BuildHTTPRoutes(proxy, push, w.ResourceNames)
resources := model.Resources{}
for _, c := range rawRoutes {
resources = append(resources, util.MessageToAny(c))
}
return resources
}
Last updated