From ef92a3a6d7903f7a641a776742cdf423891df6a6 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 19 Oct 2020 18:08:51 -0700 Subject: [PATCH 1/6] Move cas options under authority. --- authority/authority.go | 6 +++--- authority/config.go | 17 +++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/authority/authority.go b/authority/authority.go index 3fdb67cc..5b6f7761 100644 --- a/authority/authority.go +++ b/authority/authority.go @@ -156,8 +156,8 @@ func (a *Authority) init() error { // 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 + if a.config.AuthorityConfig.Options != nil { + options = *a.config.AuthorityConfig.Options } // Read intermediate and create X509 signer for default CAS. @@ -183,7 +183,7 @@ func (a *Authority) init() error { // Get root certificate from CAS. if srv, ok := a.x509CAService.(casapi.CertificateAuthorityGetter); ok { resp, err := srv.GetCertificateAuthority(&casapi.GetCertificateAuthorityRequest{ - Name: options.Certificateauthority, + Name: options.CertificateAuthority, }) if err != nil { return err diff --git a/authority/config.go b/authority/config.go index 48d56952..9d79ce9a 100644 --- a/authority/config.go +++ b/authority/config.go @@ -55,7 +55,6 @@ type Config struct { Address string `json:"address"` DNSNames []string `json:"dnsNames"` KMS *kms.Options `json:"kms,omitempty"` - CAS *cas.Options `json:"cas,omitempty"` SSH *SSHConfig `json:"ssh,omitempty"` Logger json.RawMessage `json:"logger,omitempty"` DB *db.Config `json:"db,omitempty"` @@ -78,8 +77,11 @@ type ASN1DN struct { CommonName string `json:"commonName,omitempty" step:"commonName"` } -// AuthConfig represents the configuration options for the authority. +// AuthConfig represents the configuration options for the authority. An +// underlaying registration authority can also be configured using the +// cas.Options. type AuthConfig struct { + *cas.Options Provisioners provisioner.List `json:"provisioners"` Template *ASN1DN `json:"template,omitempty"` Claims *provisioner.Claims `json:"claims,omitempty"` @@ -185,8 +187,11 @@ func (c *Config) Validate() error { return errors.New("dnsNames cannot be empty") } - // The default CAS requires root, crt and key. - if c.CAS.Is(cas.SoftCAS) { + // Options holds the RA/CAS configuration. + ra := c.AuthorityConfig.Options + + // The default RA/CAS requires root, crt and key. + if ra.Is(cas.SoftCAS) { switch { case c.Root.HasEmpties(): return errors.New("root cannot be empty") @@ -225,8 +230,8 @@ func (c *Config) Validate() error { return err } - // Validate CAS options, nil is ok. - if err := c.CAS.Validate(); err != nil { + // Validate RA/CAS options, nil is ok. + if err := ra.Validate(); err != nil { return err } From d46990d4c4dec10c940ee0fc64eee258f3d7bb68 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 19 Oct 2020 18:42:03 -0700 Subject: [PATCH 2/6] Add support for step ca init with a RA. --- pki/pki.go | 73 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/pki/pki.go b/pki/pki.go index 50ee4e56..fa1954ae 100644 --- a/pki/pki.go +++ b/pki/pki.go @@ -1,6 +1,7 @@ package pki import ( + "context" "crypto" "crypto/sha256" "crypto/x509" @@ -20,6 +21,8 @@ import ( "github.com/smallstep/certificates/authority" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/ca" + "github.com/smallstep/certificates/cas" + "github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/db" "github.com/smallstep/cli/config" "github.com/smallstep/cli/errs" @@ -157,6 +160,7 @@ type PKI struct { dnsNames []string caURL string enableSSH bool + authorityOptions *apiv1.Options } // New creates a new PKI configuration. @@ -233,6 +237,12 @@ func (p *PKI) GetRootFingerprint() string { return p.rootFingerprint } +// SetAuthorityOptions sets the authority options object, these options are used +// to configure a registration authority. +func (p *PKI) SetAuthorityOptions(opts *apiv1.Options) { + p.authorityOptions = opts +} + // SetProvisioner sets the provisioner name of the OTT keys. func (p *PKI) SetProvisioner(s string) { p.provisioner = s @@ -307,9 +317,11 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{ return err } - _, err := pemutil.Serialize(rootKey, pemutil.WithPassword(pass), pemutil.ToFile(p.rootKey, 0600)) - if err != nil { - return err + if rootKey != nil { + _, err := pemutil.Serialize(rootKey, pemutil.WithPassword(pass), pemutil.ToFile(p.rootKey, 0600)) + if err != nil { + return err + } } sum := sha256.Sum256(rootCrt.Raw) @@ -318,6 +330,37 @@ func (p *PKI) WriteRootCertificate(rootCrt *x509.Certificate, rootKey interface{ return nil } +// GetCertificateAuthority attempts to load the certificate authority from the +// RA. +func (p *PKI) GetCertificateAuthority() error { + ca, err := cas.New(context.Background(), *p.authorityOptions) + if err != nil { + return err + } + + srv, ok := ca.(apiv1.CertificateAuthorityGetter) + if !ok { + return nil + } + + resp, err := srv.GetCertificateAuthority(&apiv1.GetCertificateAuthorityRequest{ + Name: p.authorityOptions.CertificateAuthority, + }) + if err != nil { + return err + } + + if err := p.WriteRootCertificate(resp.RootCertificate, nil, nil); err != nil { + return err + } + + // Issuer is in the RA + p.intermediate = "" + p.intermediateKey = "" + + return nil +} + // GenerateIntermediateCertificate generates an intermediate certificate with // the given name. func (p *PKI) GenerateIntermediateCertificate(name string, rootCrt *x509.Certificate, rootKey interface{}, pass []byte) error { @@ -414,11 +457,18 @@ func (p *PKI) TellPKI() { func (p *PKI) tellPKI() { ui.Println() - ui.PrintSelected("Root certificate", p.root) - ui.PrintSelected("Root private key", p.rootKey) - ui.PrintSelected("Root fingerprint", p.rootFingerprint) - ui.PrintSelected("Intermediate certificate", p.intermediate) - ui.PrintSelected("Intermediate private key", p.intermediateKey) + if p.authorityOptions == nil || p.authorityOptions.Is(apiv1.SoftCAS) { + ui.PrintSelected("Root certificate", p.root) + ui.PrintSelected("Root private key", p.rootKey) + ui.PrintSelected("Root fingerprint", p.rootFingerprint) + ui.PrintSelected("Intermediate certificate", p.intermediate) + ui.PrintSelected("Intermediate private key", p.intermediateKey) + } else if p.rootFingerprint != "" { + ui.PrintSelected("Root certificate", p.root) + ui.PrintSelected("Root fingerprint", p.rootFingerprint) + } else { + ui.Printf(`{{ "%s" | red }} {{ "Root certificate:" | bold }} failed to retrieve it from RA`+"\n", ui.IconBad) + } if p.enableSSH { ui.PrintSelected("SSH user root certificate", p.sshUserPubKey) ui.PrintSelected("SSH user root private key", p.sshUserKey) @@ -485,6 +535,7 @@ func (p *PKI) GenerateConfig(opt ...Option) (*authority.Config, error) { DataSource: GetDBPath(), }, AuthorityConfig: &authority.AuthConfig{ + Options: p.authorityOptions, DisableIssuedAtCheck: false, Provisioners: provisioner.List{prov}, }, @@ -591,7 +642,11 @@ func (p *PKI) Save(opt ...Option) error { ui.PrintSelected("Default configuration", p.defaults) ui.PrintSelected("Certificate Authority configuration", p.config) ui.Println() - ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.") + if p.authorityOptions == nil || p.authorityOptions.Is(apiv1.SoftCAS) { + ui.Println("Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.") + } else { + ui.Println("Your registration authority is ready to go. To generate certificates for individual services see 'step help ca'.") + } p.askFeedback() From 2ec0c24e988e53af6310f3a810cbbef9e02c6de0 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 19 Oct 2020 18:43:11 -0700 Subject: [PATCH 3/6] Update docs for RA. --- docs/cas.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/cas.md b/docs/cas.md index b2c6782e..7aefdcb1 100644 --- a/docs/cas.md +++ b/docs/cas.md @@ -1,14 +1,15 @@ -# Certificate Management Services +# Registration Authorities -This document describes how to use a certificate management service or CAS to -sign X.509 certificates requests. +This document describes how to use an external registration authority (RA), aka +certificate authority service (CAS) to sign X.509 certificates requests. A CAS is a system that implements an API to sign certificate requests, the difference between CAS and KMS is that the latter can sign any data, while CAS is intended to sign only X.509 certificates. `step-ca` defines an interface that can be implemented to support other -services, currently only CloudCAS and the default SoftCAS are implemented. +registration authorities, currently only CloudCAS and the default SoftCAS are +implemented. The `CertificateAuthorityService` is defined in the package `github.com/smallstep/certificates/cas/apiv1` and it is: @@ -123,15 +124,15 @@ or using `gcloud` CLI: --reusable-config "subordinate-server-tls-pathlen-0" ``` -Not it's time to enable it in `step-ca` adding the new property `"cas"` must be added -to the `ca.json`. +Now it's time to enable it in `step-ca` by adding some new files in the +`"authority"` section of the `ca.json`. ```json { - "cas": { + "authority": { "type": "cloudCAS", "credentialsFile": "/path/to/credentials.json", - "certificateAuthority": "projects//locations//certificateAuthorities/" + "certificateAuthority": "projects//locations//certificateAuthorities/", } } ``` @@ -161,12 +162,10 @@ need to configure `"root"`, and because the intermediate is in Google Cloud, "type": "badger", "dataSource": "/home/jane/.step/db", }, - "cas": { + "authority": { "type": "cloudCAS", "credentialsFile": "/home/jane/.step/credentials.json", - "certificateAuthority": "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/prod-intermediate-ca" - }, - "authority": { + "certificateAuthority": "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/prod-intermediate-ca", "provisioners": [ { "type": "JWK", From 9f21813dd6cbd0b64cf170d7eba68592f3af51a6 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 19 Oct 2020 18:44:27 -0700 Subject: [PATCH 4/6] Rename option. --- cas/apiv1/options.go | 2 +- cas/apiv1/options_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cas/apiv1/options.go b/cas/apiv1/options.go index fc674ab0..3ee1434c 100644 --- a/cas/apiv1/options.go +++ b/cas/apiv1/options.go @@ -18,7 +18,7 @@ type Options struct { // CertificateAuthority reference. In CloudCAS the format is // `projects/*/locations/*/certificateAuthorities/*`. - Certificateauthority string `json:"certificateAuthority"` + CertificateAuthority string `json:"certificateAuthority"` // Issuer and signer are the issuer certificate and signer used in SoftCAS. // They are configured in ca.json crt and key properties. diff --git a/cas/apiv1/options_test.go b/cas/apiv1/options_test.go index 91da8372..6d601366 100644 --- a/cas/apiv1/options_test.go +++ b/cas/apiv1/options_test.go @@ -42,7 +42,7 @@ func TestOptions_Validate(t *testing.T) { type fields struct { Type string CredentialsFile string - Certificateauthority string + CertificateAuthority string Issuer *x509.Certificate Signer crypto.Signer } @@ -69,7 +69,7 @@ func TestOptions_Validate(t *testing.T) { o := &Options{ Type: tt.fields.Type, CredentialsFile: tt.fields.CredentialsFile, - Certificateauthority: tt.fields.Certificateauthority, + CertificateAuthority: tt.fields.CertificateAuthority, Issuer: tt.fields.Issuer, Signer: tt.fields.Signer, } @@ -86,7 +86,7 @@ func TestOptions_Is(t *testing.T) { type fields struct { Type string CredentialsFile string - Certificateauthority string + CertificateAuthority string Issuer *x509.Certificate Signer crypto.Signer } @@ -119,7 +119,7 @@ func TestOptions_Is(t *testing.T) { o := &Options{ Type: tt.fields.Type, CredentialsFile: tt.fields.CredentialsFile, - Certificateauthority: tt.fields.Certificateauthority, + CertificateAuthority: tt.fields.CertificateAuthority, Issuer: tt.fields.Issuer, Signer: tt.fields.Signer, } From 2654231c499b8cb6047b67a01b489c43f9a5ecc1 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 19 Oct 2020 18:47:36 -0700 Subject: [PATCH 5/6] Update option property. --- cas/cloudcas/cloudcas.go | 4 ++-- cas/cloudcas/cloudcas_test.go | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cas/cloudcas/cloudcas.go b/cas/cloudcas/cloudcas.go index 4866a797..8820304a 100644 --- a/cas/cloudcas/cloudcas.go +++ b/cas/cloudcas/cloudcas.go @@ -70,7 +70,7 @@ 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.Certificateauthority == "" { + if opts.CertificateAuthority == "" { return nil, errors.New("cloudCAS 'certificateAuthority' cannot be empty") } @@ -81,7 +81,7 @@ func New(ctx context.Context, opts apiv1.Options) (*CloudCAS, error) { return &CloudCAS{ client: client, - certificateAuthority: opts.Certificateauthority, + certificateAuthority: opts.CertificateAuthority, }, nil } diff --git a/cas/cloudcas/cloudcas_test.go b/cas/cloudcas/cloudcas_test.go index f2b708f5..38446325 100644 --- a/cas/cloudcas/cloudcas_test.go +++ b/cas/cloudcas/cloudcas_test.go @@ -174,20 +174,20 @@ func TestNew(t *testing.T) { wantErr bool }{ {"ok", args{context.Background(), apiv1.Options{ - Certificateauthority: testAuthorityName, + CertificateAuthority: testAuthorityName, }}, &CloudCAS{ client: &testClient{}, certificateAuthority: testAuthorityName, }, false}, {"ok with credentials", args{context.Background(), apiv1.Options{ - Certificateauthority: testAuthorityName, CredentialsFile: "testdata/credentials.json", + CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/credentials.json", }}, &CloudCAS{ client: &testClient{credentialsFile: "testdata/credentials.json"}, certificateAuthority: testAuthorityName, }, false}, {"fail certificate authority", args{context.Background(), apiv1.Options{}}, nil, true}, {"fail with credentials", args{context.Background(), apiv1.Options{ - Certificateauthority: testAuthorityName, CredentialsFile: "testdata/error.json", + CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/error.json", }}, nil, true}, } for _, tt := range tests { @@ -225,7 +225,7 @@ func TestNew_register(t *testing.T) { } got, err := newFn(context.Background(), apiv1.Options{ - Certificateauthority: testAuthorityName, CredentialsFile: "testdata/credentials.json", + CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/credentials.json", }) if err != nil { t.Errorf("New() error = %v", err) @@ -255,10 +255,10 @@ func TestNew_real(t *testing.T) { args args wantErr bool }{ - {"fail default credentials", true, args{context.Background(), apiv1.Options{Certificateauthority: testAuthorityName}}, true}, + {"fail default credentials", true, args{context.Background(), apiv1.Options{CertificateAuthority: testAuthorityName}}, true}, {"fail certificate authority", false, args{context.Background(), apiv1.Options{}}, true}, {"fail with credentials", false, args{context.Background(), apiv1.Options{ - Certificateauthority: testAuthorityName, CredentialsFile: "testdata/missing.json", + CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/missing.json", }}, true}, } for _, tt := range tests { From 341dc1c3ea9a84c886fec7ff03d60f0ae288db95 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Mon, 19 Oct 2020 18:55:30 -0700 Subject: [PATCH 6/6] Remove merge data. --- docs/cas.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/cas.md b/docs/cas.md index 2804d4eb..7aefdcb1 100644 --- a/docs/cas.md +++ b/docs/cas.md @@ -1,14 +1,7 @@ -<<<<<<< HEAD # Registration Authorities This document describes how to use an external registration authority (RA), aka certificate authority service (CAS) to sign X.509 certificates requests. -======= -# Certificate Authority Services - -This document describes how to use a certificate authority service or CAS to -sign X.509 certificates requests. ->>>>>>> master A CAS is a system that implements an API to sign certificate requests, the difference between CAS and KMS is that the latter can sign any data, while CAS