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

[Not Ready For Review]add SPIFFE ID in the verification func parameters #3930

Closed
Closed
Show file tree
Hide file tree
Changes from all 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
37 changes: 36 additions & 1 deletion internal/credentials/spiffe.go
Expand Up @@ -25,6 +25,7 @@ package credentials

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

"google.golang.org/grpc/grpclog"
Expand All @@ -48,7 +49,7 @@ func SPIFFEIDFromState(state tls.ConnectionState) *url.URL {
logger.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 {
if len(uri.Host) == 0 || len(uri.Path) == 0 {
logger.Warning("invalid SPIFFE ID: domain or workload ID is empty")
return nil
}
Expand All @@ -65,3 +66,37 @@ func SPIFFEIDFromState(state tls.ConnectionState) *url.URL {
}
return spiffeID
}

// SPIFFEIDFromState parses the SPIFFE ID from x509.Certificate. If the SPIFFE
// ID format is invalid, return nil with warning.
func SPIFFEIDFromCert(cert *x509.Certificate) *url.URL {
if cert == nil {
return nil
}
var spiffeID *url.URL
for _, uri := range cert.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 {
logger.Warning("invalid SPIFFE ID: total ID length larger than 2048 bytes")
return nil
}
if len(uri.Host) == 0 || len(uri.Path) == 0 {
logger.Warning("invalid SPIFFE ID: domain or workload ID is empty")
return nil
}
if len(uri.Host) > 255 {
logger.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(cert.URIs) > 1 {
logger.Warning("invalid SPIFFE ID: multiple URI SANs")
return nil
}
spiffeID = uri
}
return spiffeID
}
8 changes: 7 additions & 1 deletion security/advancedtls/advancedtls.go
Expand Up @@ -27,6 +27,7 @@ import (
"crypto/x509"
"fmt"
"net"
"net/url"
"reflect"
"syscall"
"time"
Expand All @@ -53,6 +54,10 @@ type VerificationFuncParams struct {
// certificate(s) and that verification passed. This field would be nil if
// either user chose not to verify or the verification failed.
Leaf *x509.Certificate
// The connection information stored during the verification.
RawConn net.Conn
// The SPIFFE ID stored in |State|.
SPIFFEID *url.URL
}

// VerificationResults contains the information about results of
Expand Down Expand Up @@ -502,13 +507,14 @@ func buildVerifyFunc(c *advancedTLSCreds,
}
leafCert = certs[0]
}
// Perform custom verification check if specified.
if c.verifyFunc != nil {
_, err := c.verifyFunc(&VerificationFuncParams{
ServerName: serverName,
RawCerts: rawCerts,
VerifiedChains: chains,
Leaf: leafCert,
RawConn: rawConn,
SPIFFEID: credinternal.SPIFFEIDFromCert(leafCert),
})
return err
}
Expand Down
74 changes: 69 additions & 5 deletions security/advancedtls/advancedtls_integration_test.go
Expand Up @@ -80,11 +80,19 @@ type certStore struct {
serverPeer1 tls.Certificate
// serverPeer2 is the certificate sent by server to prove its identity.
// It is trusted by clientTrust2.
serverPeer2 tls.Certificate
clientTrust1 *x509.CertPool
clientTrust2 *x509.CertPool
serverTrust1 *x509.CertPool
serverTrust2 *x509.CertPool
serverPeer2 tls.Certificate
// clientPeerSPIFFE1 is the certificate with the SPIFFE ID
// "spiffe://foo.bar.com/client/workload/1" sent by client to prove identity.
// It is trusted by serverTrust1.
clientPeerSPIFFE1 tls.Certificate
// serverPeerSPIFFE1 is the certificate with the SPIFFE ID
// "spiffe://foo.bar.com/client/workload/1" sent by client to prove identity.
// It is trusted by serverTrust1.
serverPeerSPIFFE1 tls.Certificate
clientTrust1 *x509.CertPool
clientTrust2 *x509.CertPool
serverTrust1 *x509.CertPool
serverTrust2 *x509.CertPool
}

// loadCerts function is used to load test certificates at the beginning of
Expand All @@ -111,6 +119,16 @@ func (cs *certStore) loadCerts() error {
if err != nil {
return err
}
cs.clientPeerSPIFFE1, err = tls.LoadX509KeyPair(testdata.Path("client_cert_spiffe_1.pem"),
testdata.Path("client_key_spiffe_1.pem"))
if err != nil {
return err
}
cs.serverPeerSPIFFE1, err = tls.LoadX509KeyPair(testdata.Path("server_cert_spiffe_1.pem"),
testdata.Path("server_key_spiffe_1.pem"))
if err != nil {
return err
}
cs.clientTrust1, err = readTrustCert(testdata.Path("client_trust_cert_1.pem"))
if err != nil {
return err
Expand Down Expand Up @@ -384,6 +402,52 @@ func (s) TestEnd2End(t *testing.T) {
},
serverVType: CertVerification,
},
// Test if the SPIFFE ID is plumbed to VerificationFuncParams while
// performing the custom authorization.
// At initialization(stage = 0), client will be initialized with cert
// clientPeerSPIFFE1 and clientTrust1, server with serverPeerSPIFFE1 and
// serverTrust1.
// The mutual authentication works at the beginning, since clientPeerSPIFFE1
// trusted by serverTrust1, serverPeerSPIFFE1 trusted by clientTrust1.
// Besides, the client custom verification check allows the SPIFFE
// ID shown on serverPeerSPIFFE1, and server verification allows SPIFFE ID
// on clientPeerSPIFFE1.
// At stage 1, server disallows the the connections by setting custom
// verification check. The following calls should fail. Previous
// connections should not be affected.
// At stage 2, server allows the SPIFFE ID shown on the clientPeerSPIFFE1.
// Authentications should go back to normal.
{
desc: "TestSPIFFEIDIsPlumbed",
clientCert: []tls.Certificate{cs.clientPeerSPIFFE1},
clientRoot: cs.clientTrust1,
clientVerifyFunc: func(params *VerificationFuncParams) (*VerificationResults, error) {
if params.SPIFFEID == nil || params.SPIFFEID.Scheme != "spiffe" ||
params.SPIFFEID.Host != "foo.bar.com" || params.SPIFFEID.Path != "/server/workload/1" {
return nil, fmt.Errorf("custom authz check fails")
}
return &VerificationResults{}, nil

},
clientVType: CertVerification,
serverCert: []tls.Certificate{cs.serverPeerSPIFFE1},
serverRoot: cs.serverTrust1,
serverVerifyFunc: func(params *VerificationFuncParams) (*VerificationResults, error) {
switch stage.read() {
case 0, 2:
if params.SPIFFEID == nil || params.SPIFFEID.Scheme != "spiffe" ||
params.SPIFFEID.Host != "foo.bar.com" || params.SPIFFEID.Path != "/client/workload/1" {
return nil, fmt.Errorf("custom authz check fails")
}
return &VerificationResults{}, nil
case 1:
return nil, fmt.Errorf("custom authz check fails")
default:
return nil, fmt.Errorf("custom authz check fails")
}
},
serverVType: CertVerification,
},
} {
test := test
t.Run(test.desc, func(t *testing.T) {
Expand Down
10 changes: 5 additions & 5 deletions security/advancedtls/testdata/README.md
Expand Up @@ -14,16 +14,16 @@ commands we run:
$ openssl req -x509 -newkey rsa:4096 -keyout ca_key.pem -out ca_cert.pem -nodes -days $DURATION_DAYS
```

2. Generate a CSR `csr.pem` using `subject_key.pem`:

2. Generate a private key `subject_key.pem` for the subject:
```
$ openssl req -new -key subject_key.pem -out csr.pem
$ openssl genrsa -out subject_key.pem 4096
```

3. Generate a private key `subject_key.pem` for the subject:
3. Generate a CSR `csr.pem` using `subject_key.pem`:

```
$ openssl genrsa -out subject_key.pem 4096
$ openssl req -new -key subject_key.pem -out csr.pem
```

4. Use `ca_key.pem` and `ca_cert.pem` to sign `csr.pem`, and get a certificate, `subject_cert.pem`, for the subject:
Expand Down
125 changes: 125 additions & 0 deletions security/advancedtls/testdata/client_cert_spiffe_1.pem
@@ -0,0 +1,125 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 2 (0x2)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=VA, O=Internet Widgits Pty Ltd, CN=foo.bar.hoo.ca.com
Validity
Not Before: Oct 5 20:11:06 2020 GMT
Not After : May 27 20:11:06 2045 GMT
Subject: C=US, ST=CA, L=Sunnyvale, O=XX, OU=XXX, CN=foo.bar.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (4096 bit)
Modulus:
00:bb:ee:3e:6d:79:0b:3c:33:b5:cb:ef:56:f7:a4:
15:42:d4:94:05:c8:59:37:d1:d8:7c:44:40:dd:cd:
9f:64:e1:c6:6d:c8:8d:a1:3a:85:df:34:3e:5b:23:
1d:16:d1:c3:67:94:c9:35:21:cd:8f:80:46:a6:a2:
78:c1:f6:c7:45:c3:f2:0a:9d:ad:2c:64:72:f6:5b:
7b:79:dd:33:d7:5c:c0:9f:97:9e:45:8e:c9:30:33:
6d:0e:24:b4:62:85:ab:ce:43:b3:25:98:31:39:f4:
d2:fe:ce:10:bf:53:e7:92:a5:b7:8d:a4:5b:e3:37:
ec:c5:06:8e:94:ee:10:d0:6e:dc:6b:90:08:c9:06:
6d:01:79:ba:94:df:0b:20:3d:ae:a2:cc:7f:30:53:
07:cb:8a:ca:77:80:d5:94:12:1a:2a:36:b3:f3:5e:
d7:51:02:00:2a:1e:f1:bd:89:0b:37:4f:dc:58:af:
0f:74:14:01:21:20:8e:69:b6:e9:0d:da:52:21:ea:
71:2b:64:69:a9:c9:fb:f8:7e:55:d7:8f:a0:c7:98:
cb:a7:e5:16:44:2f:39:d2:9b:ad:e7:4a:de:f8:3b:
1b:65:a0:c9:50:ce:0a:6c:32:29:38:78:4a:f5:11:
28:53:a9:c6:c1:85:2b:10:09:79:5a:54:9c:9f:1b:
b7:f7:16:66:3e:be:cb:2f:3b:e6:27:3e:d2:d1:c6:
f5:2b:d7:46:e7:22:6a:42:17:0c:cc:46:1d:12:e0:
81:f8:6d:cc:58:2c:1c:0a:21:8d:0d:af:d5:a9:31:
35:1f:99:14:3b:2c:32:c2:b2:8b:9d:39:cd:ee:40:
22:7b:4e:a8:4a:f1:15:b2:51:4d:82:3c:0e:35:e7:
d1:79:d7:b9:0e:f9:9d:ba:d0:bf:17:fb:e2:df:55:
ac:f8:8e:e5:dc:f2:dc:50:1b:cc:a5:a2:2a:84:8a:
75:16:a8:95:b2:6c:bc:21:d9:89:6f:c9:83:6d:f8:
0f:26:26:de:f1:1a:ad:4e:8b:54:dd:43:e9:ef:04:
92:71:75:bb:7f:3c:82:e9:1f:4e:c2:04:73:fd:c1:
73:df:0a:ae:2d:c5:9a:5d:08:cf:28:ea:2e:1f:ee:
d7:8e:21:01:55:a1:8e:90:79:43:86:ac:6f:ec:58:
ca:e5:04:b2:a0:75:35:89:0e:09:d0:7c:03:b3:25:
e1:ff:42:ec:8e:b7:43:1f:9e:b3:71:51:a7:b5:42:
c8:27:f1:6d:9f:ae:4e:2c:d2:8b:0c:f9:90:7a:5c:
93:ed:4a:8d:14:06:06:36:f0:a5:1b:6c:8e:85:46:
9b:14:3f:b7:23:69:a1:d2:48:32:27:67:20:49:c8:
ce:a6:c1
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
FD:25:82:CC:12:B6:0D:19:F6:94:A5:00:A7:E2:20:A0:C8:1B:05:FA
X509v3 Authority Key Identifier:
keyid:B4:19:08:1C:FC:10:23:C5:30:86:22:BC:CB:B1:5F:AD:EA:7A:5D:F1

X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Key Encipherment
X509v3 Subject Alternative Name:
URI:spiffe://foo.bar.com/client/workload/1
Signature Algorithm: sha256WithRSAEncryption
4f:21:c8:46:e0:f7:04:a0:98:a5:02:b9:c1:6b:9b:29:26:26:
9e:29:c1:42:8f:5c:71:4a:cc:5c:ff:d0:0c:02:6e:13:9f:89:
8e:24:f9:92:6c:4f:97:25:fd:d3:03:6f:aa:8d:69:fa:b0:df:
84:da:f6:47:af:3f:f3:17:5a:96:08:20:2d:df:78:a7:99:4f:
5a:9b:5c:06:7c:e9:89:f0:04:47:75:b0:da:20:d2:3f:88:9e:
a2:79:59:23:54:8e:13:05:be:26:d4:b6:e2:70:78:3f:41:56:
53:dc:ff:b7:22:04:12:38:75:c2:ec:da:b6:b3:3e:d0:1f:bc:
80:78:b8:b4:4d:de:b8:34:b3:a9:30:9b:1d:af:2a:53:df:2c:
7d:fe:c8:4c:93:a6:ad:3f:1b:73:31:56:4d:3b:3c:4f:b3:be:
40:78:08:b7:65:fd:08:37:62:6e:bb:bc:22:7e:dd:d5:3a:ad:
07:e1:31:24:c0:20:bf:0c:cc:85:60:b5:2c:34:e1:5f:f4:26:
f4:b2:8d:76:ae:de:0c:ca:17:2d:cb:ec:1d:66:b4:9b:9f:0b:
2e:1a:64:ea:fa:52:4b:bb:bd:33:73:8d:bc:9f:8b:a6:24:ea:
12:62:6b:60:23:f3:cb:e8:b9:f2:b3:ca:d2:0b:ea:76:3f:4c:
cf:cc:eb:4f:0e:5a:a6:66:96:7f:46:6f:15:e4:28:a3:dd:ea:
93:24:c3:b3:99:3f:bc:b7:71:dc:24:28:5c:4c:21:b3:ca:6a:
67:b0:a8:f4:00:c5:75:5e:6e:65:07:95:39:c2:2c:a6:bd:dc:
78:61:19:29:9d:dd:1e:54:5e:3e:6f:fb:79:37:ff:06:8c:f5:
50:cc:f2:4a:3b:03:e3:6e:7c:65:ad:c3:80:0e:79:d3:28:fd:
bf:4b:6a:b6:56:a6:2f:d0:a0:19:96:85:7e:69:46:b1:8c:4c:
c3:eb:43:86:15:06:e9:9e:90:d4:3d:85:28:3a:cf:6f:c4:e7:
d7:7d:61:29:ae:7a:99:2a:c7:26:48:84:01:43:98:97:b4:69:
5a:47:eb:f5:33:bb:f8:a5:8d:6f:56:9e:01:58:f7:0d:8c:1b:
e3:8b:a9:e2:58:20:bf:27:f2:7b:7d:e4:a5:35:61:8e:12:57:
4e:b7:31:09:73:13:0b:f9:b7:26:f9:d5:bb:c5:5d:f3:e4:d5:
b6:17:14:e5:99:39:36:e8:a9:a8:88:ca:da:79:45:cd:c8:66:
5e:ad:78:b2:52:52:fb:06:b7:a3:4d:50:16:7e:af:d8:0e:6f:
8f:48:40:57:97:6e:c3:19:cf:fb:bc:e4:3f:33:61:80:af:15:
1b:d7:b3:20:3f:ed:ff:7d
-----BEGIN CERTIFICATE-----
MIIFwzCCA6ugAwIBAgIBAjANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzEL
MAkGA1UECAwCVkExITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEb
MBkGA1UEAwwSZm9vLmJhci5ob28uY2EuY29tMB4XDTIwMTAwNTIwMTEwNloXDTQ1
MDUyNzIwMTEwNlowXzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRIwEAYDVQQH
DAlTdW5ueXZhbGUxCzAJBgNVBAoMAlhYMQwwCgYDVQQLDANYWFgxFDASBgNVBAMM
C2Zvby5iYXIuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAu+4+
bXkLPDO1y+9W96QVQtSUBchZN9HYfERA3c2fZOHGbciNoTqF3zQ+WyMdFtHDZ5TJ
NSHNj4BGpqJ4wfbHRcPyCp2tLGRy9lt7ed0z11zAn5eeRY7JMDNtDiS0YoWrzkOz
JZgxOfTS/s4Qv1PnkqW3jaRb4zfsxQaOlO4Q0G7ca5AIyQZtAXm6lN8LID2uosx/
MFMHy4rKd4DVlBIaKjaz817XUQIAKh7xvYkLN0/cWK8PdBQBISCOabbpDdpSIepx
K2Rpqcn7+H5V14+gx5jLp+UWRC850put50re+DsbZaDJUM4KbDIpOHhK9REoU6nG
wYUrEAl5WlScnxu39xZmPr7LLzvmJz7S0cb1K9dG5yJqQhcMzEYdEuCB+G3MWCwc
CiGNDa/VqTE1H5kUOywywrKLnTnN7kAie06oSvEVslFNgjwONefRede5DvmdutC/
F/vi31Ws+I7l3PLcUBvMpaIqhIp1FqiVsmy8IdmJb8mDbfgPJibe8RqtTotU3UPp
7wSScXW7fzyC6R9OwgRz/cFz3wquLcWaXQjPKOouH+7XjiEBVaGOkHlDhqxv7FjK
5QSyoHU1iQ4J0HwDsyXh/0LsjrdDH56zcVGntULIJ/Ftn65OLNKLDPmQelyT7UqN
FAYGNvClG2yOhUabFD+3I2mh0kgyJ2cgScjOpsECAwEAAaOBjjCBizAdBgNVHQ4E
FgQU/SWCzBK2DRn2lKUAp+IgoMgbBfowHwYDVR0jBBgwFoAUtBkIHPwQI8UwhiK8
y7Ffrep6XfEwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwMQYDVR0RBCowKIYmc3Bp
ZmZlOi8vZm9vLmJhci5jb20vY2xpZW50L3dvcmtsb2FkLzEwDQYJKoZIhvcNAQEL
BQADggIBAE8hyEbg9wSgmKUCucFrmykmJp4pwUKPXHFKzFz/0AwCbhOfiY4k+ZJs
T5cl/dMDb6qNafqw34Ta9kevP/MXWpYIIC3feKeZT1qbXAZ86YnwBEd1sNog0j+I
nqJ5WSNUjhMFvibUtuJweD9BVlPc/7ciBBI4dcLs2razPtAfvIB4uLRN3rg0s6kw
mx2vKlPfLH3+yEyTpq0/G3MxVk07PE+zvkB4CLdl/Qg3Ym67vCJ+3dU6rQfhMSTA
IL8MzIVgtSw04V/0JvSyjXau3gzKFy3L7B1mtJufCy4aZOr6Uku7vTNzjbyfi6Yk
6hJia2Aj88voufKzytIL6nY/TM/M608OWqZmln9GbxXkKKPd6pMkw7OZP7y3cdwk
KFxMIbPKamewqPQAxXVebmUHlTnCLKa93HhhGSmd3R5UXj5v+3k3/waM9VDM8ko7
A+NufGWtw4AOedMo/b9LarZWpi/QoBmWhX5pRrGMTMPrQ4YVBumekNQ9hSg6z2/E
59d9YSmuepkqxyZIhAFDmJe0aVpH6/Uzu/iljW9WngFY9w2MG+OLqeJYIL8n8nt9
5KU1YY4SV063MQlzEwv5tyb51bvFXfPk1bYXFOWZOTboqaiIytp5Rc3IZl6teLJS
UvsGt6NNUBZ+r9gOb49IQFeXbsMZz/u85D8zYYCvFRvXsyA/7f99
-----END CERTIFICATE-----
51 changes: 51 additions & 0 deletions security/advancedtls/testdata/client_key_spiffe_1.pem
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----