Create a way to export ca configurations.
This commit is contained in:
parent
d0c1530f89
commit
dc1ec18b52
4 changed files with 445 additions and 1 deletions
39
authority/export.go
Normal file
39
authority/export.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package authority
|
||||
|
||||
import "go.step.sm/linkedca"
|
||||
|
||||
func (a *Authority) Export() (*linkedca.Configuration, error) {
|
||||
var admins []*linkedca.Admin
|
||||
var provisioners []*linkedca.Provisioner
|
||||
|
||||
for {
|
||||
list, cursor := a.admins.Find("", 100)
|
||||
admins = append(admins, list...)
|
||||
if cursor == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
list, cursor := a.provisioners.Find("", 100)
|
||||
for _, p := range list {
|
||||
lp, err := ProvisionerToLinkedca(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provisioners = append(provisioners, lp)
|
||||
}
|
||||
if cursor == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Global claims for all provisioners.
|
||||
claims := claimsToLinkedca(a.config.AuthorityConfig.Claims)
|
||||
|
||||
return &linkedca.Configuration{
|
||||
Admins: admins,
|
||||
Provisioners: provisioners,
|
||||
Claims: claims,
|
||||
}, nil
|
||||
}
|
|
@ -4,12 +4,16 @@ import (
|
|||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority/admin"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/smallstep/certificates/authority/provisioner"
|
||||
"github.com/smallstep/certificates/errs"
|
||||
step "go.step.sm/cli-utils/config"
|
||||
"go.step.sm/crypto/jose"
|
||||
"go.step.sm/linkedca"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
|
@ -398,6 +402,13 @@ func durationsToCertificates(d *linkedca.Durations) (min, max, def *provisioner.
|
|||
return
|
||||
}
|
||||
|
||||
func durationsToLinkedca(d *provisioner.Duration) string {
|
||||
if d == nil {
|
||||
return ""
|
||||
}
|
||||
return d.Duration.String()
|
||||
}
|
||||
|
||||
// claimsToCertificates converts the linkedca provisioner claims type to the
|
||||
// certifictes claims type.
|
||||
func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) {
|
||||
|
@ -438,6 +449,109 @@ func claimsToCertificates(c *linkedca.Claims) (*provisioner.Claims, error) {
|
|||
return pc, nil
|
||||
}
|
||||
|
||||
func claimsToLinkedca(c *provisioner.Claims) *linkedca.Claims {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
disableRenewal := config.DefaultDisableRenewal
|
||||
if c.DisableRenewal != nil {
|
||||
disableRenewal = *c.DisableRenewal
|
||||
}
|
||||
|
||||
lc := &linkedca.Claims{
|
||||
DisableRenewal: disableRenewal,
|
||||
}
|
||||
|
||||
if c.DefaultTLSDur != nil || c.MinTLSDur != nil || c.MaxTLSDur != nil {
|
||||
lc.X509 = &linkedca.X509Claims{
|
||||
Enabled: true,
|
||||
Durations: &linkedca.Durations{
|
||||
Default: durationsToLinkedca(c.DefaultTLSDur),
|
||||
Min: durationsToLinkedca(c.MinTLSDur),
|
||||
Max: durationsToLinkedca(c.MaxTLSDur),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if c.EnableSSHCA != nil && *c.EnableSSHCA {
|
||||
lc.Ssh = &linkedca.SSHClaims{
|
||||
Enabled: true,
|
||||
}
|
||||
if c.DefaultUserSSHDur != nil || c.MinUserSSHDur != nil || c.MaxUserSSHDur != nil {
|
||||
lc.Ssh.UserDurations = &linkedca.Durations{
|
||||
Default: durationsToLinkedca(c.DefaultUserSSHDur),
|
||||
Min: durationsToLinkedca(c.MinUserSSHDur),
|
||||
Max: durationsToLinkedca(c.MaxUserSSHDur),
|
||||
}
|
||||
}
|
||||
if c.DefaultHostSSHDur != nil || c.MinHostSSHDur != nil || c.MaxHostSSHDur != nil {
|
||||
lc.Ssh.HostDurations = &linkedca.Durations{
|
||||
Default: durationsToLinkedca(c.DefaultHostSSHDur),
|
||||
Min: durationsToLinkedca(c.MinHostSSHDur),
|
||||
Max: durationsToLinkedca(c.MaxHostSSHDur),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lc
|
||||
}
|
||||
|
||||
func provisionerOptionsToLinkedca(p *provisioner.Options) (*linkedca.Template, *linkedca.Template, error) {
|
||||
var err error
|
||||
var x509Template, sshTemplate *linkedca.Template
|
||||
|
||||
if p == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if p.X509 != nil && p.X509.HasTemplate() {
|
||||
x509Template = &linkedca.Template{
|
||||
Template: nil,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
if p.X509.Template != "" {
|
||||
x509Template.Template = []byte(p.SSH.Template)
|
||||
} else if p.X509.TemplateFile != "" {
|
||||
filename := step.StepAbs(p.X509.TemplateFile)
|
||||
if x509Template.Template, err = ioutil.ReadFile(filename); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "error reading x509 template")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.SSH != nil && p.SSH.HasTemplate() {
|
||||
sshTemplate = &linkedca.Template{
|
||||
Template: nil,
|
||||
Data: nil,
|
||||
}
|
||||
|
||||
if p.SSH.Template != "" {
|
||||
sshTemplate.Template = []byte(p.SSH.Template)
|
||||
} else if p.SSH.TemplateFile != "" {
|
||||
filename := step.StepAbs(p.SSH.TemplateFile)
|
||||
if sshTemplate.Template, err = ioutil.ReadFile(filename); err != nil {
|
||||
return nil, nil, errors.Wrap(err, "error reading ssh template")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return x509Template, sshTemplate, nil
|
||||
}
|
||||
|
||||
func provisionerPEMToLinkedca(b []byte) [][]byte {
|
||||
var roots [][]byte
|
||||
var block *pem.Block
|
||||
for {
|
||||
if block, b = pem.Decode(b); block == nil {
|
||||
break
|
||||
}
|
||||
roots = append(roots, pem.EncodeToMemory(block))
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
// ProvisionerToCertificates converts the linkedca provisioner type to the certificates provisioner
|
||||
// interface.
|
||||
func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface, error) {
|
||||
|
@ -588,6 +702,223 @@ func ProvisionerToCertificates(p *linkedca.Provisioner) (provisioner.Interface,
|
|||
}
|
||||
}
|
||||
|
||||
// ProvisionerToLinkedca converts a provisioner.Interface to a
|
||||
// linkedca.Provisioner type.
|
||||
func ProvisionerToLinkedca(p provisioner.Interface) (*linkedca.Provisioner, error) {
|
||||
switch p := p.(type) {
|
||||
case *provisioner.JWK:
|
||||
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
publicKey, err := json.Marshal(p.Key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error marshaling key")
|
||||
}
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_JWK,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_JWK{
|
||||
JWK: &linkedca.JWKProvisioner{
|
||||
PublicKey: publicKey,
|
||||
EncryptedPrivateKey: []byte(p.EncryptedKey),
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
X509Template: x509Template,
|
||||
SshTemplate: sshTemplate,
|
||||
}, nil
|
||||
case *provisioner.OIDC:
|
||||
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_OIDC,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_OIDC{
|
||||
OIDC: &linkedca.OIDCProvisioner{
|
||||
ClientId: p.ClientID,
|
||||
ClientSecret: p.ClientSecret,
|
||||
ConfigurationEndpoint: p.ConfigurationEndpoint,
|
||||
Admins: p.Admins,
|
||||
Domains: p.Domains,
|
||||
Groups: p.Groups,
|
||||
ListenAddress: p.ListenAddress,
|
||||
TenantId: p.TenantID,
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
X509Template: x509Template,
|
||||
SshTemplate: sshTemplate,
|
||||
}, nil
|
||||
case *provisioner.GCP:
|
||||
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_GCP,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_GCP{
|
||||
GCP: &linkedca.GCPProvisioner{
|
||||
ServiceAccounts: p.ServiceAccounts,
|
||||
ProjectIds: p.ProjectIDs,
|
||||
DisableCustomSans: p.DisableCustomSANs,
|
||||
DisableTrustOnFirstUse: p.DisableTrustOnFirstUse,
|
||||
InstanceAge: p.InstanceAge.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
X509Template: x509Template,
|
||||
SshTemplate: sshTemplate,
|
||||
}, nil
|
||||
case *provisioner.AWS:
|
||||
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_AWS,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_AWS{
|
||||
AWS: &linkedca.AWSProvisioner{
|
||||
Accounts: p.Accounts,
|
||||
DisableCustomSans: p.DisableCustomSANs,
|
||||
DisableTrustOnFirstUse: p.DisableTrustOnFirstUse,
|
||||
InstanceAge: p.InstanceAge.String(),
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
X509Template: x509Template,
|
||||
SshTemplate: sshTemplate,
|
||||
}, nil
|
||||
case *provisioner.Azure:
|
||||
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_AZURE,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_Azure{
|
||||
Azure: &linkedca.AzureProvisioner{
|
||||
TenantId: p.TenantID,
|
||||
ResourceGroups: p.ResourceGroups,
|
||||
Audience: p.Audience,
|
||||
DisableCustomSans: p.DisableCustomSANs,
|
||||
DisableTrustOnFirstUse: p.DisableTrustOnFirstUse,
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
X509Template: x509Template,
|
||||
SshTemplate: sshTemplate,
|
||||
}, nil
|
||||
case *provisioner.ACME:
|
||||
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_ACME,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_ACME{
|
||||
ACME: &linkedca.ACMEProvisioner{
|
||||
ForceCn: p.ForceCN,
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
X509Template: x509Template,
|
||||
SshTemplate: sshTemplate,
|
||||
}, nil
|
||||
case *provisioner.X5C:
|
||||
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_X5C,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_X5C{
|
||||
X5C: &linkedca.X5CProvisioner{
|
||||
Roots: provisionerPEMToLinkedca(p.Roots),
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
X509Template: x509Template,
|
||||
SshTemplate: sshTemplate,
|
||||
}, nil
|
||||
case *provisioner.K8sSA:
|
||||
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_K8SSA,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_K8SSA{
|
||||
K8SSA: &linkedca.K8SSAProvisioner{
|
||||
PublicKeys: provisionerPEMToLinkedca(p.PubKeys),
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
X509Template: x509Template,
|
||||
SshTemplate: sshTemplate,
|
||||
}, nil
|
||||
case *provisioner.SSHPOP:
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_SSHPOP,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_SSHPOP{
|
||||
SSHPOP: &linkedca.SSHPOPProvisioner{},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
}, nil
|
||||
case *provisioner.SCEP:
|
||||
x509Template, sshTemplate, err := provisionerOptionsToLinkedca(p.Options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &linkedca.Provisioner{
|
||||
Type: linkedca.Provisioner_SCEP,
|
||||
Name: p.GetName(),
|
||||
Details: &linkedca.ProvisionerDetails{
|
||||
Data: &linkedca.ProvisionerDetails_SCEP{
|
||||
SCEP: &linkedca.SCEPProvisioner{
|
||||
ForceCn: p.ForceCN,
|
||||
Challenge: p.GetChallengePassword(),
|
||||
Capabilities: p.Capabilities,
|
||||
MinimumPublicKeyLength: int32(p.MinimumPublicKeyLength),
|
||||
},
|
||||
},
|
||||
},
|
||||
Claims: claimsToLinkedca(p.Claims),
|
||||
X509Template: x509Template,
|
||||
SshTemplate: sshTemplate,
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("provisioner %s not implemented", p.GetType())
|
||||
}
|
||||
}
|
||||
|
||||
func parseInstanceAge(age string) (provisioner.Duration, error) {
|
||||
var instanceAge provisioner.Duration
|
||||
if age != "" {
|
||||
|
|
74
commands/export.go
Normal file
74
commands/export.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smallstep/certificates/authority"
|
||||
"github.com/smallstep/certificates/authority/config"
|
||||
"github.com/urfave/cli"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
"go.step.sm/cli-utils/command"
|
||||
"go.step.sm/cli-utils/errs"
|
||||
)
|
||||
|
||||
func init() {
|
||||
command.Register(cli.Command{
|
||||
Name: "export",
|
||||
Usage: "export the current configuration of step-ca",
|
||||
UsageText: "**step-ca export** <config>",
|
||||
Action: exportAction,
|
||||
Description: `**step-ca export** exports the current configuration of step-ca.
|
||||
|
||||
## POSITIONAL ARGUMENTS
|
||||
|
||||
<config>
|
||||
: The ca.json that contains the step-ca configuration.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Export the current configuration:
|
||||
'''
|
||||
$ step-ca export $(step path)/config/ca.json
|
||||
'''`,
|
||||
})
|
||||
}
|
||||
|
||||
func exportAction(ctx *cli.Context) error {
|
||||
if err := errs.NumberOfArguments(ctx, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configFile := ctx.Args().Get(0)
|
||||
|
||||
config, err := config.LoadConfiguration(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
auth, err := authority.New(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
export, err := auth.Export()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := protojson.Marshal(export)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error marshaling export")
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := json.Indent(&buf, b, "", "\t"); err != nil {
|
||||
return errors.Wrap(err, "error indenting export")
|
||||
}
|
||||
|
||||
fmt.Println(buf.String())
|
||||
return nil
|
||||
}
|
2
go.mod
2
go.mod
|
@ -42,6 +42,6 @@ require (
|
|||
// replace github.com/smallstep/nosql => ../nosql
|
||||
// replace go.step.sm/crypto => ../crypto
|
||||
// replace go.step.sm/cli-utils => ../cli-utils
|
||||
// replace go.step.sm/linkedca => ../linkedca
|
||||
replace go.step.sm/linkedca => ../linkedca
|
||||
|
||||
replace go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 => github.com/omorsi/pkcs7 v0.0.0-20210217142924-a7b80a2a8568
|
||||
|
|
Loading…
Reference in a new issue