Add interface to get root certificate from CAS.

This change makes easier the configuration of cloudCAS as it does
not require to configure the root or intermediate certificate
in the ca.json. CloudCAS will get the root certificate using
the configured certificateAuthority.
pull/367/head
Mariano Cano 4 years ago
parent fa099f2ae2
commit 38fa780775

@ -133,6 +133,14 @@ func (a *Authority) init() error {
var err error
// Initialize step-ca Database if it's not already initialized with WithDB.
// If a.config.DB is nil then a simple, barebones in memory DB will be used.
if a.db == nil {
if a.db, err = db.New(a.config.DB); err != nil {
return err
}
}
// Initialize key manager if it has not been set in the options.
if a.keyManager == nil {
var options kmsapi.Options
@ -145,12 +153,43 @@ func (a *Authority) init() error {
}
}
// Initialize step-ca Database if it's not already initialized with WithDB.
// If a.config.DB is nil then a simple, barebones in memory DB will be used.
if a.db == nil {
if a.db, err = db.New(a.config.DB); err != nil {
// Initialize the X.509 CA Service if it has not been set in the options.
if a.x509CAService == nil {
var options casapi.Options
if a.config.CAS != nil {
options = *a.config.CAS
}
// Read intermediate and create X509 signer for default CAS.
if options.Is(casapi.SoftCAS) {
options.Issuer, err = pemutil.ReadCertificate(a.config.IntermediateCert)
if err != nil {
return err
}
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
}
a.x509CAService, err = cas.New(context.Background(), options)
if err != nil {
return err
}
// Get root certificate from CAS.
if srv, ok := a.x509CAService.(casapi.CertificateAuthorityGetter); ok {
resp, err := srv.GetCertificateAuthority(&casapi.GetCertificateAuthorityRequest{
Name: options.Certificateauthority,
})
if err != nil {
return err
}
a.rootX509Certs = append(a.rootX509Certs, resp.RootCertificate)
}
}
// Read root certificates and store them in the certificates map.
@ -185,34 +224,6 @@ func (a *Authority) init() error {
a.certificates.Store(hex.EncodeToString(sum[:]), crt)
}
// Initialize the X.509 CA Service if it has not been set in the options.
if a.x509CAService == nil {
var options casapi.Options
if a.config.CAS != nil {
options = *a.config.CAS
}
// Read intermediate and create X509 signer for default CAS.
if options.HasType(casapi.SoftCAS) {
options.Issuer, err = pemutil.ReadCertificate(a.config.IntermediateCert)
if err != nil {
return err
}
options.Signer, err = a.keyManager.CreateSigner(&kmsapi.CreateSignerRequest{
SigningKey: a.config.IntermediateKey,
Password: []byte(a.config.Password),
})
if err != nil {
return err
}
}
a.x509CAService, err = cas.New(context.Background(), options)
if err != nil {
return nil
}
}
// Decrypt and load SSH keys
var tmplVars templates.Step
if a.config.SSH != nil {

@ -181,19 +181,22 @@ func (c *Config) Validate() error {
case c.Address == "":
return errors.New("address cannot be empty")
case c.Root.HasEmpties():
return errors.New("root cannot be empty")
case c.IntermediateCert == "":
return errors.New("crt cannot be empty")
case c.IntermediateKey == "" && c.CAS.HasType(cas.SoftCAS):
return errors.New("key cannot be empty")
case len(c.DNSNames) == 0:
return errors.New("dnsNames cannot be empty")
}
// The default CAS requires root, crt and key.
if c.CAS.Is(cas.SoftCAS) {
switch {
case c.Root.HasEmpties():
return errors.New("root cannot be empty")
case c.IntermediateCert == "":
return errors.New("crt cannot be empty")
case c.IntermediateKey == "":
return errors.New("key cannot be empty")
}
}
// Validate address (a port is required)
if _, _, err := net.SplitHostPort(c.Address); err != nil {
return errors.Errorf("invalid address %s", c.Address)

@ -46,3 +46,15 @@ type RevokeCertificateResponse struct {
Certificate *x509.Certificate
CertificateChain []*x509.Certificate
}
// GetCertificateAuthorityRequest is the request used to get the root
// certificate from a CAS.
type GetCertificateAuthorityRequest struct {
Name string
}
// GetCertificateAuthorityResponse is the response that contains
// the root certificate.
type GetCertificateAuthorityResponse struct {
RootCertificate *x509.Certificate
}

@ -12,6 +12,12 @@ type CertificateAuthorityService interface {
RevokeCertificate(req *RevokeCertificateRequest) (*RevokeCertificateResponse, error)
}
// CertificateAuthorityGetter is an interface implemented by a
// CertificateAuthorityService that has a method to get the root certificate.
type CertificateAuthorityGetter interface {
GetCertificateAuthority(req *GetCertificateAuthorityRequest) (*GetCertificateAuthorityResponse, error)
}
// Type represents the CAS type used.
type Type string

@ -29,6 +29,7 @@ func init() {
type CertificateAuthorityClient interface {
CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error)
RevokeCertificate(ctx context.Context, req *pb.RevokeCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error)
GetCertificateAuthority(ctx context.Context, req *pb.GetCertificateAuthorityRequest, opts ...gax.CallOption) (*pb.CertificateAuthority, error)
}
// recocationCodeMap maps revocation reason codes from RFC 5280, to Google CAS
@ -84,6 +85,39 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) {
}, nil
}
// GetCertificateAuthority returns the root certificate for the given
// certificate authority. It implements apiv1.CertificateAuthorityGetter
// interface.
func (c *CloudCAS) GetCertificateAuthority(req *apiv1.GetCertificateAuthorityRequest) (*apiv1.GetCertificateAuthorityResponse, error) {
name := req.Name
if name == "" {
name = c.certificateAuthority
}
ctx, cancel := defaultContext()
defer cancel()
resp, err := c.client.GetCertificateAuthority(ctx, &pb.GetCertificateAuthorityRequest{
Name: name,
})
if err != nil {
return nil, errors.Wrap(err, "cloudCAS GetCertificateAuthority failed")
}
if len(resp.PemCaCertificates) == 0 {
return nil, errors.New("cloudCAS GetCertificateAuthority: PemCACertificate should not be empty")
}
// Last certificate in the chain is the root.
root, err := parseCertificate(resp.PemCaCertificates[len(resp.PemCaCertificates)-1])
if err != nil {
return nil, err
}
return &apiv1.GetCertificateAuthorityResponse{
RootCertificate: root,
}, nil
}
// CreateCertificate signs a new certificate using Google Cloud CAS.
func (c *CloudCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
switch {

@ -74,9 +74,10 @@ zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw=
)
type testClient struct {
credentialsFile string
certificate *pb.Certificate
err error
credentialsFile string
certificate *pb.Certificate
certificateAuthority *pb.CertificateAuthority
err error
}
func newTestClient(credentialsFile string) (CertificateAuthorityClient, error) {
@ -96,6 +97,9 @@ func okTestClient() *testClient {
PemCertificate: testSignedCertificate,
PemCertificateChain: []string{testIntermediateCertificate, testRootCertificate},
},
certificateAuthority: &pb.CertificateAuthority{
PemCaCertificates: []string{testIntermediateCertificate, testRootCertificate},
},
}
}
@ -114,6 +118,9 @@ func badTestClient() *testClient {
PemCertificate: "not a pem cert",
PemCertificateChain: []string{testIntermediateCertificate, testRootCertificate},
},
certificateAuthority: &pb.CertificateAuthority{
PemCaCertificates: []string{testIntermediateCertificate, "not a pem cert"},
},
}
}
@ -134,6 +141,10 @@ func (c *testClient) RevokeCertificate(ctx context.Context, req *pb.RevokeCertif
return c.certificate, c.err
}
func (c *testClient) GetCertificateAuthority(ctx context.Context, req *pb.GetCertificateAuthorityRequest, opts ...gax.CallOption) (*pb.CertificateAuthority, error) {
return c.certificateAuthority, c.err
}
func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate {
t.Helper()
crt, err := parseCertificate(pemCert)
@ -263,6 +274,52 @@ func TestNew_real(t *testing.T) {
}
}
func TestCloudCAS_GetCertificateAuthority(t *testing.T) {
root := mustParseCertificate(t, testRootCertificate)
type fields struct {
client CertificateAuthorityClient
certificateAuthority string
}
type args struct {
req *apiv1.GetCertificateAuthorityRequest
}
tests := []struct {
name string
fields fields
args args
want *apiv1.GetCertificateAuthorityResponse
wantErr bool
}{
{"ok", fields{okTestClient(), testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{}}, &apiv1.GetCertificateAuthorityResponse{
RootCertificate: root,
}, false},
{"ok with name", fields{okTestClient(), testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{
Name: testCertificateName,
}}, &apiv1.GetCertificateAuthorityResponse{
RootCertificate: root,
}, false},
{"fail GetCertificateAuthority", fields{failTestClient(), testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{}}, nil, true},
{"fail bad root", fields{badTestClient(), testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{}}, nil, true},
{"fail no pems", fields{&testClient{certificateAuthority: &pb.CertificateAuthority{}}, testCertificateName}, args{&apiv1.GetCertificateAuthorityRequest{}}, nil, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CloudCAS{
client: tt.fields.client,
certificateAuthority: tt.fields.certificateAuthority,
}
got, err := c.GetCertificateAuthority(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("CloudCAS.GetCertificateAuthority() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CloudCAS.GetCertificateAuthority() = %v, want %v", got, tt.want)
}
})
}
}
func TestCloudCAS_CreateCertificate(t *testing.T) {
type fields struct {
client CertificateAuthorityClient

Loading…
Cancel
Save