Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

credentials: check and expose SPIFFE ID #3626

Merged
merged 6 commits into from Jul 16, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
149 changes: 149 additions & 0 deletions credentials/credentials_go10_test.go
@@ -0,0 +1,149 @@
// +build go1.10

/*
*
* Copyright 2020 gRPC 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 credentials

import (
"crypto/tls"
"crypto/x509"
"net/url"
"testing"
)

func (s) TestParseSpiffeID(t *testing.T) {
ZhenLian marked this conversation as resolved.
Show resolved Hide resolved
tests := []struct {
name string
urls []*url.URL
// If we expect ParseSpiffeID to return an error.
expectError bool
// If we expect TLSInfo.SpiffeID to be plumbed.
expectID bool
}{
{
name: "empty URIs",
urls: []*url.URL{},
expectError: false,
expectID: false,
},
{
name: "good SPIFFE ID",
urls: []*url.URL{
{
Scheme: "spiffe",
Host: "foo.bar.com",
Path: "workload/wl1",
RawPath: "workload/wl1",
},
{
Scheme: "https",
Host: "foo.bar.com",
Path: "workload/wl1",
RawPath: "workload/wl1",
},
},
expectError: false,
expectID: true,
},
{
name: "invalid host",
urls: []*url.URL{
{
Scheme: "spiffe",
Host: "",
Path: "workload/wl1",
RawPath: "workload/wl1",
},
},
expectError: true,
expectID: false,
},
{
name: "invalid path",
urls: []*url.URL{
{
Scheme: "spiffe",
Host: "foo.bar.com",
Path: "",
RawPath: "",
},
},
expectError: true,
expectID: false,
},
{
name: "large path",
urls: []*url.URL{
{
Scheme: "spiffe",
Host: "foo.bar.com",
Path: string(make([]byte, 2050)),
RawPath: string(make([]byte, 2050)),
},
},
expectError: true,
expectID: false,
},
{
name: "large host",
urls: []*url.URL{
{
Scheme: "spiffe",
Host: string(make([]byte, 256)),
Path: "workload/wl1",
RawPath: "workload/wl1",
},
},
expectError: true,
expectID: false,
},
{
name: "multiple SPIFFE IDs",
urls: []*url.URL{
{
Scheme: "spiffe",
Host: "foo.bar.com",
Path: "workload/wl1",
RawPath: "workload/wl1",
},
{
Scheme: "spiffe",
Host: "bar.baz.com",
Path: "workload/wl2",
RawPath: "workload/wl2",
},
},
expectError: false,
expectID: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
info := TLSInfo{
State: tls.ConnectionState{PeerCertificates: []*x509.Certificate{{URIs: tt.urls}}}}
err := info.ParseSpiffeID()
if got, want := err != nil, tt.expectError; got != want {
t.Errorf("want expectError = %v, but got expectError = %v, with error %v", want, got, err)
}
if got, want := info.SpiffeID != nil, tt.expectID; got != want {
t.Errorf("want expectID = %v, but spiffe ID is %v", want, info.SpiffeID)
}
})
}
}
69 changes: 69 additions & 0 deletions credentials/go10.go
@@ -0,0 +1,69 @@
// +build go1.10
ZhenLian marked this conversation as resolved.
Show resolved Hide resolved

/*
*
* Copyright 2020 gRPC 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 credentials

import (
"fmt"
"net/url"

"google.golang.org/grpc/grpclog"
)

// ParseSpiffeID parses the Spiffe ID from State and fill it into SpiffeID.
// An error is returned only when we are sure Spiffe ID is used but the format
// is wrong.
// This function can only be used with go version 1.10 and onwards. When used
// with a prior version, no error will be returned, but the field
// TLSInfo.SpiffeID wouldn't be plumbed.
ZhenLian marked this conversation as resolved.
Show resolved Hide resolved
func (t *TLSInfo) ParseSpiffeID() error {
ZhenLian marked this conversation as resolved.
Show resolved Hide resolved
ZhenLian marked this conversation as resolved.
Show resolved Hide resolved
if len(t.State.PeerCertificates) == 0 || len(t.State.PeerCertificates[0].URIs) == 0 {
return nil
}
spiffeIDCnt := 0
var spiffeID url.URL
for _, uri := range t.State.PeerCertificates[0].URIs {
if uri == nil || uri.Scheme != "spiffe" || uri.Opaque != "" || (uri.User != nil && uri.User.Username() != "") {
continue
}
// From this point, we assume the uri is intended for a Spiffe ID.
if len(uri.Host)+len(uri.Scheme)+len(uri.RawPath)+4 > 2048 ||
len(uri.Host)+len(uri.Scheme)+len(uri.Path)+4 > 2048 {
return fmt.Errorf("invalid SPIFFE ID: total ID length larger than 2048 bytes")
}
if len(uri.Host) == 0 || len(uri.RawPath) == 0 || len(uri.Path) == 0 {
return fmt.Errorf("invalid SPIFFE ID: domain or workload ID is empty")
}
if len(uri.Host) > 255 {
return fmt.Errorf("invalid SPIFFE ID: domain length larger than 255 characters")
}
// We use a default deep copy since we know the User field of a SPIFFE ID is empty.
spiffeID = *uri
spiffeIDCnt++
}
if spiffeIDCnt == 1 {
t.SpiffeID = &spiffeID
} else if spiffeIDCnt > 1 {
// A standard SPIFFE ID should be unique. If there are more, we log this
// mis-behavior and not plumb any of them.
grpclog.Warning("invalid SPIFFE ID: multiple SPIFFE IDs")
}
return nil
}
30 changes: 30 additions & 0 deletions credentials/gobefore10.go
@@ -0,0 +1,30 @@
// +build !go1.10

/*
*
* Copyright 2020 gRPC 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 credentials

import (
"google.golang.org/grpc/grpclog"
)

func (t *TLSInfo) ParseSpiffeID() error {
grpclog.Info("go version prior to 1.10 doesn't support parsing URIs in certificates. Please consider a newer version")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can delete this; users don't need a log line every time they make a connection, and Go 1.9 isn't supported anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SG. I added a TODO above this function to remind me for the future deletion of this file. Please let me know if this works for you. Thanks!

return nil
}
26 changes: 23 additions & 3 deletions credentials/tls.go
Expand Up @@ -25,14 +25,16 @@ import (
"fmt"
"io/ioutil"
"net"
"net/url"

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

// TLSInfo contains the auth information for a TLS authenticated connection.
// It implements the AuthInfo interface.
type TLSInfo struct {
State tls.ConnectionState
State tls.ConnectionState
SpiffeID *url.URL
ZhenLian marked this conversation as resolved.
Show resolved Hide resolved
ZhenLian marked this conversation as resolved.
Show resolved Hide resolved
CommonAuthInfo
}

Expand Down Expand Up @@ -94,7 +96,16 @@ func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawCon
conn.Close()
return nil, nil, ctx.Err()
}
return internal.WrapSyscallConn(rawConn, conn), TLSInfo{conn.ConnectionState(), CommonAuthInfo{PrivacyAndIntegrity}}, nil
tlsInfo := TLSInfo{
State: conn.ConnectionState(),
CommonAuthInfo: CommonAuthInfo{
SecurityLevel: PrivacyAndIntegrity,
},
}
if err := tlsInfo.ParseSpiffeID(); err != nil {
return nil, nil, err
}
return internal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
}

func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) {
Expand All @@ -103,7 +114,16 @@ func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error)
conn.Close()
return nil, nil, err
}
return internal.WrapSyscallConn(rawConn, conn), TLSInfo{conn.ConnectionState(), CommonAuthInfo{PrivacyAndIntegrity}}, nil
tlsInfo := TLSInfo{
State: conn.ConnectionState(),
CommonAuthInfo: CommonAuthInfo{
SecurityLevel: PrivacyAndIntegrity,
},
}
if err := tlsInfo.ParseSpiffeID(); err != nil {
return nil, nil, err
}
return internal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
}

func (c *tlsCreds) Clone() TransportCredentials {
Expand Down