forked from googleapis/google-cloud-go
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(auth): adds X509 workload certificate provider (googleapis#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.
- Loading branch information
Showing
10 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
8 changes: 8 additions & 0 deletions
8
auth/internal/transport/cert/testdata/certificate_config_workload.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"cert_configs": { | ||
"workload": { | ||
"cert_path": "testdata/workload_cert.pem", | ||
"key_path": "testdata/workload_key.pem" | ||
} | ||
} | ||
} |
2 changes: 2 additions & 0 deletions
2
auth/internal/transport/cert/testdata/certificate_config_workload_empty.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
{ | ||
} |
8 changes: 8 additions & 0 deletions
8
auth/internal/transport/cert/testdata/certificate_config_workload_invalid_cert.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"cert_configs": { | ||
"workload": { | ||
"cert_path": "testdata/workload_cert_invalid.txt", | ||
"key_path": "testdata/workload_key.pem" | ||
} | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
auth/internal/transport/cert/testdata/certificate_config_workload_no_cert.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"cert_configs": { | ||
"workload": { | ||
"key_path": "testdata/workload_key.pem" | ||
} | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
auth/internal/transport/cert/testdata/certificate_config_workload_no_key.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"cert_configs": { | ||
"workload": { | ||
"cert_path": "testdata/workload_cert.pem" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIDujCCAqICCQD+yrCYuiC8djANBgkqhkiG9w0BAQsFADCBnTELMAkGA1UEBhMC | ||
VVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAPBgNVBAcMCEtpcmtsYW5kMQ8wDQYD | ||
VQQKDAZHb29nbGUxDjAMBgNVBAsMBUNsb3VkMRswGQYDVQQDDBJnb29nbGVhcGlz | ||
dGVzdC5jb20xKDAmBgkqhkiG9w0BCQEWGWdvb2dsZWFwaXN0ZXN0QGdvb2dsZS5j | ||
b20wIBcNMjAxMDIzMjEyNTU1WhgPMjEyMDA5MjkyMTI1NTVaMIGdMQswCQYDVQQG | ||
EwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxDzAN | ||
BgNVBAoMBkdvb2dsZTEOMAwGA1UECwwFQ2xvdWQxGzAZBgNVBAMMEmdvb2dsZWFw | ||
aXN0ZXN0LmNvbTEoMCYGCSqGSIb3DQEJARYZZ29vZ2xlYXBpc3Rlc3RAZ29vZ2xl | ||
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKnzFX97VP4XSQ8l | ||
4/Z08eajnAiGpK+ZQTV9k7Qy2tpo5+iFFiL0JLGP9+GRILuDGQufYlPLDhLLho9V | ||
YXIR9UOhhapmQJqUAUFhvZlBEixLxcfwa2LecNiJ6+8gvJCoRbrPIrz91crY+t59 | ||
aY/09vmsCbFDX8d8WWVnww4285dfKwE2IDinqZ1VuT4zYR66f4lL8qj6t5TXeGAW | ||
Nkd6O3yuAVO8RLiXBRRABP5217mq0jNL+kJUormzhuKgvP+oxRsi56XHPGiq7l2e | ||
54PS/cqa4atjqbhZI1xV27y0sVr0/CmBsfeM3TwLbCSjv7r0lCz64xtCJa8R45MA | ||
22or9z8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAnwLY9qBIQ2IYDLNLx16av8C6 | ||
9vca8gOzMpYZ4UKHDN+Qk2CidpmFamXWDXqmOLNZYlmEoGY5n8zg8rwYK+vauqwb | ||
o94HzxLmQcQ4kmAI4xJnMqKZAbukRdWw2GCuvdVqG4Osngz4WBIHrAsl4btogdJy | ||
ACU/YUA3K0tLjwe6wUYYF6eu5sb6zJkF4cfLpqECWtF9XG6nkJbo2GomHFuHm+6t | ||
gOj7YiqU/cHCyU4FQF9/2jDLzFHxt2Bb30zi602YjuIZhYp35ktI66XwsE4kFmwo | ||
iHCEG0fXMNN7OMFmNg2YVLhaHxrQNFxbzOQdfKg2gi2qzX4AiCo1tx5LCg6aGw== | ||
-----END CERTIFICATE----- |
1 change: 1 addition & 0 deletions
1
auth/internal/transport/cert/testdata/workload_cert_invalid.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
This is not a cert file. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
-----BEGIN PRIVATE KEY----- | ||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCp8xV/e1T+F0kP | ||
JeP2dPHmo5wIhqSvmUE1fZO0MtraaOfohRYi9CSxj/fhkSC7gxkLn2JTyw4Sy4aP | ||
VWFyEfVDoYWqZkCalAFBYb2ZQRIsS8XH8Gti3nDYievvILyQqEW6zyK8/dXK2Pre | ||
fWmP9Pb5rAmxQ1/HfFllZ8MONvOXXysBNiA4p6mdVbk+M2Eeun+JS/Ko+reU13hg | ||
FjZHejt8rgFTvES4lwUUQAT+dte5qtIzS/pCVKK5s4bioLz/qMUbIuelxzxoqu5d | ||
nueD0v3KmuGrY6m4WSNcVdu8tLFa9PwpgbH3jN08C2wko7+69JQs+uMbQiWvEeOT | ||
ANtqK/c/AgMBAAECggEAYjeE3hb1yJ7Gb0WzmDR/tI4rV9YQiRcl03cOjJ6zUnQ8 | ||
SmnXoD2+kwuj8y1/YD7kk436MnjwWjZbPqzWUylDuGE5sX/EqFEO5K1K+K3dhdII | ||
rIMqXIo3Zz1WJ+2gbG2DVvHsnpKIIuIBIeISxsqIjUQ6mcJZMR2RQISV+roRTxIU | ||
1Ga0xWrExcKL8FSjs8ih0DWU4vHoSYH4DFXB1/ViyLn+DEljnOlo8Q+7DG0uQQnX | ||
ixfYMbXSJcZxFm1iwuZv8SESjqbTsogNny5Wi6H9Vp0JFasAPUjnc+QuD/U1HTDn | ||
PCX3eBNMcxvVJDhu/7nnO7kcU1Cx0gJeN+1bklrAcQKBgQDURl0Ac8N94I82n4Lg | ||
wjGLWj3AMxSEHNcZuomCvoYcLTmJdd2tOnunXhh1jANnx6q8P8aR5fiTthokIUdx | ||
bOmWwFAbP6kMe0WFWQhXjX4mXLRmJ4mWayWCE7hstnDb3/Fr7LuJeg5L3OU4ss3b | ||
j4UvhtuQ9Qh8piVhKwFkQh3tOQKBgQDM9NSkRDVW3Q37lMUdyn8B2FBF78e/9ck+ | ||
5bHOs52G2hXJ4tyLYNjBoLXPpMp9VWRTXxUaii+gHSa4DkHTkFwIg34hLgrCX7Gc | ||
a0rldvkpX0xWSANfvO9bvavPgKnLSP8j3mjDiwqJuy3L5TBThIHDvPV9F/akpLne | ||
bdcywa4ANwKBgHlvAzcGAniZJPRXjfRrwxH3/slbr0nggcDLMG0l9uxZhse3MKgv | ||
g5t8PbvI7A3LcEWeqka+a1R84Tl3/DnL11kRDQJ5iYiFYIDnLNmBLQBfGigySAhP | ||
pTZjd6ZhO/DcjGx0EdiUhWcqp8qmpxMKaGOG30ZulntQRKPwiSxEkoApAoGBAJ1o | ||
h4ulawXMfnmyt3T62XJ0TKp5zoKqZSYuSNIEdr5j7goAdvuApNiI8jmISY/arlOt | ||
mcqpSIyC9wKyyHGQ1G4hdxRKhS7lScZlTL9REWlp7HnzksvLklV2JWcXXNBovrMw | ||
lGth9PT00eZfni72fKb1D+FEL0Qh0zJ2T6mGwHkfAoGAMOy8bbyCASCYG9MYzqaP | ||
Lf+AKKNEYUvUGspyJUqu5ERudr5stmei6PrchxFiKjm5Qg7B/M1VnKsCtL9kk8Z9 | ||
lHgwU5mOATZvd9k/5oiuRxzXyrWqFoT/mivI2rZE+g5cLTLytCTnyLjHm5B/aTy8 | ||
1AmbAh5hvWYs+EMKZAlQ5GM= | ||
-----END PRIVATE KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// Copyright 2024 Google LLC | ||
// | ||
// 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 cert | ||
|
||
import ( | ||
"crypto/tls" | ||
"encoding/json" | ||
"errors" | ||
"io" | ||
"os" | ||
|
||
"github.com/googleapis/enterprise-certificate-proxy/client/util" | ||
) | ||
|
||
type certConfigs struct { | ||
Workload *workloadSource `json:"workload"` | ||
} | ||
|
||
type workloadSource struct { | ||
CertPath string `json:"cert_path"` | ||
KeyPath string `json:"key_path"` | ||
} | ||
|
||
type certificateConfig struct { | ||
CertConfigs certConfigs `json:"cert_configs"` | ||
} | ||
|
||
// NewWorkloadX509CertProvider creates a certificate source | ||
// that reads a certificate and private key file from the local file system. | ||
// This is intended to be used for workload identity federation. | ||
// | ||
// The configFilePath points to a config file containing relevant parameters | ||
// such as the certificate and key file paths. | ||
// If configFilePath is empty, the client will attempt to load the config from | ||
// a well-known gcloud location. | ||
func NewWorkloadX509CertProvider(configFilePath string) (Provider, error) { | ||
if configFilePath == "" { | ||
envFilePath := util.GetConfigFilePathFromEnv() | ||
if envFilePath != "" { | ||
configFilePath = envFilePath | ||
} else { | ||
configFilePath = util.GetDefaultConfigFilePath() | ||
} | ||
} | ||
|
||
certFile, keyFile, err := getCertAndKeyFiles(configFilePath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
source := &workloadSource{ | ||
CertPath: certFile, | ||
KeyPath: keyFile, | ||
} | ||
return source.getClientCertificate, nil | ||
} | ||
|
||
// getClientCertificate attempts to load the certificate and key from the files specified in the | ||
// certificate config. | ||
func (s *workloadSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) { | ||
cert, err := tls.LoadX509KeyPair(s.CertPath, s.KeyPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &cert, nil | ||
} | ||
|
||
// getCertAndKeyFiles attempts to read the provided config file and return the certificate and private | ||
// key file paths. | ||
func getCertAndKeyFiles(configFilePath string) (string, string, error) { | ||
jsonFile, err := os.Open(configFilePath) | ||
if err != nil { | ||
if errors.Is(err, os.ErrNotExist) { | ||
return "", "", errSourceUnavailable | ||
} | ||
return "", "", err | ||
} | ||
|
||
byteValue, err := io.ReadAll(jsonFile) | ||
if err != nil { | ||
return "", "", err | ||
} | ||
|
||
var config certificateConfig | ||
if err := json.Unmarshal(byteValue, &config); err != nil { | ||
return "", "", err | ||
} | ||
|
||
if config.CertConfigs.Workload == nil { | ||
return "", "", errors.New("no Workload Identity Federation certificate information found in the certificate configuration file") | ||
} | ||
|
||
certFile := config.CertConfigs.Workload.CertPath | ||
keyFile := config.CertConfigs.Workload.KeyPath | ||
|
||
if certFile == "" { | ||
return "", "", errors.New("certificate configuration is missing the certificate file location") | ||
} | ||
|
||
if keyFile == "" { | ||
return "", "", errors.New("certificate configuration is missing the key file location") | ||
} | ||
|
||
return certFile, keyFile, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright 2024 Google LLC | ||
// | ||
// 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 cert | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
) | ||
|
||
func TestWorkloadCertSource_ConfigMissing(t *testing.T) { | ||
source, err := NewWorkloadX509CertProvider("missing.json") | ||
if got, want := err, errSourceUnavailable; !errors.Is(err, errSourceUnavailable) { | ||
t.Fatalf("got %v, want %v", got, want) | ||
} | ||
if source != nil { | ||
t.Errorf("got %v, want nil source", source) | ||
} | ||
} | ||
|
||
func TestWorkloadCertSource_EmptyConfig(t *testing.T) { | ||
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload_empty.json") | ||
if err == nil { | ||
t.Fatal("got nil, want non-nil error") | ||
} | ||
if source != nil { | ||
t.Errorf("got %v, want nil source", source) | ||
} | ||
} | ||
|
||
func TestWorkloadCertSource_MissingCert(t *testing.T) { | ||
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload_no_cert.json") | ||
if err == nil { | ||
t.Fatal("got nil, want non-nil error") | ||
} | ||
if source != nil { | ||
t.Errorf("got %v, want nil source", source) | ||
} | ||
} | ||
|
||
func TestWorkloadCertSource_MissingKey(t *testing.T) { | ||
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload_no_key.json") | ||
if err == nil { | ||
t.Fatal("got nil, want non-nil error") | ||
} | ||
if source != nil { | ||
t.Errorf("got %v, want nil source", source) | ||
} | ||
} | ||
|
||
func TestWorkloadCertSource_GetClientCertificateInvalidCert(t *testing.T) { | ||
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload_invalid_cert.json") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
_, err = source(nil) | ||
if err == nil { | ||
t.Fatal("got nil, want non-nil error") | ||
} | ||
} | ||
|
||
func TestWorkloadCertSource_GetClientCertificateSuccess(t *testing.T) { | ||
source, err := NewWorkloadX509CertProvider("testdata/certificate_config_workload.json") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
cert, err := source(nil) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if cert.Certificate == nil { | ||
t.Fatal("got nil, want non-nil Certificate") | ||
} | ||
if cert.PrivateKey == nil { | ||
t.Fatal("got nil, want non-nil PrivateKey") | ||
} | ||
} |