pkg/cas/certs.go (83 lines of code) (raw):
// Copyright 2021 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
//
// https://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 cas
import (
"fmt"
"time"
"context"
"log"
"google.golang.org/api/option"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/durationpb"
"k8s.io/apimachinery/pkg/util/rand"
privateca "cloud.google.com/go/security/privateca/apiv1"
privatecapb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1"
)
type casCertProvider struct {
capool string
caClient *privateca.CertificateAuthorityClient
}
// NewCASCertProvider create a client for Google CAS.
//
// capool is in format: projects/*/locations/*/caPools/*
//
// Should default based on the config project and the location of the config cluster.
//
// Files: if running as root, will create the well-known files:
// -
//
// In GKE, if "--enable-mesh-certificates" cluster option and and the annotation
// "security.cloud.google.com/use-workload-certificates" will automatically
// create the files and this is not needed. As such the file should be checked first.
// The config in GKE is based on WorkloadCertificateConfig - this file is attempting to emulate it.
//
//
// See: https://cloud.google.com/traffic-director/docs/security-proxyless-setup?hl=en
func NewCASCertProvider(capool string, ol []grpc.DialOption) (*casCertProvider, error) {
caClient := &casCertProvider{capool: capool}
ctx := context.Background()
var err error
var ol1 []option.ClientOption
for _, v := range ol {
ol1 = append(ol1, option.WithGRPCDialOption(v))
}
caClient.caClient, err = privateca.NewCertificateAuthorityClient(ctx, ol1...)
if err != nil {
log.Printf("unable to initialize google cas caclient: %v", err)
return nil, err
}
return caClient, nil
}
func (r *casCertProvider) createCertReq(csrPEM []byte, lifetime time.Duration) *privatecapb.CreateCertificateRequest {
var isCA bool = false
rand.Seed(time.Now().UnixNano())
name := fmt.Sprintf("csr-workload-%s", rand.String(8))
// We use Certificate_Config option to ensure that we only request a certificate with CAS supported extensions/usages.
// CAS uses the PEM encoded CSR only for its public key and infers the certificate SAN (identity) of the workload through SPIFFE identity reflection
creq := &privatecapb.CreateCertificateRequest{
Parent: r.capool,
CertificateId: name,
Certificate: &privatecapb.Certificate{
Lifetime: durationpb.New(lifetime),
CertificateConfig: &privatecapb.Certificate_Config{
Config: &privatecapb.CertificateConfig{
SubjectConfig: &privatecapb.CertificateConfig_SubjectConfig{
Subject: &privatecapb.Subject{},
},
X509Config: &privatecapb.X509Parameters{
KeyUsage: &privatecapb.KeyUsage{
BaseKeyUsage: &privatecapb.KeyUsage_KeyUsageOptions{
DigitalSignature: true,
KeyEncipherment: true,
},
ExtendedKeyUsage: &privatecapb.KeyUsage_ExtendedKeyUsageOptions{
ServerAuth: true,
ClientAuth: true,
},
},
CaOptions: &privatecapb.X509Parameters_CaOptions{
IsCa: &isCA,
},
},
PublicKey: &privatecapb.PublicKey{
Format: privatecapb.PublicKey_PEM,
Key: csrPEM,
},
},
},
SubjectMode: privatecapb.SubjectRequestMode_REFLECTED_SPIFFE,
},
}
return creq
}
// CSRSign returns the cert and the full path to the root. Istio workloads present full chains.
func (r *casCertProvider) CSRSign(ctx context.Context, csrPEM []byte, certValidTTLInSec int64) ([]string, error) {
certChain := []string{}
creq := r.createCertReq(csrPEM, time.Duration(certValidTTLInSec)*time.Second)
cresp, err := r.caClient.CreateCertificate(ctx, creq)
if err != nil {
return certChain, err
}
certChain = append(certChain, cresp.GetPemCertificate())
certChain = append(certChain, cresp.GetPemCertificateChain()...)
return certChain, nil
}