package pki import ( "fmt" "io" "text/template" "github.com/pkg/errors" "github.com/smallstep/certificates/authority" authconfig "github.com/smallstep/certificates/authority/config" "github.com/smallstep/certificates/authority/provisioner" "github.com/smallstep/certificates/templates" "go.step.sm/linkedca" ) type helmVariables struct { *linkedca.Configuration Defaults *linkedca.Defaults Password string EnableSSH bool EnableAdmin bool TLS authconfig.TLSOptions Provisioners []provisioner.Interface } // WriteHelmTemplate a helm template to configure the // smallstep/step-certificates helm chart. func (p *PKI) WriteHelmTemplate(w io.Writer) error { tmpl, err := template.New("helm").Funcs(templates.StepFuncMap()).Parse(helmTemplate) if err != nil { return errors.Wrap(err, "error writing helm template") } // Delete ssh section if it is not enabled if !p.options.enableSSH { p.Ssh = nil } // Convert provisioners to ca.json representation provisioners := []provisioner.Interface{} for _, p := range p.Authority.Provisioners { pp, err := authority.ProvisionerToCertificates(p) if err != nil { return err } provisioners = append(provisioners, pp) } // Add default ACME provisioner if enabled. Note that this logic is similar // to what's in p.GenerateConfig(), but that codepath isn't taken when // writing the Helm template. The default JWK provisioner is added earlier in // the process and that's part of the provisioners above. // // To prevent name clashes for the default ACME provisioner, we append "-1" to // the name if it already exists. See https://github.com/smallstep/cli/issues/1018 // for the reason. // // TODO(hs): consider refactoring the initialization, so that this becomes // easier to reason about and maintain. if p.options.enableACME { acmeProvisionerName := "acme" for _, prov := range provisioners { if prov.GetName() == acmeProvisionerName { acmeProvisionerName = fmt.Sprintf("%s-1", acmeProvisionerName) break } } provisioners = append(provisioners, &provisioner.ACME{ Type: "ACME", Name: acmeProvisionerName, }) } // Add default SSHPOP provisioner if enabled. Similar to the above, this is // the same as what happens in p.GenerateConfig(). To prevent name clashes for the // default SSHPOP provisioner, we append "-1" to it if it already exists. See // https://github.com/smallstep/cli/issues/1018 for the reason. if p.options.enableSSH { sshProvisionerName := "sshpop" for _, prov := range provisioners { if prov.GetName() == sshProvisionerName { sshProvisionerName = fmt.Sprintf("%s-1", sshProvisionerName) break } } provisioners = append(provisioners, &provisioner.SSHPOP{ Type: "SSHPOP", Name: sshProvisionerName, Claims: &provisioner.Claims{ EnableSSHCA: &p.options.enableSSH, }, }) } if err := tmpl.Execute(w, helmVariables{ Configuration: &p.Configuration, Defaults: &p.Defaults, Password: "", EnableSSH: p.options.enableSSH, EnableAdmin: p.options.enableAdmin, TLS: authconfig.DefaultTLSOptions, Provisioners: provisioners, }); err != nil { return errors.Wrap(err, "error executing helm template") } return nil } const helmTemplate = `# Helm template inject: enabled: true # Config contains the configuration files ca.json and defaults.json config: files: ca.json: root: {{ first .Root }} federateRoots: [] crt: {{ .Intermediate }} key: {{ .IntermediateKey }} {{- if .Kms }} kms: type: {{ lower (.Kms.Type | toString) }} {{- end }} {{- if .EnableSSH }} ssh: hostKey: {{ .Ssh.HostKey }} userKey: {{ .Ssh.UserKey }} {{- end }} address: {{ .Address }} dnsNames: {{- range .DnsNames }} - {{ . }} {{- end }} logger: format: json db: type: badgerv2 dataSource: /home/step/db authority: enableAdmin: {{ .EnableAdmin }} provisioners: {{- range .Provisioners }} - {{ . | toJson }} {{- end }} tls: cipherSuites: {{- range .TLS.CipherSuites }} - {{ . }} {{- end }} minVersion: {{ .TLS.MinVersion }} maxVersion: {{ .TLS.MaxVersion }} renegotiation: {{ .TLS.Renegotiation }} defaults.json: ca-url: {{ .Defaults.CaUrl }} ca-config: {{ .Defaults.CaConfig }} fingerprint: {{ .Defaults.Fingerprint }} root: {{ .Defaults.Root }} # Certificates contains the root and intermediate certificate and # optionally the SSH host and user public keys certificates: # intermediate_ca contains the text of the intermediate CA Certificate intermediate_ca: | {{- index .Files .Intermediate | toString | nindent 6 }} # root_ca contains the text of the root CA Certificate root_ca: | {{- first .Root | index .Files | toString | nindent 6 }} {{- if .Ssh }} # ssh_host_ca contains the text of the public ssh key for the SSH root CA ssh_host_ca: {{ index .Files .Ssh.HostPublicKey | toString }} # ssh_user_ca contains the text of the public ssh key for the SSH root CA ssh_user_ca: {{ index .Files .Ssh.UserPublicKey | toString }} {{- end }} # Secrets contains the root and intermediate keys and optionally the SSH # private keys secrets: # ca_password contains the password used to encrypt x509.intermediate_ca_key, ssh.host_ca_key and ssh.user_ca_key # This value must be base64 encoded. ca_password: {{ .Password | b64enc }} provisioner_password: {{ .Password | b64enc}} x509: # intermediate_ca_key contains the contents of your encrypted intermediate CA key intermediate_ca_key: | {{- index .Files .IntermediateKey | toString | nindent 8 }} # root_ca_key contains the contents of your encrypted root CA key # Note that this value can be omitted without impacting the functionality of step-certificates # If supplied, this should be encrypted using a unique password that is not used for encrypting # the intermediate_ca_key, ssh.host_ca_key or ssh.user_ca_key. root_ca_key: | {{- first .RootKey | index .Files | toString | nindent 8 }} {{- if .Ssh }} ssh: # ssh_host_ca_key contains the contents of your encrypted SSH Host CA key host_ca_key: | {{- index .Files .Ssh.HostKey | toString | nindent 8 }} # ssh_user_ca_key contains the contents of your encrypted SSH User CA key user_ca_key: | {{- index .Files .Ssh.UserKey | toString | nindent 8 }} {{- end }} `