Skip to content

Commit

Permalink
add security level negotiation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
yihuazhang committed Dec 13, 2019
1 parent e3baa76 commit e501cd9
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 7 deletions.
7 changes: 7 additions & 0 deletions credentials/alts/internal/authinfo/authinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ var _ credentials.AuthInfo = (*altsAuthInfo)(nil)
// application. altsAuthInfo is immutable and implements credentials.AuthInfo.
type altsAuthInfo struct {
p *altspb.AltsContext
info credentials.CommonAuthInfo
}

// New returns a new altsAuthInfo object given handshaker results.
Expand All @@ -48,6 +49,7 @@ func newAuthInfo(result *altspb.HandshakerResult) *altsAuthInfo {
LocalServiceAccount: result.GetLocalIdentity().GetServiceAccount(),
PeerRpcVersions: result.GetPeerRpcVersions(),
},
info: credentials.CommonAuthInfo{credentials.PrivacyAndIntegrity},
}
}

Expand All @@ -56,6 +58,11 @@ func (s *altsAuthInfo) AuthType() string {
return "alts"
}

// CommonInfo returns the common auth information.
func (s *altsAuthInfo) CommonInfo() *credentials.CommonAuthInfo {
return &s.info
}

// ApplicationProtocol returns the context's application protocol.
func (s *altsAuthInfo) ApplicationProtocol() string {
return s.p.GetApplicationProtocol()
Expand Down
38 changes: 36 additions & 2 deletions credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ type PerRPCCredentials interface {
RequireTransportSecurity() bool
}

// SecurityLevel defines the protection level on an established channel.
type SecurityLevel int

const (
// SecurityNone indicates a channel is a plaintext channel that does not provide any protection.
SecurityNone SecurityLevel = iota
// IntegrityOnly indicates a channel only provides integrity protection.
IntegrityOnly
// PrivacyAndIntegrity indicates a channel provides both privacy and integrity protection.
PrivacyAndIntegrity
)

// CommonAuthInfo contains authenticated information common to AuthInfo implementations.
type CommonAuthInfo struct {
Level SecurityLevel
}

// ProtocolInfo provides information regarding the gRPC wire protocol version,
// security protocol, security protocol version in use, server name, etc.
type ProtocolInfo struct {
Expand Down Expand Up @@ -125,8 +142,10 @@ type Bundle interface {
//
// This API is experimental.
type RequestInfo struct {
// The method passed to Invoke or NewStream for this RPC. (For proto methods, this has the format "/some.Service/Method")
Method string
// The method passed to Invoke or NewStream for this RPC. (For proto methods, this has the format "/some.Service/Method")
Method string
// AuthInfo contains the information resulted from a security handshake (TransportCredentials.ClientHandshake, TransportCredentials.ServerHandshake)
AuthInfo AuthInfo
}

// requestInfoKey is a struct to be used as the key when attaching a RequestInfo to a context object.
Expand All @@ -140,6 +159,21 @@ func RequestInfoFromContext(ctx context.Context) (ri RequestInfo, ok bool) {
return
}

// CheckSecurityLevel checks if channel's security level is greater than or equal to the specified one.
// It returns true if 1) the condition is satisified or 2) AuthInfo struct does not have Info() method.
//
// This API is experimental.
func CheckSecurityLevel(ctx context.Context, level SecurityLevel) (bool) {
type internalInfo interface {
CommonInfo() *CommonAuthInfo
}
ri, _ := RequestInfoFromContext(ctx)
if ci, ok := ri.AuthInfo.(internalInfo); ok {
return ci.CommonInfo().Level >= level
}
return true
}

func init() {
internal.NewRequestInfoContext = func(ctx context.Context, ri RequestInfo) context.Context {
return context.WithValue(ctx, requestInfoKey{}, ri)
Expand Down
53 changes: 51 additions & 2 deletions credentials/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,57 @@ import (
"testing"

"google.golang.org/grpc/testdata"
"google.golang.org/grpc/internal"
)

// A struct that implements AuthInfo interface but does not implement CommonInfo() method.
type testAuthInfoNoInfoMethod struct{}

func (ta testAuthInfoNoInfoMethod) AuthType() string {
return "testAuthInfoNoInfoMethod"
}

// A struct that implements AuthInfo interface and implements CommonInfo() method.
type testAuthInfo struct{
Info CommonAuthInfo
}

func (ta testAuthInfo) AuthType() string {
return "testAuthInfo"
}

func (ta testAuthInfo) CommonInfo() *CommonAuthInfo {
return &ta.Info
}

func (ta *testAuthInfo) SetSecurityLevel(level SecurityLevel) {
ta.Info.Level = level
}

func TestCheckSecurityLevel(t *testing.T) {
auth := &testAuthInfo{CommonAuthInfo{PrivacyAndIntegrity}}
ri := RequestInfo{
Method: "testInfo",
AuthInfo: auth,
}
ctxWithRequestInfo := internal.NewRequestInfoContext.(func(context.Context, RequestInfo) context.Context)(context.Background(), ri)
if !CheckSecurityLevel(ctxWithRequestInfo, PrivacyAndIntegrity) {
t.Fatalf("CheckSeurityLevel() returned false, want true")
}
}

func TestCheckSecurityLevelNoInfoMethod(t *testing.T) {
auth := &testAuthInfoNoInfoMethod{}
ri := RequestInfo{
Method: "testInfo",
AuthInfo: auth,
}
ctxWithRequestInfo := internal.NewRequestInfoContext.(func(context.Context, RequestInfo) context.Context)(context.Background(), ri)
if !CheckSecurityLevel(ctxWithRequestInfo, PrivacyAndIntegrity) {
t.Fatalf("CheckSeurityLevel() returned false, want true")
}
}

func TestTLSOverrideServerName(t *testing.T) {
expectedServerName := "server.name"
c := NewTLS(nil)
Expand Down Expand Up @@ -225,7 +274,7 @@ func tlsServerHandshake(conn net.Conn) (AuthInfo, error) {
if err != nil {
return nil, err
}
return TLSInfo{State: serverConn.ConnectionState()}, nil
return TLSInfo{State: serverConn.ConnectionState(), Info: CommonAuthInfo {PrivacyAndIntegrity} }, nil
}

func tlsClientHandshake(conn net.Conn, _ string) (AuthInfo, error) {
Expand All @@ -234,7 +283,7 @@ func tlsClientHandshake(conn net.Conn, _ string) (AuthInfo, error) {
if err := clientConn.Handshake(); err != nil {
return nil, err
}
return TLSInfo{State: clientConn.ConnectionState()}, nil
return TLSInfo{State: clientConn.ConnectionState(), Info: CommonAuthInfo {PrivacyAndIntegrity} }, nil
}

func TestAppendH2ToNextProtos(t *testing.T) {
Expand Down
12 changes: 12 additions & 0 deletions credentials/oauth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ func (ts TokenSource) GetRequestMetadata(ctx context.Context, uri ...string) (ma
if err != nil {
return nil, err
}
if !credentials.CheckSecurityLevel(ctx, credentials.PrivacyAndIntegrity) {
return nil, fmt.Errorf("channel is not secure enough to transfer TokenSource PerRPCCredentials which requires PrivacyAndIntegrity")
}
return map[string]string{
"authorization": token.Type() + " " + token.AccessToken,
}, nil
Expand Down Expand Up @@ -79,6 +82,9 @@ func (j jwtAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[s
if err != nil {
return nil, err
}
if !credentials.CheckSecurityLevel(ctx, credentials.PrivacyAndIntegrity) {
return nil, fmt.Errorf("channel is not secure enough to transfer JWTAccess PerRPCCredentials which requires PrivacyAndIntegrity")
}
return map[string]string{
"authorization": token.Type() + " " + token.AccessToken,
}, nil
Expand All @@ -99,6 +105,9 @@ func NewOauthAccess(token *oauth2.Token) credentials.PerRPCCredentials {
}

func (oa oauthAccess) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
if !credentials.CheckSecurityLevel(ctx, credentials.PrivacyAndIntegrity) {
return nil, fmt.Errorf("channel is not secure enough to transfer OauthAccess PerRPCCredentials which requires PrivacyAndIntegrity")
}
return map[string]string{
"authorization": oa.token.Type() + " " + oa.token.AccessToken,
}, nil
Expand Down Expand Up @@ -133,6 +142,9 @@ func (s *serviceAccount) GetRequestMetadata(ctx context.Context, uri ...string)
return nil, err
}
}
if !credentials.CheckSecurityLevel(ctx, credentials.PrivacyAndIntegrity) {
return nil, fmt.Errorf("channel is not secure enough to transfer ServiceAccount PerRPCCredentials which requires PrivacyAndIntegrity")
}
return map[string]string{
"authorization": s.t.Type() + " " + s.t.AccessToken,
}, nil
Expand Down
10 changes: 8 additions & 2 deletions credentials/tls.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,19 @@ import (
// It implements the AuthInfo interface.
type TLSInfo struct {
State tls.ConnectionState
Info CommonAuthInfo
}

// AuthType returns the type of TLSInfo as a string.
func (t TLSInfo) AuthType() string {
return "tls"
}

// CommonInfo returns the common auth information.
func (t TLSInfo) CommonInfo() *CommonAuthInfo {
return &t.Info
}

// GetSecurityValue returns security info requested by channelz.
func (t TLSInfo) GetSecurityValue() ChannelzSecurityValue {
v := &TLSChannelzSecurityValue{
Expand Down Expand Up @@ -90,15 +96,15 @@ func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawCon
case <-ctx.Done():
return nil, nil, ctx.Err()
}
return internal.WrapSyscallConn(rawConn, conn), TLSInfo{conn.ConnectionState()}, nil
return internal.WrapSyscallConn(rawConn, conn), TLSInfo{conn.ConnectionState(), CommonAuthInfo{PrivacyAndIntegrity}}, nil
}

func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) {
conn := tls.Server(rawConn, c.config)
if err := conn.Handshake(); err != nil {
return nil, nil, err
}
return internal.WrapSyscallConn(rawConn, conn), TLSInfo{conn.ConnectionState()}, nil
return internal.WrapSyscallConn(rawConn, conn), TLSInfo{conn.ConnectionState(), CommonAuthInfo{PrivacyAndIntegrity}}, nil
}

func (c *tlsCreds) Clone() TransportCredentials {
Expand Down
2 changes: 1 addition & 1 deletion internal/transport/handler_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream), trace
Addr: ht.RemoteAddr(),
}
if req.TLS != nil {
pr.AuthInfo = credentials.TLSInfo{State: *req.TLS}
pr.AuthInfo = credentials.TLSInfo{State: *req.TLS, Info: credentials.CommonAuthInfo {credentials.PrivacyAndIntegrity}}
}
ctx = metadata.NewIncomingContext(ctx, ht.headerMD)
s.ctx = peer.NewContext(ctx, pr)
Expand Down
1 change: 1 addition & 0 deletions internal/transport/http2_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr)
aud := t.createAudience(callHdr)
ri := credentials.RequestInfo{
Method: callHdr.Method,
AuthInfo: t.authInfo,
}
ctxWithRequestInfo := internal.NewRequestInfoContext.(func(context.Context, credentials.RequestInfo) context.Context)(ctx, ri)
authData, err := t.getTrAuthData(ctxWithRequestInfo, aud)
Expand Down

0 comments on commit e501cd9

Please sign in to comment.