blob: e6fd75cc3f1da4050b6da691754ab7cb459a5115 [file] [log] [blame]
Scott Baker2c1c4822019-10-16 11:02:41 -07001// Copyright 2019 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package credentials implements gRPC credential interface with etcd specific logic.
16// e.g., client handshake with custom authority parameter
17package credentials
18
19import (
20 "context"
21 "crypto/tls"
22 "net"
23 "sync"
24
25 "go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
26 grpccredentials "google.golang.org/grpc/credentials"
27)
28
29// Config defines gRPC credential configuration.
30type Config struct {
31 TLSConfig *tls.Config
32}
33
34// Bundle defines gRPC credential interface.
35type Bundle interface {
36 grpccredentials.Bundle
37 UpdateAuthToken(token string)
38}
39
40// NewBundle constructs a new gRPC credential bundle.
41func NewBundle(cfg Config) Bundle {
42 return &bundle{
43 tc: newTransportCredential(cfg.TLSConfig),
44 rc: newPerRPCCredential(),
45 }
46}
47
48// bundle implements "grpccredentials.Bundle" interface.
49type bundle struct {
50 tc *transportCredential
51 rc *perRPCCredential
52}
53
54func (b *bundle) TransportCredentials() grpccredentials.TransportCredentials {
55 return b.tc
56}
57
58func (b *bundle) PerRPCCredentials() grpccredentials.PerRPCCredentials {
59 return b.rc
60}
61
62func (b *bundle) NewWithMode(mode string) (grpccredentials.Bundle, error) {
63 // no-op
64 return nil, nil
65}
66
67// transportCredential implements "grpccredentials.TransportCredentials" interface.
68type transportCredential struct {
69 gtc grpccredentials.TransportCredentials
70}
71
72func newTransportCredential(cfg *tls.Config) *transportCredential {
73 return &transportCredential{
74 gtc: grpccredentials.NewTLS(cfg),
75 }
76}
77
78func (tc *transportCredential) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, grpccredentials.AuthInfo, error) {
79 // Only overwrite when authority is an IP address!
80 // Let's say, a server runs SRV records on "etcd.local" that resolves
81 // to "m1.etcd.local", and its SAN field also includes "m1.etcd.local".
82 // But what if SAN does not include its resolved IP address (e.g. 127.0.0.1)?
83 // Then, the server should only authenticate using its DNS hostname "m1.etcd.local",
84 // instead of overwriting it with its IP address.
85 // And we do not overwrite "localhost" either. Only overwrite IP addresses!
86 if isIP(authority) {
87 target := rawConn.RemoteAddr().String()
88 if authority != target {
89 // When user dials with "grpc.WithDialer", "grpc.DialContext" "cc.parsedTarget"
90 // update only happens once. This is problematic, because when TLS is enabled,
91 // retries happen through "grpc.WithDialer" with static "cc.parsedTarget" from
92 // the initial dial call.
93 // If the server authenticates by IP addresses, we want to set a new endpoint as
94 // a new authority. Otherwise
95 // "transport: authentication handshake failed: x509: certificate is valid for 127.0.0.1, 192.168.121.180, not 192.168.223.156"
96 // when the new dial target is "192.168.121.180" whose certificate host name is also "192.168.121.180"
97 // but client tries to authenticate with previously set "cc.parsedTarget" field "192.168.223.156"
98 authority = target
99 }
100 }
101 return tc.gtc.ClientHandshake(ctx, authority, rawConn)
102}
103
104// return true if given string is an IP.
105func isIP(ep string) bool {
106 return net.ParseIP(ep) != nil
107}
108
109func (tc *transportCredential) ServerHandshake(rawConn net.Conn) (net.Conn, grpccredentials.AuthInfo, error) {
110 return tc.gtc.ServerHandshake(rawConn)
111}
112
113func (tc *transportCredential) Info() grpccredentials.ProtocolInfo {
114 return tc.gtc.Info()
115}
116
117func (tc *transportCredential) Clone() grpccredentials.TransportCredentials {
118 return &transportCredential{
119 gtc: tc.gtc.Clone(),
120 }
121}
122
123func (tc *transportCredential) OverrideServerName(serverNameOverride string) error {
124 return tc.gtc.OverrideServerName(serverNameOverride)
125}
126
127// perRPCCredential implements "grpccredentials.PerRPCCredentials" interface.
128type perRPCCredential struct {
129 authToken string
130 authTokenMu sync.RWMutex
131}
132
133func newPerRPCCredential() *perRPCCredential { return &perRPCCredential{} }
134
135func (rc *perRPCCredential) RequireTransportSecurity() bool { return false }
136
137func (rc *perRPCCredential) GetRequestMetadata(ctx context.Context, s ...string) (map[string]string, error) {
138 rc.authTokenMu.RLock()
139 authToken := rc.authToken
140 rc.authTokenMu.RUnlock()
141 return map[string]string{rpctypes.TokenFieldNameGRPC: authToken}, nil
142}
143
144func (b *bundle) UpdateAuthToken(token string) {
145 if b.rc == nil {
146 return
147 }
148 b.rc.UpdateAuthToken(token)
149}
150
151func (rc *perRPCCredential) UpdateAuthToken(token string) {
152 rc.authTokenMu.Lock()
153 rc.authToken = token
154 rc.authTokenMu.Unlock()
155}