2019-07-24 01:46:43 +00:00
package authority
import (
2019-10-28 18:50:43 +00:00
"context"
2019-07-24 01:46:43 +00:00
"crypto/rand"
2019-11-20 20:59:48 +00:00
"crypto/x509"
2019-07-24 01:46:43 +00:00
"encoding/binary"
"net/http"
"strings"
2019-10-28 18:50:43 +00:00
"time"
2019-07-24 01:46:43 +00:00
2022-03-08 12:26:07 +00:00
"github.com/pkg/errors"
2021-05-03 19:48:20 +00:00
"github.com/smallstep/certificates/authority/config"
2019-07-24 01:46:43 +00:00
"github.com/smallstep/certificates/authority/provisioner"
2019-10-10 20:08:57 +00:00
"github.com/smallstep/certificates/db"
2019-12-16 07:54:25 +00:00
"github.com/smallstep/certificates/errs"
2019-10-05 00:08:42 +00:00
"github.com/smallstep/certificates/templates"
2020-08-10 18:26:51 +00:00
"go.step.sm/crypto/randutil"
"go.step.sm/crypto/sshutil"
2019-07-24 01:46:43 +00:00
"golang.org/x/crypto/ssh"
)
2019-08-03 00:48:34 +00:00
const (
// SSHAddUserPrincipal is the principal that will run the add user command.
// Defaults to "provisioner" but it can be changed in the configuration.
SSHAddUserPrincipal = "provisioner"
// SSHAddUserCommand is the default command to run to add a new user.
// Defaults to "sudo useradd -m <principal>; nc -q0 localhost 22" but it can be changed in the
// configuration. The string "<principal>" will be replace by the new
// principal to add.
SSHAddUserCommand = "sudo useradd -m <principal>; nc -q0 localhost 22"
)
2019-07-24 01:46:43 +00:00
2019-10-09 01:35:28 +00:00
// GetSSHRoots returns the SSH User and Host public keys.
2021-05-03 19:48:20 +00:00
func ( a * Authority ) GetSSHRoots ( context . Context ) ( * config . SSHKeys , error ) {
return & config . SSHKeys {
2019-10-09 01:09:41 +00:00
HostKeys : a . sshCAHostCerts ,
UserKeys : a . sshCAUserCerts ,
} , nil
}
2019-10-09 01:35:28 +00:00
// GetSSHFederation returns the public keys for federated SSH signers.
2021-05-03 19:48:20 +00:00
func ( a * Authority ) GetSSHFederation ( context . Context ) ( * config . SSHKeys , error ) {
return & config . SSHKeys {
2019-10-09 01:09:41 +00:00
HostKeys : a . sshCAHostFederatedCerts ,
UserKeys : a . sshCAUserFederatedCerts ,
} , nil
2019-09-25 02:12:13 +00:00
}
2019-10-04 02:03:38 +00:00
// GetSSHConfig returns rendered templates for clients (user) or servers (host).
2020-03-11 02:01:45 +00:00
func ( a * Authority ) GetSSHConfig ( ctx context . Context , typ string , data map [ string ] string ) ( [ ] templates . Output , error ) {
2019-10-04 02:03:38 +00:00
if a . sshCAUserCertSignKey == nil && a . sshCAHostCertSignKey == nil {
2020-01-24 06:04:34 +00:00
return nil , errs . NotFound ( "getSSHConfig: ssh is not configured" )
2019-10-04 02:03:38 +00:00
}
2020-06-17 00:57:35 +00:00
if a . templates == nil {
2020-06-16 00:25:47 +00:00
return nil , errs . NotFound ( "getSSHConfig: ssh templates are not configured" )
}
2019-10-04 02:03:38 +00:00
var ts [ ] templates . Template
switch typ {
case provisioner . SSHUserCert :
2020-06-17 00:57:35 +00:00
if a . templates != nil && a . templates . SSH != nil {
ts = a . templates . SSH . User
2019-10-04 02:03:38 +00:00
}
case provisioner . SSHHostCert :
2020-06-17 00:57:35 +00:00
if a . templates != nil && a . templates . SSH != nil {
ts = a . templates . SSH . Host
2019-10-04 02:03:38 +00:00
}
default :
2021-11-18 23:12:44 +00:00
return nil , errs . BadRequest ( "invalid certificate type '%s'" , typ )
2019-10-04 02:03:38 +00:00
}
2019-10-05 00:08:42 +00:00
// Merge user and default data
var mergedData map [ string ] interface { }
if len ( data ) == 0 {
2020-06-17 00:57:35 +00:00
mergedData = a . templates . Data
2019-10-05 00:08:42 +00:00
} else {
2020-06-17 00:57:35 +00:00
mergedData = make ( map [ string ] interface { } , len ( a . templates . Data ) + 1 )
2019-10-05 00:08:42 +00:00
mergedData [ "User" ] = data
2020-06-17 00:57:35 +00:00
for k , v := range a . templates . Data {
2019-10-05 00:08:42 +00:00
mergedData [ k ] = v
}
}
// Render templates
2019-10-04 02:03:38 +00:00
output := [ ] templates . Output { }
for _ , t := range ts {
2020-06-17 00:26:54 +00:00
if err := t . Load ( ) ; err != nil {
return nil , err
}
// Check for required variables.
if err := t . ValidateRequiredData ( data ) ; err != nil {
2021-11-19 02:17:36 +00:00
return nil , errs . BadRequestErr ( err , "%v, please use `--set <key=value>` flag" , err )
2020-06-17 00:26:54 +00:00
}
2019-10-05 00:08:42 +00:00
o , err := t . Output ( mergedData )
2019-10-04 02:03:38 +00:00
if err != nil {
return nil , err
}
2021-11-12 21:12:11 +00:00
2021-11-15 23:32:07 +00:00
// Backwards compatibility for version of the cli older than v0.18.0.
// Before v0.18.0 we were not passing any value for SSHTemplateVersionKey
// from the cli.
2021-11-16 18:02:04 +00:00
if o . Name == "step_includes.tpl" && data [ templates . SSHTemplateVersionKey ] == "" {
o . Type = templates . File
o . Path = strings . TrimPrefix ( o . Path , "${STEPPATH}/" )
2021-11-12 21:12:11 +00:00
}
2019-10-04 02:03:38 +00:00
output = append ( output , o )
}
return output , nil
}
2019-11-15 02:24:58 +00:00
// GetSSHBastion returns the bastion configuration, for the given pair user,
// hostname.
2021-10-08 18:59:57 +00:00
func ( a * Authority ) GetSSHBastion ( ctx context . Context , user , hostname string ) ( * config . Bastion , error ) {
2019-11-15 02:24:58 +00:00
if a . sshBastionFunc != nil {
2020-03-11 02:01:45 +00:00
bs , err := a . sshBastionFunc ( ctx , user , hostname )
2019-12-20 21:30:05 +00:00
return bs , errs . Wrap ( http . StatusInternalServerError , err , "authority.GetSSHBastion" )
2019-11-15 02:24:58 +00:00
}
if a . config . SSH != nil {
if a . config . SSH . Bastion != nil && a . config . SSH . Bastion . Hostname != "" {
2020-06-19 19:37:08 +00:00
// Do not return a bastion for a bastion host.
//
// This condition might fail if a different name or IP is used.
// Trying to resolve hostnames to IPs and compare them won't be a
// complete solution because it depends on the network
// configuration, of the CA and clients and can also return false
// positives. Although not perfect, this simple solution will work
// in most cases.
if ! strings . EqualFold ( hostname , a . config . SSH . Bastion . Hostname ) {
return a . config . SSH . Bastion , nil
}
2019-11-15 02:24:58 +00:00
}
return nil , nil
}
2020-01-24 06:04:34 +00:00
return nil , errs . NotFound ( "authority.GetSSHBastion; ssh is not configured" )
2019-10-28 18:50:43 +00:00
}
2019-07-24 01:46:43 +00:00
// SignSSH creates a signed SSH certificate with the given public key and options.
2020-07-23 01:24:45 +00:00
func ( a * Authority ) SignSSH ( ctx context . Context , key ssh . PublicKey , opts provisioner . SignSSHOptions , signOpts ... provisioner . SignOption ) ( * ssh . Certificate , error ) {
2020-07-27 22:43:41 +00:00
var (
certOptions [ ] sshutil . Option
mods [ ] provisioner . SSHCertModifier
validators [ ] provisioner . SSHCertValidator
)
2019-07-24 01:46:43 +00:00
2020-08-04 01:36:05 +00:00
// Validate given options.
if err := opts . Validate ( ) ; err != nil {
2021-11-19 02:44:58 +00:00
return nil , err
2020-08-04 01:36:05 +00:00
}
2020-01-04 02:22:02 +00:00
// Set backdate with the configured value
opts . Backdate = a . config . AuthorityConfig . Backdate . Duration
2019-07-24 01:46:43 +00:00
for _ , op := range signOpts {
switch o := op . ( type ) {
2020-07-27 22:43:41 +00:00
// add options to NewCertificate
case provisioner . SSHCertificateOptions :
certOptions = append ( certOptions , o . Options ( opts ) ... )
2019-07-24 01:46:43 +00:00
// modify the ssh.Certificate
2020-01-24 06:04:34 +00:00
case provisioner . SSHCertModifier :
2019-07-24 01:46:43 +00:00
mods = append ( mods , o )
2020-07-27 22:43:41 +00:00
2019-07-24 01:46:43 +00:00
// validate the ssh.Certificate
2020-01-24 06:04:34 +00:00
case provisioner . SSHCertValidator :
2019-07-24 01:46:43 +00:00
validators = append ( validators , o )
2020-07-27 22:43:41 +00:00
2019-07-24 01:46:43 +00:00
// validate the given SSHOptions
2020-01-24 06:04:34 +00:00
case provisioner . SSHCertOptionsValidator :
2019-07-24 01:46:43 +00:00
if err := o . Valid ( opts ) ; err != nil {
2021-11-24 01:52:17 +00:00
return nil , errs . BadRequestErr ( err , "error validating ssh certificate options" )
2019-07-24 01:46:43 +00:00
}
2020-07-27 22:43:41 +00:00
2019-07-24 01:46:43 +00:00
default :
2020-07-29 23:06:39 +00:00
return nil , errs . InternalServer ( "authority.SignSSH: invalid extra option type %T" , o )
2019-07-24 01:46:43 +00:00
}
}
2020-07-29 23:06:39 +00:00
// Simulated certificate request with request options.
cr := sshutil . CertificateRequest {
2020-07-30 02:26:46 +00:00
Type : opts . CertType ,
2020-07-29 23:06:39 +00:00
KeyID : opts . KeyID ,
Principals : opts . Principals ,
Key : key ,
}
2020-07-27 22:43:41 +00:00
// Create certificate from template.
2020-07-29 23:06:39 +00:00
certificate , err := sshutil . NewCertificate ( cr , certOptions ... )
2019-07-24 01:46:43 +00:00
if err != nil {
2020-07-27 22:43:41 +00:00
if _ , ok := err . ( * sshutil . TemplateError ) ; ok {
2021-11-19 02:44:58 +00:00
return nil , errs . ApplyOptions (
errs . BadRequestErr ( err , err . Error ( ) ) ,
2020-07-27 22:43:41 +00:00
errs . WithKeyVal ( "signOptions" , signOpts ) ,
)
}
2022-01-10 14:49:37 +00:00
// explicitly check for unmarshaling errors, which are most probably caused by JSON template syntax errors
if strings . HasPrefix ( err . Error ( ) , "error unmarshaling certificate" ) {
2022-01-12 09:41:36 +00:00
return nil , errs . InternalServerErr ( templatingError ( err ) ,
2022-01-10 14:49:37 +00:00
errs . WithKeyVal ( "signOptions" , signOpts ) ,
2022-01-12 09:41:36 +00:00
errs . WithMessage ( "error applying certificate template" ) ,
2022-01-10 14:49:37 +00:00
)
}
2020-07-27 22:43:41 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.SignSSH" )
2019-07-24 01:46:43 +00:00
}
2020-07-29 23:06:39 +00:00
// Get actual *ssh.Certificate and continue with provisioner modifiers.
2020-08-28 21:29:18 +00:00
certTpl := certificate . GetCertificate ( )
2019-07-24 01:46:43 +00:00
2020-07-29 23:06:39 +00:00
// Use SignSSHOptions to modify the certificate validity. It will be later
// checked or set if not defined.
2020-08-28 21:29:18 +00:00
if err := opts . ModifyValidity ( certTpl ) ; err != nil {
2021-11-19 02:44:58 +00:00
return nil , errs . BadRequestErr ( err , err . Error ( ) )
2019-07-24 01:46:43 +00:00
}
2020-07-27 22:43:41 +00:00
// Use provisioner modifiers.
2019-07-24 01:46:43 +00:00
for _ , m := range mods {
2020-08-28 21:29:18 +00:00
if err := m . Modify ( certTpl , opts ) ; err != nil {
2021-11-23 20:04:51 +00:00
return nil , errs . ForbiddenErr ( err , "error creating ssh certificate" )
2019-07-24 01:46:43 +00:00
}
}
// Get signer from authority keys
var signer ssh . Signer
2020-08-28 21:29:18 +00:00
switch certTpl . CertType {
2019-07-24 01:46:43 +00:00
case ssh . UserCert :
2019-08-01 22:04:56 +00:00
if a . sshCAUserCertSignKey == nil {
2020-07-29 23:06:39 +00:00
return nil , errs . NotImplemented ( "authority.SignSSH: user certificate signing is not enabled" )
2019-08-01 22:04:56 +00:00
}
2019-09-25 02:12:13 +00:00
signer = a . sshCAUserCertSignKey
2019-07-24 01:46:43 +00:00
case ssh . HostCert :
2019-08-01 22:04:56 +00:00
if a . sshCAHostCertSignKey == nil {
2020-07-29 23:06:39 +00:00
return nil , errs . NotImplemented ( "authority.SignSSH: host certificate signing is not enabled" )
2019-08-01 22:04:56 +00:00
}
2019-09-25 02:12:13 +00:00
signer = a . sshCAHostCertSignKey
2019-07-24 01:46:43 +00:00
default :
2020-08-28 21:29:18 +00:00
return nil , errs . InternalServer ( "authority.SignSSH: unexpected ssh certificate type: %d" , certTpl . CertType )
2019-07-24 01:46:43 +00:00
}
2022-03-08 12:26:07 +00:00
switch certTpl . CertType {
case ssh . UserCert :
// when no user policy engine is configured, but a host policy engine is
// configured, the user certificate is denied.
if a . sshUserPolicy == nil && a . sshHostPolicy != nil {
return nil , errs . ForbiddenErr ( errors . New ( "authority not allowed to sign ssh user certificates" ) , "authority.SignSSH: error creating ssh user certificate" )
}
if a . sshUserPolicy != nil {
allowed , err := a . sshUserPolicy . ArePrincipalsAllowed ( certTpl )
if err != nil {
return nil , errs . InternalServerErr ( err ,
errs . WithMessage ( "authority.SignSSH: error creating ssh user certificate" ) ,
)
}
if ! allowed {
return nil , errs . ForbiddenErr ( errors . New ( "authority not allowed to sign" ) , "authority.SignSSH: error creating ssh user certificate" )
}
}
case ssh . HostCert :
// when no host policy engine is configured, but a user policy engine is
// configured, the host certificate is denied.
if a . sshHostPolicy == nil && a . sshUserPolicy != nil {
return nil , errs . ForbiddenErr ( errors . New ( "authority not allowed to sign ssh host certificates" ) , "authority.SignSSH: error creating ssh user certificate" )
}
if a . sshHostPolicy != nil {
allowed , err := a . sshHostPolicy . ArePrincipalsAllowed ( certTpl )
if err != nil {
return nil , errs . InternalServerErr ( err ,
errs . WithMessage ( "authority.SignSSH: error creating ssh host certificate" ) ,
)
}
if ! allowed {
return nil , errs . ForbiddenErr ( errors . New ( "authority not allowed to sign" ) , "authority.SignSSH: error creating ssh host certificate" )
}
}
default :
return nil , errs . InternalServer ( "authority.SignSSH: unexpected ssh certificate type: %d" , certTpl . CertType )
}
2020-07-27 22:43:41 +00:00
// Sign certificate.
2020-08-28 21:29:18 +00:00
cert , err := sshutil . CreateCertificate ( certTpl , signer )
2019-07-24 01:46:43 +00:00
if err != nil {
2020-07-29 23:06:39 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.SignSSH: error signing certificate" )
2019-07-24 01:46:43 +00:00
}
2020-07-27 22:43:41 +00:00
// User provisioners validators.
2019-07-24 01:46:43 +00:00
for _ , v := range validators {
2020-01-24 21:42:00 +00:00
if err := v . Valid ( cert , opts ) ; err != nil {
2021-11-23 20:04:51 +00:00
return nil , errs . ForbiddenErr ( err , "error validating ssh certificate" )
2019-08-03 00:48:34 +00:00
}
}
2021-07-21 01:16:24 +00:00
if err = a . storeSSHCertificate ( cert ) ; err != nil && err != db . ErrNotImplemented {
2020-07-29 23:06:39 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "authority.SignSSH: error storing certificate in db" )
2019-10-10 20:08:57 +00:00
}
2019-08-03 00:48:34 +00:00
return cert , nil
}
2019-10-28 18:50:43 +00:00
// RenewSSH creates a signed SSH certificate using the old SSH certificate as a template.
2020-03-11 02:01:45 +00:00
func ( a * Authority ) RenewSSH ( ctx context . Context , oldCert * ssh . Certificate ) ( * ssh . Certificate , error ) {
2019-10-28 18:50:43 +00:00
if oldCert . ValidAfter == 0 || oldCert . ValidBefore == 0 {
2021-11-18 23:12:44 +00:00
return nil , errs . BadRequest ( "cannot renew a certificate without validity period" )
2021-07-21 22:22:57 +00:00
}
if err := a . authorizeSSHCertificate ( ctx , oldCert ) ; err != nil {
return nil , err
2019-10-28 18:50:43 +00:00
}
2020-01-04 02:22:02 +00:00
backdate := a . config . AuthorityConfig . Backdate . Duration
duration := time . Duration ( oldCert . ValidBefore - oldCert . ValidAfter ) * time . Second
now := time . Now ( )
va := now . Add ( - 1 * backdate )
vb := now . Add ( duration - backdate )
2019-10-28 18:50:43 +00:00
2020-07-27 22:54:51 +00:00
// Build base certificate with the old key.
// Nonce and serial will be automatically generated on signing.
2020-08-28 21:29:18 +00:00
certTpl := & ssh . Certificate {
2019-10-28 18:50:43 +00:00
Key : oldCert . Key ,
CertType : oldCert . CertType ,
KeyId : oldCert . KeyId ,
ValidPrincipals : oldCert . ValidPrincipals ,
Permissions : oldCert . Permissions ,
2020-07-27 22:54:51 +00:00
Reserved : oldCert . Reserved ,
2019-10-28 18:50:43 +00:00
ValidAfter : uint64 ( va . Unix ( ) ) ,
ValidBefore : uint64 ( vb . Unix ( ) ) ,
}
// Get signer from authority keys
var signer ssh . Signer
2020-08-28 21:29:18 +00:00
switch certTpl . CertType {
2019-10-28 18:50:43 +00:00
case ssh . UserCert :
if a . sshCAUserCertSignKey == nil {
2020-01-24 06:04:34 +00:00
return nil , errs . NotImplemented ( "renewSSH: user certificate signing is not enabled" )
2019-10-28 18:50:43 +00:00
}
signer = a . sshCAUserCertSignKey
case ssh . HostCert :
if a . sshCAHostCertSignKey == nil {
2020-01-24 06:04:34 +00:00
return nil , errs . NotImplemented ( "renewSSH: host certificate signing is not enabled" )
2019-10-28 18:50:43 +00:00
}
signer = a . sshCAHostCertSignKey
default :
2020-08-28 21:29:18 +00:00
return nil , errs . InternalServer ( "renewSSH: unexpected ssh certificate type: %d" , certTpl . CertType )
2019-10-28 18:50:43 +00:00
}
2020-07-27 22:54:51 +00:00
// Sign certificate.
2020-08-28 21:29:18 +00:00
cert , err := sshutil . CreateCertificate ( certTpl , signer )
2019-10-28 18:50:43 +00:00
if err != nil {
2020-07-27 22:54:51 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "signSSH: error signing certificate" )
2019-10-28 18:50:43 +00:00
}
2021-07-21 01:16:24 +00:00
if err = a . storeSSHCertificate ( cert ) ; err != nil && err != db . ErrNotImplemented {
2019-12-20 21:30:05 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "renewSSH: error storing certificate in db" )
2019-10-28 18:50:43 +00:00
}
return cert , nil
}
// RekeySSH creates a signed SSH certificate using the old SSH certificate as a template.
2020-03-11 02:01:45 +00:00
func ( a * Authority ) RekeySSH ( ctx context . Context , oldCert * ssh . Certificate , pub ssh . PublicKey , signOpts ... provisioner . SignOption ) ( * ssh . Certificate , error ) {
2020-01-24 06:04:34 +00:00
var validators [ ] provisioner . SSHCertValidator
2019-10-28 18:50:43 +00:00
for _ , op := range signOpts {
switch o := op . ( type ) {
// validate the ssh.Certificate
2020-01-24 06:04:34 +00:00
case provisioner . SSHCertValidator :
2019-10-28 18:50:43 +00:00
validators = append ( validators , o )
default :
2020-01-24 06:04:34 +00:00
return nil , errs . InternalServer ( "rekeySSH; invalid extra option type %T" , o )
2019-10-28 18:50:43 +00:00
}
}
if oldCert . ValidAfter == 0 || oldCert . ValidBefore == 0 {
2021-11-18 23:12:44 +00:00
return nil , errs . BadRequest ( "cannot rekey a certificate without validity period" )
2019-10-28 18:50:43 +00:00
}
2020-01-04 02:30:17 +00:00
2021-07-21 22:22:57 +00:00
if err := a . authorizeSSHCertificate ( ctx , oldCert ) ; err != nil {
return nil , err
}
2020-01-04 02:30:17 +00:00
backdate := a . config . AuthorityConfig . Backdate . Duration
duration := time . Duration ( oldCert . ValidBefore - oldCert . ValidAfter ) * time . Second
now := time . Now ( )
va := now . Add ( - 1 * backdate )
vb := now . Add ( duration - backdate )
2019-10-28 18:50:43 +00:00
2020-07-27 22:54:51 +00:00
// Build base certificate with the new key.
// Nonce and serial will be automatically generated on signing.
2019-10-28 18:50:43 +00:00
cert := & ssh . Certificate {
Key : pub ,
CertType : oldCert . CertType ,
KeyId : oldCert . KeyId ,
ValidPrincipals : oldCert . ValidPrincipals ,
Permissions : oldCert . Permissions ,
2020-07-27 22:54:51 +00:00
Reserved : oldCert . Reserved ,
2019-10-28 18:50:43 +00:00
ValidAfter : uint64 ( va . Unix ( ) ) ,
ValidBefore : uint64 ( vb . Unix ( ) ) ,
}
// Get signer from authority keys
var signer ssh . Signer
switch cert . CertType {
case ssh . UserCert :
if a . sshCAUserCertSignKey == nil {
2020-01-24 06:04:34 +00:00
return nil , errs . NotImplemented ( "rekeySSH; user certificate signing is not enabled" )
2019-10-28 18:50:43 +00:00
}
signer = a . sshCAUserCertSignKey
case ssh . HostCert :
if a . sshCAHostCertSignKey == nil {
2020-01-24 06:04:34 +00:00
return nil , errs . NotImplemented ( "rekeySSH; host certificate signing is not enabled" )
2019-10-28 18:50:43 +00:00
}
signer = a . sshCAHostCertSignKey
default :
2021-11-18 23:12:44 +00:00
return nil , errs . BadRequest ( "unexpected certificate type '%d'" , cert . CertType )
2019-10-28 18:50:43 +00:00
}
2020-07-27 22:54:51 +00:00
var err error
// Sign certificate.
cert , err = sshutil . CreateCertificate ( cert , signer )
2019-10-28 18:50:43 +00:00
if err != nil {
2020-07-27 22:54:51 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "signSSH: error signing certificate" )
2019-10-28 18:50:43 +00:00
}
2020-01-24 21:42:00 +00:00
// Apply validators from provisioner.
2019-10-28 18:50:43 +00:00
for _ , v := range validators {
2020-07-23 01:24:45 +00:00
if err := v . Valid ( cert , provisioner . SignSSHOptions { Backdate : backdate } ) ; err != nil {
2021-11-23 20:04:51 +00:00
return nil , errs . ForbiddenErr ( err , "error validating ssh certificate" )
2019-10-28 18:50:43 +00:00
}
}
2021-07-21 01:16:24 +00:00
if err = a . storeSSHCertificate ( cert ) ; err != nil && err != db . ErrNotImplemented {
2019-12-20 21:30:05 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "rekeySSH; error storing certificate in db" )
2019-10-28 18:50:43 +00:00
}
return cert , nil
}
2021-07-21 01:16:24 +00:00
func ( a * Authority ) storeSSHCertificate ( cert * ssh . Certificate ) error {
type sshCertificateStorer interface {
StoreSSHCertificate ( crt * ssh . Certificate ) error
}
if s , ok := a . adminDB . ( sshCertificateStorer ) ; ok {
return s . StoreSSHCertificate ( cert )
}
return a . db . StoreSSHCertificate ( cert )
}
2020-04-24 02:42:55 +00:00
// IsValidForAddUser checks if a user provisioner certificate can be issued to
// the given certificate.
func IsValidForAddUser ( cert * ssh . Certificate ) error {
if cert . CertType != ssh . UserCert {
2021-11-23 20:04:51 +00:00
return errs . Forbidden ( "certificate is not a user certificate" )
2020-04-24 02:42:55 +00:00
}
switch len ( cert . ValidPrincipals ) {
case 0 :
2021-11-23 20:04:51 +00:00
return errs . Forbidden ( "certificate does not have any principals" )
2020-04-24 02:42:55 +00:00
case 1 :
return nil
case 2 :
// OIDC provisioners adds a second principal with the email address.
// @ cannot be the first character.
if strings . Index ( cert . ValidPrincipals [ 1 ] , "@" ) > 0 {
return nil
}
2021-11-23 20:04:51 +00:00
return errs . Forbidden ( "certificate does not have only one principal" )
2020-04-24 02:42:55 +00:00
default :
2021-11-23 20:04:51 +00:00
return errs . Forbidden ( "certificate does not have only one principal" )
2020-04-24 02:42:55 +00:00
}
}
2019-08-03 00:48:34 +00:00
// SignSSHAddUser signs a certificate that provisions a new user in a server.
2020-03-11 02:01:45 +00:00
func ( a * Authority ) SignSSHAddUser ( ctx context . Context , key ssh . PublicKey , subject * ssh . Certificate ) ( * ssh . Certificate , error ) {
2019-08-03 00:48:34 +00:00
if a . sshCAUserCertSignKey == nil {
2020-01-24 06:04:34 +00:00
return nil , errs . NotImplemented ( "signSSHAddUser: user certificate signing is not enabled" )
2019-08-03 00:48:34 +00:00
}
2020-04-24 02:42:55 +00:00
if err := IsValidForAddUser ( subject ) ; err != nil {
2021-11-23 20:04:51 +00:00
return nil , err
2019-08-03 00:48:34 +00:00
}
nonce , err := randutil . ASCII ( 32 )
if err != nil {
2020-01-24 06:04:34 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "signSSHAddUser" )
2019-08-03 00:48:34 +00:00
}
var serial uint64
if err := binary . Read ( rand . Reader , binary . BigEndian , & serial ) ; err != nil {
2019-12-20 21:30:05 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "signSSHAddUser: error reading random number" )
2019-08-03 00:48:34 +00:00
}
2019-09-25 02:12:13 +00:00
signer := a . sshCAUserCertSignKey
2019-08-03 00:48:34 +00:00
principal := subject . ValidPrincipals [ 0 ]
addUserPrincipal := a . getAddUserPrincipal ( )
cert := & ssh . Certificate {
Nonce : [ ] byte ( nonce ) ,
Key : key ,
Serial : serial ,
CertType : ssh . UserCert ,
KeyId : principal + "-" + addUserPrincipal ,
ValidPrincipals : [ ] string { addUserPrincipal } ,
ValidAfter : subject . ValidAfter ,
ValidBefore : subject . ValidBefore ,
Permissions : ssh . Permissions {
CriticalOptions : map [ string ] string {
"force-command" : a . getAddUserCommand ( principal ) ,
} ,
} ,
SignatureKey : signer . PublicKey ( ) ,
}
// Get bytes for signing trailing the signature length.
data := cert . Marshal ( )
data = data [ : len ( data ) - 4 ]
// Sign the certificate
sig , err := signer . Sign ( rand . Reader , data )
if err != nil {
return nil , err
}
cert . Signature = sig
2019-10-10 20:08:57 +00:00
2021-07-21 01:16:24 +00:00
if err = a . storeSSHCertificate ( cert ) ; err != nil && err != db . ErrNotImplemented {
2019-12-20 21:30:05 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "signSSHAddUser: error storing certificate in db" )
2019-10-10 20:08:57 +00:00
}
2019-07-24 01:46:43 +00:00
return cert , nil
}
2019-08-03 00:48:34 +00:00
2019-10-10 20:08:57 +00:00
// CheckSSHHost checks the given principal has been registered before.
2021-10-08 18:59:57 +00:00
func ( a * Authority ) CheckSSHHost ( ctx context . Context , principal , token string ) ( bool , error ) {
2019-12-10 07:14:56 +00:00
if a . sshCheckHostFunc != nil {
exists , err := a . sshCheckHostFunc ( ctx , principal , token , a . GetRootCertificates ( ) )
if err != nil {
2019-12-16 07:54:25 +00:00
return false , errs . Wrap ( http . StatusInternalServerError , err ,
"checkSSHHost: error from injected checkSSHHost func" )
2019-12-10 07:14:56 +00:00
}
return exists , nil
}
2019-10-10 20:08:57 +00:00
exists , err := a . db . IsSSHHost ( principal )
if err != nil {
if err == db . ErrNotImplemented {
2019-12-16 07:54:25 +00:00
return false , errs . Wrap ( http . StatusNotImplemented , err ,
"checkSSHHost: isSSHHost is not implemented" )
2019-10-10 20:08:57 +00:00
}
2019-12-16 07:54:25 +00:00
return false , errs . Wrap ( http . StatusInternalServerError , err ,
"checkSSHHost: error checking if hosts exists" )
2019-10-10 20:08:57 +00:00
}
return exists , nil
}
2019-10-25 20:47:49 +00:00
// GetSSHHosts returns a list of valid host principals.
2021-05-03 19:48:20 +00:00
func ( a * Authority ) GetSSHHosts ( ctx context . Context , cert * x509 . Certificate ) ( [ ] config . Host , error ) {
2019-11-20 20:59:48 +00:00
if a . sshGetHostsFunc != nil {
2020-03-11 02:01:45 +00:00
hosts , err := a . sshGetHostsFunc ( ctx , cert )
2019-12-20 21:30:05 +00:00
return hosts , errs . Wrap ( http . StatusInternalServerError , err , "getSSHHosts" )
2019-11-20 19:32:27 +00:00
}
2019-11-21 01:23:51 +00:00
hostnames , err := a . db . GetSSHHostPrincipals ( )
2019-11-20 20:59:48 +00:00
if err != nil {
2019-12-20 21:30:05 +00:00
return nil , errs . Wrap ( http . StatusInternalServerError , err , "getSSHHosts" )
2019-10-25 20:47:49 +00:00
}
2019-11-21 01:23:51 +00:00
2021-05-03 19:48:20 +00:00
hosts := make ( [ ] config . Host , len ( hostnames ) )
2019-11-21 01:23:51 +00:00
for i , hn := range hostnames {
2021-05-03 19:48:20 +00:00
hosts [ i ] = config . Host { Hostname : hn }
2019-11-21 01:23:51 +00:00
}
2019-11-20 20:59:48 +00:00
return hosts , nil
2019-10-25 20:47:49 +00:00
}
2019-08-03 00:48:34 +00:00
func ( a * Authority ) getAddUserPrincipal ( ) ( cmd string ) {
if a . config . SSH . AddUserPrincipal == "" {
return SSHAddUserPrincipal
}
return a . config . SSH . AddUserPrincipal
}
func ( a * Authority ) getAddUserCommand ( principal string ) string {
var cmd string
if a . config . SSH . AddUserCommand == "" {
cmd = SSHAddUserCommand
} else {
cmd = a . config . SSH . AddUserCommand
}
2021-10-08 18:59:57 +00:00
return strings . ReplaceAll ( cmd , "<principal>" , principal )
2019-08-03 00:48:34 +00:00
}