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

feat(auth): adds X509 workload certificate provider #10233

Merged
merged 13 commits into from
May 28, 2024
Merged
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
codyoss marked this conversation as resolved.
Show resolved Hide resolved
}
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")
}
}