client/vpc/vpc.go (61 lines of code) (raw):
// Copyright 2023 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 vpc contains utilties for handling VPC-protected keys.
package vpc
import (
"context"
"crypto/x509"
"errors"
"fmt"
"net/url"
ekmpb "cloud.google.com/go/kms/apiv1/kmspb"
rpb "cloud.google.com/go/kms/apiv1/kmspb"
"github.com/googleapis/gax-go/v2"
)
// CloudEKMClient is an interface corresponding to CloudKMS' EKMClient.
type CloudEKMClient interface {
GetEkmConnection(context.Context, *ekmpb.GetEkmConnectionRequest, ...gax.CallOption) (*ekmpb.EkmConnection, error)
Close() error
}
// Converts a slice of KMS Certificates to x509 Certificates.
func toCertPool(kmsCerts []*ekmpb.Certificate) (*x509.CertPool, error) {
certPool := x509.NewCertPool()
for _, kmsCert := range kmsCerts {
cert, err := x509.ParseCertificate(kmsCert.GetRawDer())
if err != nil {
return nil, fmt.Errorf("error parsing KMS Connection cert: %w", err)
}
certPool.AddCert(cert)
}
return certPool, nil
}
func externalURI(hostname string, keyPath string) string {
u := url.URL{
Scheme: "https",
Host: hostname,
Path: keyPath,
}
return u.String()
}
// GetURIAndCerts uses the provided client to get the EKM URI and service resolver certificates
// for the given cryptoKey.
func GetURIAndCerts(ctx context.Context, client CloudEKMClient, cryptoKey *rpb.CryptoKey) (string, *x509.CertPool, error) {
ekmConnName := cryptoKey.GetCryptoKeyBackend()
if len(ekmConnName) == 0 {
return "", nil, errors.New("No EKM Connection name specified")
}
ekmConn, err := client.GetEkmConnection(ctx, &ekmpb.GetEkmConnectionRequest{Name: ekmConnName})
if err != nil {
return "", nil, fmt.Errorf("error retrieving KMS EkmConnection: %w", err)
}
if len(ekmConn.GetServiceResolvers()) == 0 {
return "", nil, fmt.Errorf("No service resolvers found for EkmConnection %v", ekmConnName)
}
sr := ekmConn.GetServiceResolvers()[0]
leafCerts, err := toCertPool(sr.GetServerCertificates())
if err != nil {
return "", nil, err
}
// For EXTERNAL_VPC, construct the URI using the hostname from the EkmConnection and key
// path from ExternalProtectionLevelOptions.
cryptoKeyVer := cryptoKey.GetPrimary()
if cryptoKeyVer == nil {
return "", nil, errors.New("No CryptoKeyVersion found")
}
if cryptoKeyVer.ExternalProtectionLevelOptions == nil {
return "", nil, errors.New("CryptoKeyVersion does not have external protection level options despite being EXTERNAL_VPC protection level")
}
keyPath := cryptoKeyVer.GetExternalProtectionLevelOptions().GetEkmConnectionKeyPath()
return externalURI(sr.GetHostname(), keyPath), leafCerts, nil
}