Skip to content

Commit 4626aff

Browse files
committed
Remove credinternal dep
1 parent 24607bc commit 4626aff

File tree

5 files changed

+456
-8
lines changed

5 files changed

+456
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
*
3+
* Copyright 2020 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
// Package credentials defines APIs for parsing SPIFFE ID.
20+
//
21+
// All APIs in this package are experimental.
22+
package internal
23+
24+
import (
25+
"crypto/tls"
26+
"crypto/x509"
27+
"net/url"
28+
29+
"google.golang.org/grpc/grpclog"
30+
)
31+
32+
var logger = grpclog.Component("credentials")
33+
34+
// SPIFFEIDFromState parses the SPIFFE ID from State. If the SPIFFE ID format
35+
// is invalid, return nil with warning.
36+
func SPIFFEIDFromState(state tls.ConnectionState) *url.URL {
37+
if len(state.PeerCertificates) == 0 || len(state.PeerCertificates[0].URIs) == 0 {
38+
return nil
39+
}
40+
return SPIFFEIDFromCert(state.PeerCertificates[0])
41+
}
42+
43+
// SPIFFEIDFromCert parses the SPIFFE ID from x509.Certificate. If the SPIFFE
44+
// ID format is invalid, return nil with warning.
45+
func SPIFFEIDFromCert(cert *x509.Certificate) *url.URL {
46+
if cert == nil || cert.URIs == nil {
47+
return nil
48+
}
49+
var spiffeID *url.URL
50+
for _, uri := range cert.URIs {
51+
if uri == nil || uri.Scheme != "spiffe" || uri.Opaque != "" || (uri.User != nil && uri.User.Username() != "") {
52+
continue
53+
}
54+
// From this point, we assume the uri is intended for a SPIFFE ID.
55+
if len(uri.String()) > 2048 {
56+
logger.Warning("invalid SPIFFE ID: total ID length larger than 2048 bytes")
57+
return nil
58+
}
59+
if len(uri.Host) == 0 || len(uri.Path) == 0 {
60+
logger.Warning("invalid SPIFFE ID: domain or workload ID is empty")
61+
return nil
62+
}
63+
if len(uri.Host) > 255 {
64+
logger.Warning("invalid SPIFFE ID: domain length larger than 255 characters")
65+
return nil
66+
}
67+
// A valid SPIFFE certificate can only have exactly one URI SAN field.
68+
if len(cert.URIs) > 1 {
69+
logger.Warning("invalid SPIFFE ID: multiple URI SANs")
70+
return nil
71+
}
72+
spiffeID = uri
73+
}
74+
return spiffeID
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*
2+
*
3+
* Copyright 2020 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package internal
20+
21+
import (
22+
"crypto/tls"
23+
"crypto/x509"
24+
"encoding/pem"
25+
"net/url"
26+
"os"
27+
"testing"
28+
29+
"google.golang.org/grpc/internal/grpctest"
30+
"google.golang.org/grpc/testdata"
31+
)
32+
33+
const wantURI = "spiffe://foo.bar.com/client/workload/1"
34+
35+
type s struct {
36+
grpctest.Tester
37+
}
38+
39+
func Test(t *testing.T) {
40+
grpctest.RunSubTests(t, s{})
41+
}
42+
43+
func (s) TestSPIFFEIDFromState(t *testing.T) {
44+
tests := []struct {
45+
name string
46+
urls []*url.URL
47+
// If we expect a SPIFFE ID to be returned.
48+
wantID bool
49+
}{
50+
{
51+
name: "empty URIs",
52+
urls: []*url.URL{},
53+
wantID: false,
54+
},
55+
{
56+
name: "good SPIFFE ID",
57+
urls: []*url.URL{
58+
{
59+
Scheme: "spiffe",
60+
Host: "foo.bar.com",
61+
Path: "workload/wl1",
62+
RawPath: "workload/wl1",
63+
},
64+
},
65+
wantID: true,
66+
},
67+
{
68+
name: "invalid host",
69+
urls: []*url.URL{
70+
{
71+
Scheme: "spiffe",
72+
Host: "",
73+
Path: "workload/wl1",
74+
RawPath: "workload/wl1",
75+
},
76+
},
77+
wantID: false,
78+
},
79+
{
80+
name: "invalid path",
81+
urls: []*url.URL{
82+
{
83+
Scheme: "spiffe",
84+
Host: "foo.bar.com",
85+
Path: "",
86+
RawPath: "",
87+
},
88+
},
89+
wantID: false,
90+
},
91+
{
92+
name: "large path",
93+
urls: []*url.URL{
94+
{
95+
Scheme: "spiffe",
96+
Host: "foo.bar.com",
97+
Path: string(make([]byte, 2050)),
98+
RawPath: string(make([]byte, 2050)),
99+
},
100+
},
101+
wantID: false,
102+
},
103+
{
104+
name: "large host",
105+
urls: []*url.URL{
106+
{
107+
Scheme: "spiffe",
108+
Host: string(make([]byte, 256)),
109+
Path: "workload/wl1",
110+
RawPath: "workload/wl1",
111+
},
112+
},
113+
wantID: false,
114+
},
115+
{
116+
name: "multiple URI SANs",
117+
urls: []*url.URL{
118+
{
119+
Scheme: "spiffe",
120+
Host: "foo.bar.com",
121+
Path: "workload/wl1",
122+
RawPath: "workload/wl1",
123+
},
124+
{
125+
Scheme: "spiffe",
126+
Host: "bar.baz.com",
127+
Path: "workload/wl2",
128+
RawPath: "workload/wl2",
129+
},
130+
{
131+
Scheme: "https",
132+
Host: "foo.bar.com",
133+
Path: "workload/wl1",
134+
RawPath: "workload/wl1",
135+
},
136+
},
137+
wantID: false,
138+
},
139+
{
140+
name: "multiple URI SANs without SPIFFE ID",
141+
urls: []*url.URL{
142+
{
143+
Scheme: "https",
144+
Host: "foo.bar.com",
145+
Path: "workload/wl1",
146+
RawPath: "workload/wl1",
147+
},
148+
{
149+
Scheme: "ssh",
150+
Host: "foo.bar.com",
151+
Path: "workload/wl1",
152+
RawPath: "workload/wl1",
153+
},
154+
},
155+
wantID: false,
156+
},
157+
{
158+
name: "multiple URI SANs with one SPIFFE ID",
159+
urls: []*url.URL{
160+
{
161+
Scheme: "spiffe",
162+
Host: "foo.bar.com",
163+
Path: "workload/wl1",
164+
RawPath: "workload/wl1",
165+
},
166+
{
167+
Scheme: "https",
168+
Host: "foo.bar.com",
169+
Path: "workload/wl1",
170+
RawPath: "workload/wl1",
171+
},
172+
},
173+
wantID: false,
174+
},
175+
}
176+
for _, tt := range tests {
177+
t.Run(tt.name, func(t *testing.T) {
178+
state := tls.ConnectionState{PeerCertificates: []*x509.Certificate{{URIs: tt.urls}}}
179+
id := SPIFFEIDFromState(state)
180+
if got, want := id != nil, tt.wantID; got != want {
181+
t.Errorf("want wantID = %v, but SPIFFE ID is %v", want, id)
182+
}
183+
})
184+
}
185+
}
186+
187+
func (s) TestSPIFFEIDFromCert(t *testing.T) {
188+
tests := []struct {
189+
name string
190+
dataPath string
191+
// If we expect a SPIFFE ID to be returned.
192+
wantID bool
193+
}{
194+
{
195+
name: "good certificate with SPIFFE ID",
196+
dataPath: "x509/spiffe_cert.pem",
197+
wantID: true,
198+
},
199+
{
200+
name: "bad certificate with SPIFFE ID and another URI",
201+
dataPath: "x509/multiple_uri_cert.pem",
202+
wantID: false,
203+
},
204+
{
205+
name: "certificate without SPIFFE ID",
206+
dataPath: "x509/client1_cert.pem",
207+
wantID: false,
208+
},
209+
}
210+
for _, tt := range tests {
211+
t.Run(tt.name, func(t *testing.T) {
212+
data, err := os.ReadFile(testdata.Path(tt.dataPath))
213+
if err != nil {
214+
t.Fatalf("os.ReadFile(%s) failed: %v", testdata.Path(tt.dataPath), err)
215+
}
216+
block, _ := pem.Decode(data)
217+
if block == nil {
218+
t.Fatalf("Failed to parse the certificate: byte block is nil")
219+
}
220+
cert, err := x509.ParseCertificate(block.Bytes)
221+
if err != nil {
222+
t.Fatalf("x509.ParseCertificate(%b) failed: %v", block.Bytes, err)
223+
}
224+
uri := SPIFFEIDFromCert(cert)
225+
if (uri != nil) != tt.wantID {
226+
t.Fatalf("wantID got and want mismatch, got %t, want %t", uri != nil, tt.wantID)
227+
}
228+
if uri != nil && uri.String() != wantURI {
229+
t.Fatalf("SPIFFE ID not expected, got %s, want %s", uri.String(), wantURI)
230+
}
231+
})
232+
}
233+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
*
3+
* Copyright 2018 gRPC authors.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package internal
20+
21+
import (
22+
"net"
23+
"syscall"
24+
)
25+
26+
type sysConn = syscall.Conn
27+
28+
// syscallConn keeps reference of rawConn to support syscall.Conn for channelz.
29+
// SyscallConn() (the method in interface syscall.Conn) is explicitly
30+
// implemented on this type,
31+
//
32+
// Interface syscall.Conn is implemented by most net.Conn implementations (e.g.
33+
// TCPConn, UnixConn), but is not part of net.Conn interface. So wrapper conns
34+
// that embed net.Conn don't implement syscall.Conn. (Side note: tls.Conn
35+
// doesn't embed net.Conn, so even if syscall.Conn is part of net.Conn, it won't
36+
// help here).
37+
type syscallConn struct {
38+
net.Conn
39+
// sysConn is a type alias of syscall.Conn. It's necessary because the name
40+
// `Conn` collides with `net.Conn`.
41+
sysConn
42+
}
43+
44+
// WrapSyscallConn tries to wrap rawConn and newConn into a net.Conn that
45+
// implements syscall.Conn. rawConn will be used to support syscall, and newConn
46+
// will be used for read/write.
47+
//
48+
// This function returns newConn if rawConn doesn't implement syscall.Conn.
49+
func WrapSyscallConn(rawConn, newConn net.Conn) net.Conn {
50+
sysConn, ok := rawConn.(syscall.Conn)
51+
if !ok {
52+
return newConn
53+
}
54+
return &syscallConn{
55+
Conn: newConn,
56+
sysConn: sysConn,
57+
}
58+
}

0 commit comments

Comments
 (0)