Skip to content

Commit 17a9db7

Browse files
authored
feat(auth): adds X509 workload certificate provider (#10233)
See go/x509-workload-auth-library-design and go/guac-cba-x509-federation-golang. Adds support for the new certificate provider type and unit tests, but does not add it to the default certificate provider logic yet.
1 parent 0ceec32 commit 17a9db7

10 files changed

+288
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"cert_configs": {
3+
"workload": {
4+
"cert_path": "testdata/workload_cert.pem",
5+
"key_path": "testdata/workload_key.pem"
6+
}
7+
}
8+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{
2+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"cert_configs": {
3+
"workload": {
4+
"cert_path": "testdata/workload_cert_invalid.txt",
5+
"key_path": "testdata/workload_key.pem"
6+
}
7+
}
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"cert_configs": {
3+
"workload": {
4+
"key_path": "testdata/workload_key.pem"
5+
}
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"cert_configs": {
3+
"workload": {
4+
"cert_path": "testdata/workload_cert.pem"
5+
}
6+
}
7+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDujCCAqICCQD+yrCYuiC8djANBgkqhkiG9w0BAQsFADCBnTELMAkGA1UEBhMC
3+
VVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAPBgNVBAcMCEtpcmtsYW5kMQ8wDQYD
4+
VQQKDAZHb29nbGUxDjAMBgNVBAsMBUNsb3VkMRswGQYDVQQDDBJnb29nbGVhcGlz
5+
dGVzdC5jb20xKDAmBgkqhkiG9w0BCQEWGWdvb2dsZWFwaXN0ZXN0QGdvb2dsZS5j
6+
b20wIBcNMjAxMDIzMjEyNTU1WhgPMjEyMDA5MjkyMTI1NTVaMIGdMQswCQYDVQQG
7+
EwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxDzAN
8+
BgNVBAoMBkdvb2dsZTEOMAwGA1UECwwFQ2xvdWQxGzAZBgNVBAMMEmdvb2dsZWFw
9+
aXN0ZXN0LmNvbTEoMCYGCSqGSIb3DQEJARYZZ29vZ2xlYXBpc3Rlc3RAZ29vZ2xl
10+
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKnzFX97VP4XSQ8l
11+
4/Z08eajnAiGpK+ZQTV9k7Qy2tpo5+iFFiL0JLGP9+GRILuDGQufYlPLDhLLho9V
12+
YXIR9UOhhapmQJqUAUFhvZlBEixLxcfwa2LecNiJ6+8gvJCoRbrPIrz91crY+t59
13+
aY/09vmsCbFDX8d8WWVnww4285dfKwE2IDinqZ1VuT4zYR66f4lL8qj6t5TXeGAW
14+
Nkd6O3yuAVO8RLiXBRRABP5217mq0jNL+kJUormzhuKgvP+oxRsi56XHPGiq7l2e
15+
54PS/cqa4atjqbhZI1xV27y0sVr0/CmBsfeM3TwLbCSjv7r0lCz64xtCJa8R45MA
16+
22or9z8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAnwLY9qBIQ2IYDLNLx16av8C6
17+
9vca8gOzMpYZ4UKHDN+Qk2CidpmFamXWDXqmOLNZYlmEoGY5n8zg8rwYK+vauqwb
18+
o94HzxLmQcQ4kmAI4xJnMqKZAbukRdWw2GCuvdVqG4Osngz4WBIHrAsl4btogdJy
19+
ACU/YUA3K0tLjwe6wUYYF6eu5sb6zJkF4cfLpqECWtF9XG6nkJbo2GomHFuHm+6t
20+
gOj7YiqU/cHCyU4FQF9/2jDLzFHxt2Bb30zi602YjuIZhYp35ktI66XwsE4kFmwo
21+
iHCEG0fXMNN7OMFmNg2YVLhaHxrQNFxbzOQdfKg2gi2qzX4AiCo1tx5LCg6aGw==
22+
-----END CERTIFICATE-----
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is not a cert file.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCp8xV/e1T+F0kP
3+
JeP2dPHmo5wIhqSvmUE1fZO0MtraaOfohRYi9CSxj/fhkSC7gxkLn2JTyw4Sy4aP
4+
VWFyEfVDoYWqZkCalAFBYb2ZQRIsS8XH8Gti3nDYievvILyQqEW6zyK8/dXK2Pre
5+
fWmP9Pb5rAmxQ1/HfFllZ8MONvOXXysBNiA4p6mdVbk+M2Eeun+JS/Ko+reU13hg
6+
FjZHejt8rgFTvES4lwUUQAT+dte5qtIzS/pCVKK5s4bioLz/qMUbIuelxzxoqu5d
7+
nueD0v3KmuGrY6m4WSNcVdu8tLFa9PwpgbH3jN08C2wko7+69JQs+uMbQiWvEeOT
8+
ANtqK/c/AgMBAAECggEAYjeE3hb1yJ7Gb0WzmDR/tI4rV9YQiRcl03cOjJ6zUnQ8
9+
SmnXoD2+kwuj8y1/YD7kk436MnjwWjZbPqzWUylDuGE5sX/EqFEO5K1K+K3dhdII
10+
rIMqXIo3Zz1WJ+2gbG2DVvHsnpKIIuIBIeISxsqIjUQ6mcJZMR2RQISV+roRTxIU
11+
1Ga0xWrExcKL8FSjs8ih0DWU4vHoSYH4DFXB1/ViyLn+DEljnOlo8Q+7DG0uQQnX
12+
ixfYMbXSJcZxFm1iwuZv8SESjqbTsogNny5Wi6H9Vp0JFasAPUjnc+QuD/U1HTDn
13+
PCX3eBNMcxvVJDhu/7nnO7kcU1Cx0gJeN+1bklrAcQKBgQDURl0Ac8N94I82n4Lg
14+
wjGLWj3AMxSEHNcZuomCvoYcLTmJdd2tOnunXhh1jANnx6q8P8aR5fiTthokIUdx
15+
bOmWwFAbP6kMe0WFWQhXjX4mXLRmJ4mWayWCE7hstnDb3/Fr7LuJeg5L3OU4ss3b
16+
j4UvhtuQ9Qh8piVhKwFkQh3tOQKBgQDM9NSkRDVW3Q37lMUdyn8B2FBF78e/9ck+
17+
5bHOs52G2hXJ4tyLYNjBoLXPpMp9VWRTXxUaii+gHSa4DkHTkFwIg34hLgrCX7Gc
18+
a0rldvkpX0xWSANfvO9bvavPgKnLSP8j3mjDiwqJuy3L5TBThIHDvPV9F/akpLne
19+
bdcywa4ANwKBgHlvAzcGAniZJPRXjfRrwxH3/slbr0nggcDLMG0l9uxZhse3MKgv
20+
g5t8PbvI7A3LcEWeqka+a1R84Tl3/DnL11kRDQJ5iYiFYIDnLNmBLQBfGigySAhP
21+
pTZjd6ZhO/DcjGx0EdiUhWcqp8qmpxMKaGOG30ZulntQRKPwiSxEkoApAoGBAJ1o
22+
h4ulawXMfnmyt3T62XJ0TKp5zoKqZSYuSNIEdr5j7goAdvuApNiI8jmISY/arlOt
23+
mcqpSIyC9wKyyHGQ1G4hdxRKhS7lScZlTL9REWlp7HnzksvLklV2JWcXXNBovrMw
24+
lGth9PT00eZfni72fKb1D+FEL0Qh0zJ2T6mGwHkfAoGAMOy8bbyCASCYG9MYzqaP
25+
Lf+AKKNEYUvUGspyJUqu5ERudr5stmei6PrchxFiKjm5Qg7B/M1VnKsCtL9kk8Z9
26+
lHgwU5mOATZvd9k/5oiuRxzXyrWqFoT/mivI2rZE+g5cLTLytCTnyLjHm5B/aTy8
27+
1AmbAh5hvWYs+EMKZAlQ5GM=
28+
-----END PRIVATE KEY-----
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cert
16+
17+
import (
18+
"crypto/tls"
19+
"encoding/json"
20+
"errors"
21+
"io"
22+
"os"
23+
24+
"github.com/googleapis/enterprise-certificate-proxy/client/util"
25+
)
26+
27+
type certConfigs struct {
28+
Workload *workloadSource `json:"workload"`
29+
}
30+
31+
type workloadSource struct {
32+
CertPath string `json:"cert_path"`
33+
KeyPath string `json:"key_path"`
34+
}
35+
36+
type certificateConfig struct {
37+
CertConfigs certConfigs `json:"cert_configs"`
38+
}
39+
40+
// NewWorkloadX509CertProvider creates a certificate source
41+
// that reads a certificate and private key file from the local file system.
42+
// This is intended to be used for workload identity federation.
43+
//
44+
// The configFilePath points to a config file containing relevant parameters
45+
// such as the certificate and key file paths.
46+
// If configFilePath is empty, the client will attempt to load the config from
47+
// a well-known gcloud location.
48+
func NewWorkloadX509CertProvider(configFilePath string) (Provider, error) {
49+
if configFilePath == "" {
50+
envFilePath := util.GetConfigFilePathFromEnv()
51+
if envFilePath != "" {
52+
configFilePath = envFilePath
53+
} else {
54+
configFilePath = util.GetDefaultConfigFilePath()
55+
}
56+
}
57+
58+
certFile, keyFile, err := getCertAndKeyFiles(configFilePath)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
source := &workloadSource{
64+
CertPath: certFile,
65+
KeyPath: keyFile,
66+
}
67+
return source.getClientCertificate, nil
68+
}
69+
70+
// getClientCertificate attempts to load the certificate and key from the files specified in the
71+
// certificate config.
72+
func (s *workloadSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
73+
cert, err := tls.LoadX509KeyPair(s.CertPath, s.KeyPath)
74+
if err != nil {
75+
return nil, err
76+
}
77+
return &cert, nil
78+
}
79+
80+
// getCertAndKeyFiles attempts to read the provided config file and return the certificate and private
81+
// key file paths.
82+
func getCertAndKeyFiles(configFilePath string) (string, string, error) {
83+
jsonFile, err := os.Open(configFilePath)
84+
if err != nil {
85+
if errors.Is(err, os.ErrNotExist) {
86+
return "", "", errSourceUnavailable
87+
}
88+
return "", "", err
89+
}
90+
91+
byteValue, err := io.ReadAll(jsonFile)
92+
if err != nil {
93+
return "", "", err
94+
}
95+
96+
var config certificateConfig
97+
if err := json.Unmarshal(byteValue, &config); err != nil {
98+
return "", "", err
99+
}
100+
101+
if config.CertConfigs.Workload == nil {
102+
return "", "", errors.New("no Workload Identity Federation certificate information found in the certificate configuration file")
103+
}
104+
105+
certFile := config.CertConfigs.Workload.CertPath
106+
keyFile := config.CertConfigs.Workload.KeyPath
107+
108+
if certFile == "" {
109+
return "", "", errors.New("certificate configuration is missing the certificate file location")
110+
}
111+
112+
if keyFile == "" {
113+
return "", "", errors.New("certificate configuration is missing the key file location")
114+
}
115+
116+
return certFile, keyFile, nil
117+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cert
16+
17+
import (
18+
"errors"
19+
"testing"
20+
)
21+
22+
func TestWorkloadCertSource_ConfigMissing(t *testing.T) {
23+
source, err := NewWorkloadX509CertProvider("missing.json")
24+
if got, want := err, errSourceUnavailable; !errors.Is(err, errSourceUnavailable) {
25+
t.Fatalf("got %v, want %v", got, want)
26+
}
27+
if source != nil {
28+
t.Errorf("got %v, want nil source", source)
29+
}
30+
}
31+
32+
func TestWorkloadCertSource_EmptyConfig(t *testing.T) {
33+
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload_empty.json")
34+
if err == nil {
35+
t.Fatal("got nil, want non-nil error")
36+
}
37+
if source != nil {
38+
t.Errorf("got %v, want nil source", source)
39+
}
40+
}
41+
42+
func TestWorkloadCertSource_MissingCert(t *testing.T) {
43+
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload_no_cert.json")
44+
if err == nil {
45+
t.Fatal("got nil, want non-nil error")
46+
}
47+
if source != nil {
48+
t.Errorf("got %v, want nil source", source)
49+
}
50+
}
51+
52+
func TestWorkloadCertSource_MissingKey(t *testing.T) {
53+
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload_no_key.json")
54+
if err == nil {
55+
t.Fatal("got nil, want non-nil error")
56+
}
57+
if source != nil {
58+
t.Errorf("got %v, want nil source", source)
59+
}
60+
}
61+
62+
func TestWorkloadCertSource_GetClientCertificateInvalidCert(t *testing.T) {
63+
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload_invalid_cert.json")
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
_, err = source(nil)
68+
if err == nil {
69+
t.Fatal("got nil, want non-nil error")
70+
}
71+
}
72+
73+
func TestWorkloadCertSource_GetClientCertificateSuccess(t *testing.T) {
74+
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload.json")
75+
if err != nil {
76+
t.Fatal(err)
77+
}
78+
cert, err := source(nil)
79+
if err != nil {
80+
t.Fatal(err)
81+
}
82+
if cert.Certificate == nil {
83+
t.Fatal("got nil, want non-nil Certificate")
84+
}
85+
if cert.PrivateKey == nil {
86+
t.Fatal("got nil, want non-nil PrivateKey")
87+
}
88+
}

0 commit comments

Comments
 (0)