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 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. // Initialize key manager if it has not been set in the options.
if a.keyManager == nil { if a.keyManager == nil {
var options kmsapi.Options 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. // Initialize the X.509 CA Service if it has not been set in the options.
// If a.config.DB is nil then a simple, barebones in memory DB will be used. if a.x509CAService == nil {
if a.db == nil { var options casapi.Options
if a.db, err = db.New(a.config.DB); err != nil { 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 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. // 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) 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 // Decrypt and load SSH keys
var tmplVars templates.Step var tmplVars templates.Step
if a.config.SSH != nil { if a.config.SSH != nil {

@ -181,19 +181,22 @@ func (c *Config) Validate() error {
case c.Address == "": case c.Address == "":
return errors.New("address cannot be empty") 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: case len(c.DNSNames) == 0:
return errors.New("dnsNames cannot be empty") 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) // Validate address (a port is required)
if _, _, err := net.SplitHostPort(c.Address); err != nil { if _, _, err := net.SplitHostPort(c.Address); err != nil {
return errors.Errorf("invalid address %s", c.Address) return errors.Errorf("invalid address %s", c.Address)

@ -46,3 +46,15 @@ type RevokeCertificateResponse struct {
Certificate *x509.Certificate Certificate *x509.Certificate
CertificateChain []*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) 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 represents the CAS type used.
type Type string type Type string

@ -29,6 +29,7 @@ func init() {
type CertificateAuthorityClient interface { type CertificateAuthorityClient interface {
CreateCertificate(ctx context.Context, req *pb.CreateCertificateRequest, opts ...gax.CallOption) (*pb.Certificate, error) 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) 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 // 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 }, 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. // CreateCertificate signs a new certificate using Google Cloud CAS.
func (c *CloudCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) { func (c *CloudCAS) CreateCertificate(req *apiv1.CreateCertificateRequest) (*apiv1.CreateCertificateResponse, error) {
switch { switch {

@ -74,9 +74,10 @@ zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw=
) )
type testClient struct { type testClient struct {
credentialsFile string credentialsFile string
certificate *pb.Certificate certificate *pb.Certificate
err error certificateAuthority *pb.CertificateAuthority
err error
} }
func newTestClient(credentialsFile string) (CertificateAuthorityClient, error) { func newTestClient(credentialsFile string) (CertificateAuthorityClient, error) {
@ -96,6 +97,9 @@ func okTestClient() *testClient {
PemCertificate: testSignedCertificate, PemCertificate: testSignedCertificate,
PemCertificateChain: []string{testIntermediateCertificate, testRootCertificate}, PemCertificateChain: []string{testIntermediateCertificate, testRootCertificate},
}, },
certificateAuthority: &pb.CertificateAuthority{
PemCaCertificates: []string{testIntermediateCertificate, testRootCertificate},
},
} }
} }
@ -114,6 +118,9 @@ func badTestClient() *testClient {
PemCertificate: "not a pem cert", PemCertificate: "not a pem cert",
PemCertificateChain: []string{testIntermediateCertificate, testRootCertificate}, 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 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 { func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate {
t.Helper() t.Helper()
crt, err := parseCertificate(pemCert) 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) { func TestCloudCAS_CreateCertificate(t *testing.T) {
type fields struct { type fields struct {
client CertificateAuthorityClient client CertificateAuthorityClient

Loading…
Cancel
Save