Skip to content

Commit

Permalink
Use known CA and add tier and gcs bucket options.
Browse files Browse the repository at this point in the history
  • Loading branch information
maraino committed Jun 9, 2021
1 parent 529eb4b commit ac3c754
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 57 deletions.
14 changes: 9 additions & 5 deletions cas/apiv1/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,15 @@ type Options struct {
// KeyManager is the KMS used to generate keys in SoftCAS.
KeyManager kms.KeyManager `json:"-"`

// Project and Location are parameters used in CloudCAS to create a new
// certificate authority.
Project string `json:"-"`
Location string `json:"-"`
CaPool string `json:"-"`
// Project, Location, CaPool and GCSBucket are parameters used in CloudCAS
// to create a new certificate authority. If a CaPool does not exists it
// will be created. GCSBucket is optional, if not provided GCloud will
// create a managed bucket.
Project string `json:"-"`
Location string `json:"-"`
CaPool string `json:"-"`
CaPoolTier string `json:"-"`
GCSBucket string `json:"-"`
}

// CertificateIssuer contains the properties used to use the StepCAS certificate
Expand Down
66 changes: 46 additions & 20 deletions cas/cloudcas/cloudcas.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/rand"
"crypto/x509"
"encoding/asn1"
"encoding/json"
"encoding/pem"
"regexp"
"strings"
Expand Down Expand Up @@ -67,13 +68,21 @@ var revocationCodeMap = map[int]pb.RevocationReason{
10: pb.RevocationReason_ATTRIBUTE_AUTHORITY_COMPROMISE,
}

// caPoolTierMap contains the map between apv1.Options.Tier and the pb type.
var caPoolTierMap = map[string]pb.CaPool_Tier{
"": pb.CaPool_DEVOPS,
"ENTERPRISE": pb.CaPool_ENTERPRISE,
"DEVOPS": pb.CaPool_DEVOPS,
}

// CloudCAS implements a Certificate Authority Service using Google Cloud CAS.
type CloudCAS struct {
client CertificateAuthorityClient
certificateAuthority string
project string
location string
caPool string
caPoolTier pb.CaPool_Tier
gcsBucket string
}

Expand All @@ -94,7 +103,8 @@ var newCertificateAuthorityClient = func(ctx context.Context, credentialsFile st
// New creates a new CertificateAuthorityService implementation using Google
// Cloud CAS.
func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
if opts.IsCreator {
var caPoolTier pb.CaPool_Tier
if opts.IsCreator && opts.CertificateAuthority == "" {
switch {
case opts.Project == "":
return nil, errors.New("cloudCAS 'project' cannot be empty")
Expand All @@ -103,7 +113,10 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
case opts.CaPool == "":
return nil, errors.New("cloudCAS 'caPool' cannot be empty")
}

var ok bool
if caPoolTier, ok = caPoolTierMap[strings.ToUpper(opts.CaPoolTier)]; !ok {
return nil, errors.New("cloudCAS 'caPoolTier' is not a valid tier")
}
} else {
if opts.CertificateAuthority == "" {
return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty")
Expand All @@ -130,12 +143,15 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
return nil, err
}

// GCSBucket is the the bucket name or empty for a managed bucket.
return &CloudCAS{
client: client,
certificateAuthority: opts.CertificateAuthority,
project: opts.Project,
location: opts.Location,
caPool: opts.CaPool,
gcsBucket: opts.GCSBucket,
caPoolTier: caPoolTier,
}, nil
}

Expand Down Expand Up @@ -267,6 +283,8 @@ func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthor
return nil, errors.New("cloudCAS `location` cannot be empty")
case c.caPool == "":
return nil, errors.New("cloudCAS `caPool` cannot be empty")
case c.caPoolTier == 0:
return nil, errors.New("cloudCAS `caPoolTier` cannot be empty")
case req.Template == nil:
return nil, errors.New("createCertificateAuthorityRequest `template` cannot be nil")
case req.Lifetime == 0:
Expand Down Expand Up @@ -362,14 +380,6 @@ func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthor
return nil, errors.Wrap(err, "cloudCAS CreateCertificateAuthority failed")
}

// Enable root certificate
if req.Type == apiv1.RootCA {
ca, err = c.enableCertificateAuthority(ca)
if err != nil {
return nil, err
}
}

// Sign Intermediate CAs with the parent.
if req.Type == apiv1.IntermediateCA {
ca, err = c.signIntermediateCA(parent, ca.Name, req)
Expand All @@ -378,6 +388,12 @@ func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthor
}
}

// Enable Certificate Authority.
ca, err = c.enableCertificateAuthority(ca)
if err != nil {
return nil, err
}

if len(ca.PemCaCertificates) == 0 {
return nil, errors.New("cloudCAS CreateCertificateAuthority failed: PemCaCertificates is empty")
}
Expand All @@ -397,6 +413,9 @@ func (c *CloudCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthor
}
}

b, _ := json.MarshalIndent(ca, "", "\t")
println(string(b))

return &apiv1.CreateCertificateAuthorityResponse{
Name: ca.Name,
Certificate: cert,
Expand All @@ -419,18 +438,24 @@ func (c *CloudCAS) createCaPoolIfNecessary() (string, error) {
return "", errors.Wrap(err, "cloudCAS GetCaPool failed")
}

// PublishCrl is only supported by the enterprise tier
var publishCrl bool
if c.caPoolTier == pb.CaPool_ENTERPRISE {
publishCrl = true
}

ctx, cancel = defaultContext()
defer cancel()

op, err := c.client.CreateCaPool(ctx, &pb.CreateCaPoolRequest{
Parent: "projects/" + c.project + "/locations/" + c.location,
CaPoolId: c.caPool,
CaPool: &pb.CaPool{
Tier: pb.CaPool_ENTERPRISE,
Tier: c.caPoolTier,
IssuancePolicy: nil,
PublishingOptions: &pb.CaPool_PublishingOptions{
PublishCaCert: true,
PublishCrl: true,
PublishCrl: publishCrl,
},
},
})
Expand Down Expand Up @@ -507,7 +532,8 @@ func (c *CloudCAS) createCertificate(tpl *x509.Certificate, lifetime time.Durati
Lifetime: durationpb.New(lifetime),
Labels: map[string]string{},
},
RequestId: requestID,
IssuingCertificateAuthorityId: getResourceName(c.certificateAuthority),
RequestId: requestID,
})
if err != nil {
return nil, nil, errors.Wrap(err, "cloudCAS CreateCertificate failed")
Expand Down Expand Up @@ -583,7 +609,8 @@ func (c *CloudCAS) signIntermediateCA(parent, name string, req *apiv1.CreateCert
Lifetime: durationpb.New(req.Lifetime),
Labels: map[string]string{},
},
RequestId: req.RequestID,
IssuingCertificateAuthorityId: getResourceName(req.Parent.Name),
RequestId: req.RequestID,
})
if err != nil {
return nil, errors.Wrap(err, "cloudCAS CreateCertificate failed")
Expand Down Expand Up @@ -618,12 +645,6 @@ func (c *CloudCAS) signIntermediateCA(parent, name string, req *apiv1.CreateCert
return nil, errors.Wrap(err, "cloudCAS ActivateCertificateAuthority failed")
}

// Enable certificate authority
ca, err = c.enableCertificateAuthority(ca)
if err != nil {
return nil, err
}

return ca, nil
}

Expand Down Expand Up @@ -690,7 +711,12 @@ func getCertificateAndChain(certpb *pb.Certificate) (*x509.Certificate, []*x509.
}

return cert, chain, nil
}

// getResourceName returns the last part of a resource.
func getResourceName(name string) string {
parts := strings.Split(name, "/")
return parts[len(parts)-1]
}

// Normalize a certificate authority name to comply with [a-zA-Z0-9-_].
Expand Down

0 comments on commit ac3c754

Please sign in to comment.