mirror of https://github.com/grpc/grpc-go.git
credentials: check and expose SPIFFE ID (#3626)
* credentials: check and expose SPIFFE ID
This commit is contained in:
parent
9fcde86ebe
commit
dd8658f921
|
|
@ -25,8 +25,10 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"google.golang.org/grpc/credentials/internal"
|
||||
credinternal "google.golang.org/grpc/internal/credentials"
|
||||
)
|
||||
|
||||
// TLSInfo contains the auth information for a TLS authenticated connection.
|
||||
|
|
@ -34,6 +36,8 @@ import (
|
|||
type TLSInfo struct {
|
||||
State tls.ConnectionState
|
||||
CommonAuthInfo
|
||||
// This API is experimental.
|
||||
SPIFFEID *url.URL
|
||||
}
|
||||
|
||||
// AuthType returns the type of TLSInfo as a string.
|
||||
|
|
@ -94,7 +98,17 @@ 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,
|
||||
},
|
||||
}
|
||||
id := credinternal.SPIFFEIDFromState(conn.ConnectionState())
|
||||
if id != nil {
|
||||
tlsInfo.SPIFFEID = id
|
||||
}
|
||||
return internal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
|
||||
}
|
||||
|
||||
func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) {
|
||||
|
|
@ -103,7 +117,17 @@ 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,
|
||||
},
|
||||
}
|
||||
id := credinternal.SPIFFEIDFromState(conn.ConnectionState())
|
||||
if id != nil {
|
||||
tlsInfo.SPIFFEID = id
|
||||
}
|
||||
return internal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
|
||||
}
|
||||
|
||||
func (c *tlsCreds) Clone() TransportCredentials {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
// +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 defines APIs for parsing SPIFFE ID.
|
||||
//
|
||||
// All APIs in this package are experimental.
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/url"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// SPIFFEIDFromState parses the SPIFFE ID from State. If the SPIFFE ID format
|
||||
// is invalid, return nil with warning.
|
||||
func SPIFFEIDFromState(state tls.ConnectionState) *url.URL {
|
||||
if len(state.PeerCertificates) == 0 || len(state.PeerCertificates[0].URIs) == 0 {
|
||||
return nil
|
||||
}
|
||||
var spiffeID *url.URL
|
||||
for _, uri := range 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.String()) > 2048 {
|
||||
grpclog.Warning("invalid SPIFFE ID: total ID length larger than 2048 bytes")
|
||||
return nil
|
||||
}
|
||||
if len(uri.Host) == 0 || len(uri.RawPath) == 0 || len(uri.Path) == 0 {
|
||||
grpclog.Warning("invalid SPIFFE ID: domain or workload ID is empty")
|
||||
return nil
|
||||
}
|
||||
if len(uri.Host) > 255 {
|
||||
grpclog.Warning("invalid SPIFFE ID: domain length larger than 255 characters")
|
||||
return nil
|
||||
}
|
||||
// A valid SPIFFE certificate can only have exactly one URI SAN field.
|
||||
if len(state.PeerCertificates[0].URIs) > 1 {
|
||||
grpclog.Warning("invalid SPIFFE ID: multiple URI SANs")
|
||||
return nil
|
||||
}
|
||||
spiffeID = uri
|
||||
}
|
||||
return spiffeID
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
// +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"
|
||||
|
||||
"google.golang.org/grpc/internal/grpctest"
|
||||
)
|
||||
|
||||
type s struct {
|
||||
grpctest.Tester
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
grpctest.RunSubTests(t, s{})
|
||||
}
|
||||
|
||||
func (s) TestSPIFFEIDFromState(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
urls []*url.URL
|
||||
// If we expect a SPIFFE ID to be returned.
|
||||
expectID bool
|
||||
}{
|
||||
{
|
||||
name: "empty URIs",
|
||||
urls: []*url.URL{},
|
||||
expectID: false,
|
||||
},
|
||||
{
|
||||
name: "good SPIFFE ID",
|
||||
urls: []*url.URL{
|
||||
{
|
||||
Scheme: "spiffe",
|
||||
Host: "foo.bar.com",
|
||||
Path: "workload/wl1",
|
||||
RawPath: "workload/wl1",
|
||||
},
|
||||
},
|
||||
expectID: true,
|
||||
},
|
||||
{
|
||||
name: "invalid host",
|
||||
urls: []*url.URL{
|
||||
{
|
||||
Scheme: "spiffe",
|
||||
Host: "",
|
||||
Path: "workload/wl1",
|
||||
RawPath: "workload/wl1",
|
||||
},
|
||||
},
|
||||
expectID: false,
|
||||
},
|
||||
{
|
||||
name: "invalid path",
|
||||
urls: []*url.URL{
|
||||
{
|
||||
Scheme: "spiffe",
|
||||
Host: "foo.bar.com",
|
||||
Path: "",
|
||||
RawPath: "",
|
||||
},
|
||||
},
|
||||
expectID: false,
|
||||
},
|
||||
{
|
||||
name: "large path",
|
||||
urls: []*url.URL{
|
||||
{
|
||||
Scheme: "spiffe",
|
||||
Host: "foo.bar.com",
|
||||
Path: string(make([]byte, 2050)),
|
||||
RawPath: string(make([]byte, 2050)),
|
||||
},
|
||||
},
|
||||
expectID: false,
|
||||
},
|
||||
{
|
||||
name: "large host",
|
||||
urls: []*url.URL{
|
||||
{
|
||||
Scheme: "spiffe",
|
||||
Host: string(make([]byte, 256)),
|
||||
Path: "workload/wl1",
|
||||
RawPath: "workload/wl1",
|
||||
},
|
||||
},
|
||||
expectID: false,
|
||||
},
|
||||
{
|
||||
name: "multiple URI SANs",
|
||||
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",
|
||||
},
|
||||
{
|
||||
Scheme: "https",
|
||||
Host: "foo.bar.com",
|
||||
Path: "workload/wl1",
|
||||
RawPath: "workload/wl1",
|
||||
},
|
||||
},
|
||||
expectID: false,
|
||||
},
|
||||
{
|
||||
name: "multiple URI SANs without SPIFFE ID",
|
||||
urls: []*url.URL{
|
||||
{
|
||||
Scheme: "https",
|
||||
Host: "foo.bar.com",
|
||||
Path: "workload/wl1",
|
||||
RawPath: "workload/wl1",
|
||||
},
|
||||
{
|
||||
Scheme: "ssh",
|
||||
Host: "foo.bar.com",
|
||||
Path: "workload/wl1",
|
||||
RawPath: "workload/wl1",
|
||||
},
|
||||
},
|
||||
expectID: false,
|
||||
},
|
||||
{
|
||||
name: "multiple URI SANs with one 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",
|
||||
},
|
||||
},
|
||||
expectID: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := tls.ConnectionState{PeerCertificates: []*x509.Certificate{{URIs: tt.urls}}}
|
||||
id := SPIFFEIDFromState(state)
|
||||
if got, want := id != nil, tt.expectID; got != want {
|
||||
t.Errorf("want expectID = %v, but SPIFFE ID is %v", want, id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// +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"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
//TODO(ZhenLian): delete this file when we remove Go 1.9 tests.
|
||||
func SPIFFEIDFromState(state tls.ConnectionState) *url.URL {
|
||||
return nil
|
||||
}
|
||||
Loading…
Reference in New Issue