Skip to content

Commit

Permalink
feat(auth): adds X509 workload certificate provider (googleapis#10233)
Browse files Browse the repository at this point in the history
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
aeitzman authored May 28, 2024
1 parent 0ceec32 commit 17a9db7
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 0 deletions.
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"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
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"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"cert_configs": {
"workload": {
"key_path": "testdata/workload_key.pem"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"cert_configs": {
"workload": {
"cert_path": "testdata/workload_cert.pem"
}
}
}
22 changes: 22 additions & 0 deletions auth/internal/transport/cert/testdata/workload_cert.pem
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-----
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is not a cert file.
28 changes: 28 additions & 0 deletions auth/internal/transport/cert/testdata/workload_key.pem
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-----
117 changes: 117 additions & 0 deletions auth/internal/transport/cert/workload_cert.go
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
}
88 changes: 88 additions & 0 deletions auth/internal/transport/cert/workload_cert_test.go
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")
}
}

0 comments on commit 17a9db7

Please sign in to comment.