mirror of
https://github.com/smallstep/certificates.git
synced 2024-11-17 15:29:21 +00:00
369b8f81c3
Fixes #975
285 lines
7.9 KiB
Go
285 lines
7.9 KiB
Go
package authority
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/certificates/authority/provisioner"
|
|
"go.step.sm/cli-utils/step"
|
|
"go.step.sm/linkedca"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
)
|
|
|
|
// Export creates a linkedca configuration form the current ca.json and loaded
|
|
// authorities.
|
|
//
|
|
// Note that export will not export neither the pki password nor the certificate
|
|
// issuer password.
|
|
func (a *Authority) Export() (c *linkedca.Configuration, err error) {
|
|
// Recover from panics
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = r.(error)
|
|
}
|
|
}()
|
|
|
|
files := make(map[string][]byte)
|
|
|
|
// The exported configuration should not include the password in it.
|
|
c = &linkedca.Configuration{
|
|
Version: "1.0",
|
|
Root: mustReadFilesOrURIs(a.config.Root, files),
|
|
FederatedRoots: mustReadFilesOrURIs(a.config.FederatedRoots, files),
|
|
Intermediate: mustReadFileOrURI(a.config.IntermediateCert, files),
|
|
IntermediateKey: mustReadFileOrURI(a.config.IntermediateKey, files),
|
|
Address: a.config.Address,
|
|
InsecureAddress: a.config.InsecureAddress,
|
|
DnsNames: a.config.DNSNames,
|
|
Db: mustMarshalToStruct(a.config.DB),
|
|
Logger: mustMarshalToStruct(a.config.Logger),
|
|
Monitoring: mustMarshalToStruct(a.config.Monitoring),
|
|
Authority: &linkedca.Authority{
|
|
Id: a.config.AuthorityConfig.AuthorityID,
|
|
EnableAdmin: a.config.AuthorityConfig.EnableAdmin,
|
|
DisableIssuedAtCheck: a.config.AuthorityConfig.DisableIssuedAtCheck,
|
|
Backdate: mustDuration(a.config.AuthorityConfig.Backdate),
|
|
DeploymentType: a.config.AuthorityConfig.DeploymentType,
|
|
},
|
|
Files: files,
|
|
}
|
|
|
|
// SSH
|
|
if v := a.config.SSH; v != nil {
|
|
c.Ssh = &linkedca.SSH{
|
|
HostKey: mustReadFileOrURI(v.HostKey, files),
|
|
UserKey: mustReadFileOrURI(v.UserKey, files),
|
|
AddUserPrincipal: v.AddUserPrincipal,
|
|
AddUserCommand: v.AddUserCommand,
|
|
}
|
|
for _, k := range v.Keys {
|
|
typ, ok := linkedca.SSHPublicKey_Type_value[strings.ToUpper(k.Type)]
|
|
if !ok {
|
|
return nil, errors.Errorf("unsupported ssh key type %s", k.Type)
|
|
}
|
|
c.Ssh.Keys = append(c.Ssh.Keys, &linkedca.SSHPublicKey{
|
|
Type: linkedca.SSHPublicKey_Type(typ),
|
|
Federated: k.Federated,
|
|
Key: mustMarshalToStruct(k),
|
|
})
|
|
}
|
|
if b := v.Bastion; b != nil {
|
|
c.Ssh.Bastion = &linkedca.Bastion{
|
|
Hostname: b.Hostname,
|
|
User: b.User,
|
|
Port: b.Port,
|
|
Command: b.Command,
|
|
Flags: b.Flags,
|
|
}
|
|
}
|
|
}
|
|
|
|
// KMS
|
|
if v := a.config.KMS; v != nil {
|
|
var typ int32
|
|
var ok bool
|
|
if v.Type == "" {
|
|
typ = int32(linkedca.KMS_SOFTKMS)
|
|
} else {
|
|
typ, ok = linkedca.KMS_Type_value[strings.ToUpper(string(v.Type))]
|
|
if !ok {
|
|
return nil, errors.Errorf("unsupported kms type %s", v.Type)
|
|
}
|
|
}
|
|
c.Kms = &linkedca.KMS{
|
|
Type: linkedca.KMS_Type(typ),
|
|
CredentialsFile: v.CredentialsFile,
|
|
Uri: v.URI,
|
|
Pin: v.Pin,
|
|
ManagementKey: v.ManagementKey,
|
|
Region: v.Region,
|
|
Profile: v.Profile,
|
|
}
|
|
}
|
|
|
|
// Authority
|
|
// cas options
|
|
if v := a.config.AuthorityConfig.Options; v != nil {
|
|
c.Authority.Type = 0
|
|
c.Authority.CertificateAuthority = v.CertificateAuthority
|
|
c.Authority.CertificateAuthorityFingerprint = v.CertificateAuthorityFingerprint
|
|
c.Authority.CredentialsFile = v.CredentialsFile
|
|
if iss := v.CertificateIssuer; iss != nil {
|
|
typ, ok := linkedca.CertificateIssuer_Type_value[strings.ToUpper(iss.Type)]
|
|
if !ok {
|
|
return nil, errors.Errorf("unknown certificate issuer type %s", iss.Type)
|
|
}
|
|
// The exported certificate issuer should not include the password.
|
|
c.Authority.CertificateIssuer = &linkedca.CertificateIssuer{
|
|
Type: linkedca.CertificateIssuer_Type(typ),
|
|
Provisioner: iss.Provisioner,
|
|
Certificate: mustReadFileOrURI(iss.Certificate, files),
|
|
Key: mustReadFileOrURI(iss.Key, files),
|
|
}
|
|
}
|
|
}
|
|
// admins
|
|
for {
|
|
list, cursor := a.admins.Find("", 100)
|
|
c.Authority.Admins = append(c.Authority.Admins, list...)
|
|
if cursor == "" {
|
|
break
|
|
}
|
|
}
|
|
// provisioners
|
|
for {
|
|
list, cursor := a.provisioners.Find("", 100)
|
|
for _, p := range list {
|
|
lp, err := ProvisionerToLinkedca(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.Authority.Provisioners = append(c.Authority.Provisioners, lp)
|
|
}
|
|
if cursor == "" {
|
|
break
|
|
}
|
|
}
|
|
// global claims
|
|
c.Authority.Claims = claimsToLinkedca(a.config.AuthorityConfig.Claims)
|
|
// Distinguished names template
|
|
if v := a.config.AuthorityConfig.Template; v != nil {
|
|
c.Authority.Template = &linkedca.DistinguishedName{
|
|
Country: v.Country,
|
|
Organization: v.Organization,
|
|
OrganizationalUnit: v.OrganizationalUnit,
|
|
Locality: v.Locality,
|
|
Province: v.Province,
|
|
StreetAddress: v.StreetAddress,
|
|
SerialNumber: v.SerialNumber,
|
|
CommonName: v.CommonName,
|
|
}
|
|
}
|
|
|
|
// TLS
|
|
if v := a.config.TLS; v != nil {
|
|
c.Tls = &linkedca.TLS{
|
|
MinVersion: v.MinVersion.String(),
|
|
MaxVersion: v.MaxVersion.String(),
|
|
Renegotiation: v.Renegotiation,
|
|
}
|
|
for _, cs := range v.CipherSuites.Value() {
|
|
c.Tls.CipherSuites = append(c.Tls.CipherSuites, linkedca.TLS_CiperSuite(cs))
|
|
}
|
|
}
|
|
|
|
// Templates
|
|
if v := a.config.Templates; v != nil {
|
|
c.Templates = &linkedca.ConfigTemplates{
|
|
Ssh: &linkedca.SSHConfigTemplate{},
|
|
Data: mustMarshalToStruct(v.Data),
|
|
}
|
|
// Remove automatically loaded vars
|
|
if c.Templates.Data != nil && c.Templates.Data.Fields != nil {
|
|
delete(c.Templates.Data.Fields, "Step")
|
|
}
|
|
for _, t := range v.SSH.Host {
|
|
typ, ok := linkedca.ConfigTemplate_Type_value[strings.ToUpper(string(t.Type))]
|
|
if !ok {
|
|
return nil, errors.Errorf("unsupported template type %s", t.Type)
|
|
}
|
|
c.Templates.Ssh.Hosts = append(c.Templates.Ssh.Hosts, &linkedca.ConfigTemplate{
|
|
Type: linkedca.ConfigTemplate_Type(typ),
|
|
Name: t.Name,
|
|
Template: mustReadFileOrURI(t.TemplatePath, files),
|
|
Path: t.Path,
|
|
Comment: t.Comment,
|
|
Requires: t.RequiredData,
|
|
Content: t.Content,
|
|
})
|
|
}
|
|
for _, t := range v.SSH.User {
|
|
typ, ok := linkedca.ConfigTemplate_Type_value[strings.ToUpper(string(t.Type))]
|
|
if !ok {
|
|
return nil, errors.Errorf("unsupported template type %s", t.Type)
|
|
}
|
|
c.Templates.Ssh.Users = append(c.Templates.Ssh.Users, &linkedca.ConfigTemplate{
|
|
Type: linkedca.ConfigTemplate_Type(typ),
|
|
Name: t.Name,
|
|
Template: mustReadFileOrURI(t.TemplatePath, files),
|
|
Path: t.Path,
|
|
Comment: t.Comment,
|
|
Requires: t.RequiredData,
|
|
Content: t.Content,
|
|
})
|
|
}
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func mustDuration(d *provisioner.Duration) string {
|
|
if d == nil || d.Duration == 0 {
|
|
return ""
|
|
}
|
|
return d.String()
|
|
}
|
|
|
|
func mustMarshalToStruct(v interface{}) *structpb.Struct {
|
|
b, err := json.Marshal(v)
|
|
if err != nil {
|
|
panic(errors.Wrapf(err, "error marshaling %T", v))
|
|
}
|
|
var r *structpb.Struct
|
|
if err := json.Unmarshal(b, &r); err != nil {
|
|
panic(errors.Wrapf(err, "error unmarshaling %T", v))
|
|
}
|
|
return r
|
|
}
|
|
|
|
func mustReadFileOrURI(fn string, m map[string][]byte) string {
|
|
if fn == "" {
|
|
return ""
|
|
}
|
|
|
|
stepPath := filepath.ToSlash(step.Path())
|
|
if !strings.HasSuffix(stepPath, "/") {
|
|
stepPath += "/"
|
|
}
|
|
|
|
fn = strings.TrimPrefix(filepath.ToSlash(fn), stepPath)
|
|
|
|
ok, err := isFilename(fn)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if ok {
|
|
b, err := os.ReadFile(step.Abs(fn))
|
|
if err != nil {
|
|
panic(errors.Wrapf(err, "error reading %s", fn))
|
|
}
|
|
m[fn] = b
|
|
return fn
|
|
}
|
|
return fn
|
|
}
|
|
|
|
func mustReadFilesOrURIs(fns []string, m map[string][]byte) []string {
|
|
var result []string
|
|
for _, fn := range fns {
|
|
result = append(result, mustReadFileOrURI(fn, m))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func isFilename(fn string) (bool, error) {
|
|
u, err := url.Parse(fn)
|
|
if err != nil {
|
|
return false, errors.Wrapf(err, "error parsing %s", fn)
|
|
}
|
|
return u.Scheme == "" || u.Scheme == "file", nil
|
|
}
|