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.
This commit is contained in:
parent
fa099f2ae2
commit
38fa780775
6 changed files with 167 additions and 44 deletions
|
@ -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…
Reference in a new issue