mirror of
https://github.com/gravitational/teleport
synced 2024-10-22 02:03:24 +00:00
2172965057
It's no longer needed, since CertAuthority contains the signing algorithm internally.
1098 lines
33 KiB
Go
1098 lines
33 KiB
Go
/*
|
|
Copyright 2017-2019 Gravitational, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package services
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
"github.com/gravitational/teleport/lib/sshutils"
|
|
"github.com/gravitational/teleport/lib/tlsca"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
"github.com/gravitational/teleport/lib/wrappers"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
"github.com/tstranex/u2f"
|
|
"golang.org/x/crypto/ssh"
|
|
)
|
|
|
|
// HostCertParams defines all parameters needed to generate a host certificate
|
|
type HostCertParams struct {
|
|
// PrivateCASigningKey is the private key of the CA that will sign the public key of the host
|
|
PrivateCASigningKey []byte
|
|
// CASigningAlg is the signature algorithm used by the CA private key.
|
|
CASigningAlg string
|
|
// PublicHostKey is the public key of the host
|
|
PublicHostKey []byte
|
|
// HostID is used by Teleport to uniquely identify a node within a cluster
|
|
HostID string
|
|
// Principals is a list of additional principals to add to the certificate.
|
|
Principals []string
|
|
// NodeName is the DNS name of the node
|
|
NodeName string
|
|
// ClusterName is the name of the cluster within which a node lives
|
|
ClusterName string
|
|
// Roles identifies the roles of a Teleport instance
|
|
Roles teleport.Roles
|
|
// TTL defines how long a certificate is valid for
|
|
TTL time.Duration
|
|
}
|
|
|
|
// Check checks parameters for errors
|
|
func (c HostCertParams) Check() error {
|
|
if len(c.PrivateCASigningKey) == 0 || c.CASigningAlg == "" {
|
|
return trace.BadParameter("PrivateCASigningKey and CASigningAlg are required")
|
|
}
|
|
if c.HostID == "" && len(c.Principals) == 0 {
|
|
return trace.BadParameter("HostID [%q] or Principals [%q] are required",
|
|
c.HostID, c.Principals)
|
|
}
|
|
if c.ClusterName == "" {
|
|
return trace.BadParameter("ClusterName [%q] is required", c.ClusterName)
|
|
}
|
|
|
|
if err := c.Roles.Check(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChangePasswordReq defines a request to change user password
|
|
type ChangePasswordReq struct {
|
|
// User is user ID
|
|
User string
|
|
// OldPassword is user current password
|
|
OldPassword []byte `json:"old_password"`
|
|
// NewPassword is user new password
|
|
NewPassword []byte `json:"new_password"`
|
|
// SecondFactorToken is user 2nd factor token
|
|
SecondFactorToken string `json:"second_factor_token"`
|
|
// U2FSignResponse is U2F sign response
|
|
U2FSignResponse *u2f.SignResponse `json:"u2f_sign_response"`
|
|
}
|
|
|
|
// UserCertParams defines OpenSSH user certificate parameters
|
|
type UserCertParams struct {
|
|
// PrivateCASigningKey is the private key of the CA that will sign the public key of the user
|
|
PrivateCASigningKey []byte
|
|
// CASigningAlg is the signature algorithm used by the CA private key.
|
|
CASigningAlg string
|
|
// PublicUserKey is the public key of the user
|
|
PublicUserKey []byte
|
|
// TTL defines how long a certificate is valid for
|
|
TTL time.Duration
|
|
// Username is teleport username
|
|
Username string
|
|
// AllowedLogins is a list of SSH principals
|
|
AllowedLogins []string
|
|
// PermitX11Forwarding permits X11 forwarding for this cert
|
|
PermitX11Forwarding bool
|
|
// PermitAgentForwarding permits agent forwarding for this cert
|
|
PermitAgentForwarding bool
|
|
// PermitPortForwarding permits port forwarding.
|
|
PermitPortForwarding bool
|
|
// Roles is a list of roles assigned to this user
|
|
Roles []string
|
|
// CertificateFormat is the format of the SSH certificate.
|
|
CertificateFormat string
|
|
// RouteToCluster specifies the target cluster
|
|
// if present in the certificate, will be used
|
|
// to route the requests to
|
|
RouteToCluster string
|
|
// Traits hold claim data used to populate a role at runtime.
|
|
Traits wrappers.Traits
|
|
// ActiveRequests tracks privilege escalation requests applied during
|
|
// certificate construction.
|
|
ActiveRequests RequestIDs
|
|
}
|
|
|
|
func (c UserCertParams) Check() error {
|
|
if len(c.PrivateCASigningKey) == 0 || c.CASigningAlg == "" {
|
|
return trace.BadParameter("PrivateCASigningKey and CASigningAlg are required")
|
|
}
|
|
if c.TTL < defaults.MinCertDuration {
|
|
return trace.BadParameter("TTL can't be less than %v", defaults.MinCertDuration)
|
|
}
|
|
if len(c.AllowedLogins) == 0 {
|
|
return trace.BadParameter("AllowedLogins are required")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CertRoles defines certificate roles
|
|
type CertRoles struct {
|
|
// Version is current version of the roles
|
|
Version string `json:"version"`
|
|
// Roles is a list of roles
|
|
Roles []string `json:"roles"`
|
|
}
|
|
|
|
// CertRolesSchema defines cert roles schema
|
|
const CertRolesSchema = `{
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"version": {"type": "string"},
|
|
"roles": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
// MarshalCertRoles marshal roles list to OpenSSH
|
|
func MarshalCertRoles(roles []string) (string, error) {
|
|
out, err := json.Marshal(CertRoles{Version: V1, Roles: roles})
|
|
if err != nil {
|
|
return "", trace.Wrap(err)
|
|
}
|
|
return string(out), err
|
|
}
|
|
|
|
// UnmarshalCertRoles marshals roles list to OpenSSH
|
|
func UnmarshalCertRoles(data string) ([]string, error) {
|
|
var certRoles CertRoles
|
|
if err := utils.UnmarshalWithSchema(CertRolesSchema, &certRoles, []byte(data)); err != nil {
|
|
return nil, trace.BadParameter(err.Error())
|
|
}
|
|
return certRoles.Roles, nil
|
|
}
|
|
|
|
// CertAuthority is a host or user certificate authority that
|
|
// can check and if it has private key stored as well, sign it too
|
|
type CertAuthority interface {
|
|
// ResourceWithSecrets sets common resource properties
|
|
ResourceWithSecrets
|
|
// GetID returns certificate authority ID -
|
|
// combined type and name
|
|
GetID() CertAuthID
|
|
// GetType returns user or host certificate authority
|
|
GetType() CertAuthType
|
|
// GetClusterName returns cluster name this cert authority
|
|
// is associated with
|
|
GetClusterName() string
|
|
// GetCheckingKeys returns public keys to check signature
|
|
GetCheckingKeys() [][]byte
|
|
// GetSigningKeys returns signing keys
|
|
GetSigningKeys() [][]byte
|
|
// CombinedMapping is used to specify combined mapping from legacy property Roles
|
|
// and new property RoleMap
|
|
CombinedMapping() RoleMap
|
|
// GetRoleMap returns role map property
|
|
GetRoleMap() RoleMap
|
|
// SetRoleMap sets role map
|
|
SetRoleMap(m RoleMap)
|
|
// GetRoles returns a list of roles assumed by users signed by this CA
|
|
GetRoles() []string
|
|
// SetRoles sets assigned roles for this certificate authority
|
|
SetRoles(roles []string)
|
|
// FirstSigningKey returns first signing key or returns error if it's not here
|
|
// The first key is returned because multiple keys can exist during key rotation.
|
|
FirstSigningKey() ([]byte, error)
|
|
// Check checks object for errors
|
|
Check() error
|
|
// CheckAndSetDefaults checks and set default values for any missing fields.
|
|
CheckAndSetDefaults() error
|
|
// SetSigningKeys sets signing keys
|
|
SetSigningKeys([][]byte) error
|
|
// SetCheckingKeys sets signing keys
|
|
SetCheckingKeys([][]byte) error
|
|
// AddRole adds a role to ca role list
|
|
AddRole(name string)
|
|
// Checkers returns public keys that can be used to check cert authorities
|
|
Checkers() ([]ssh.PublicKey, error)
|
|
// Signers returns a list of signers that could be used to sign keys
|
|
Signers() ([]ssh.Signer, error)
|
|
// V1 returns V1 version of the resource
|
|
V1() *CertAuthorityV1
|
|
// V2 returns V2 version of the resource
|
|
V2() *CertAuthorityV2
|
|
// String returns human readable version of the CertAuthority
|
|
String() string
|
|
// TLSCA returns first TLS certificate authority from the list of key pairs
|
|
TLSCA() (*tlsca.CertAuthority, error)
|
|
// SetTLSKeyPairs sets TLS key pairs
|
|
SetTLSKeyPairs(keyPairs []TLSKeyPair)
|
|
// GetTLSKeyPairs returns first PEM encoded TLS cert
|
|
GetTLSKeyPairs() []TLSKeyPair
|
|
// GetRotation returns rotation state.
|
|
GetRotation() Rotation
|
|
// SetRotation sets rotation state.
|
|
SetRotation(Rotation)
|
|
// GetSigningAlg returns the signing algorithm used by signing keys.
|
|
GetSigningAlg() string
|
|
// SetSigningAlg sets the signing algorithm used by signing keys.
|
|
SetSigningAlg(string)
|
|
// Clone returns a copy of the cert authority object.
|
|
Clone() CertAuthority
|
|
}
|
|
|
|
// CertPoolFromCertAuthorities returns certificate pools from TLS certificates
|
|
// set up in the certificate authorities list
|
|
func CertPoolFromCertAuthorities(cas []CertAuthority) (*x509.CertPool, error) {
|
|
certPool := x509.NewCertPool()
|
|
for _, ca := range cas {
|
|
keyPairs := ca.GetTLSKeyPairs()
|
|
if len(keyPairs) == 0 {
|
|
continue
|
|
}
|
|
for _, keyPair := range keyPairs {
|
|
cert, err := tlsca.ParseCertificatePEM(keyPair.Cert)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
certPool.AddCert(cert)
|
|
}
|
|
return certPool, nil
|
|
}
|
|
return certPool, nil
|
|
}
|
|
|
|
// CertPool returns certificate pools from TLS certificates
|
|
// set up in the certificate authority
|
|
func CertPool(ca CertAuthority) (*x509.CertPool, error) {
|
|
keyPairs := ca.GetTLSKeyPairs()
|
|
if len(keyPairs) == 0 {
|
|
return nil, trace.BadParameter("certificate authority has no TLS certificates")
|
|
}
|
|
certPool := x509.NewCertPool()
|
|
for _, keyPair := range keyPairs {
|
|
cert, err := tlsca.ParseCertificatePEM(keyPair.Cert)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
certPool.AddCert(cert)
|
|
}
|
|
return certPool, nil
|
|
}
|
|
|
|
// TLSCerts returns TLS certificates from CA
|
|
func TLSCerts(ca CertAuthority) [][]byte {
|
|
pairs := ca.GetTLSKeyPairs()
|
|
out := make([][]byte, len(pairs))
|
|
for i, pair := range pairs {
|
|
out[i] = append([]byte{}, pair.Cert...)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// NewCertAuthority returns new cert authority
|
|
func NewCertAuthority(
|
|
caType CertAuthType,
|
|
clusterName string,
|
|
signingKeys [][]byte,
|
|
checkingKeys [][]byte,
|
|
roles []string,
|
|
signingAlg CertAuthoritySpecV2_SigningAlgType,
|
|
) CertAuthority {
|
|
return &CertAuthorityV2{
|
|
Kind: KindCertAuthority,
|
|
Version: V2,
|
|
SubKind: string(caType),
|
|
Metadata: Metadata{
|
|
Name: clusterName,
|
|
Namespace: defaults.Namespace,
|
|
},
|
|
Spec: CertAuthoritySpecV2{
|
|
Roles: roles,
|
|
Type: caType,
|
|
ClusterName: clusterName,
|
|
CheckingKeys: checkingKeys,
|
|
SigningKeys: signingKeys,
|
|
SigningAlg: signingAlg,
|
|
},
|
|
}
|
|
}
|
|
|
|
// CertAuthoritiesToV1 converts list of cert authorities to V1 slice
|
|
func CertAuthoritiesToV1(in []CertAuthority) ([]CertAuthorityV1, error) {
|
|
out := make([]CertAuthorityV1, len(in))
|
|
type cav1 interface {
|
|
V1() *CertAuthorityV1
|
|
}
|
|
for i, ca := range in {
|
|
v1, ok := ca.(cav1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("could not transform object to V1")
|
|
}
|
|
out[i] = *(v1.V1())
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// GetVersion returns resource version
|
|
func (c *CertAuthorityV2) GetVersion() string {
|
|
return c.Version
|
|
}
|
|
|
|
// GetKind returns resource kind
|
|
func (c *CertAuthorityV2) GetKind() string {
|
|
return c.Kind
|
|
}
|
|
|
|
// GetSubKind returns resource sub kind
|
|
func (c *CertAuthorityV2) GetSubKind() string {
|
|
return c.SubKind
|
|
}
|
|
|
|
// SetSubKind sets resource subkind
|
|
func (c *CertAuthorityV2) SetSubKind(s string) {
|
|
c.SubKind = s
|
|
}
|
|
|
|
// Clone returns a copy of the cert authority object.
|
|
func (c *CertAuthorityV2) Clone() CertAuthority {
|
|
out := *c
|
|
out.Spec.CheckingKeys = utils.CopyByteSlices(c.Spec.CheckingKeys)
|
|
out.Spec.SigningKeys = utils.CopyByteSlices(c.Spec.SigningKeys)
|
|
for i, kp := range c.Spec.TLSKeyPairs {
|
|
out.Spec.TLSKeyPairs[i] = TLSKeyPair{
|
|
Key: utils.CopyByteSlice(kp.Key),
|
|
Cert: utils.CopyByteSlice(kp.Cert),
|
|
}
|
|
}
|
|
out.Spec.Roles = utils.CopyStrings(c.Spec.Roles)
|
|
return &out
|
|
}
|
|
|
|
// GetRotation returns rotation state.
|
|
func (c *CertAuthorityV2) GetRotation() Rotation {
|
|
if c.Spec.Rotation == nil {
|
|
return Rotation{}
|
|
}
|
|
return *c.Spec.Rotation
|
|
}
|
|
|
|
// SetRotation sets rotation state.
|
|
func (c *CertAuthorityV2) SetRotation(r Rotation) {
|
|
c.Spec.Rotation = &r
|
|
}
|
|
|
|
// TLSCA returns TLS certificate authority
|
|
func (c *CertAuthorityV2) TLSCA() (*tlsca.CertAuthority, error) {
|
|
if len(c.Spec.TLSKeyPairs) == 0 {
|
|
return nil, trace.BadParameter("no TLS key pairs found for certificate authority")
|
|
}
|
|
return tlsca.New(c.Spec.TLSKeyPairs[0].Cert, c.Spec.TLSKeyPairs[0].Key)
|
|
}
|
|
|
|
// SetTLSPrivateKey sets TLS key pairs
|
|
func (c *CertAuthorityV2) SetTLSKeyPairs(pairs []TLSKeyPair) {
|
|
c.Spec.TLSKeyPairs = pairs
|
|
}
|
|
|
|
// GetTLSPrivateKey returns TLS key pairs
|
|
func (c *CertAuthorityV2) GetTLSKeyPairs() []TLSKeyPair {
|
|
return c.Spec.TLSKeyPairs
|
|
}
|
|
|
|
// GetMetadata returns object metadata
|
|
func (c *CertAuthorityV2) GetMetadata() Metadata {
|
|
return c.Metadata
|
|
}
|
|
|
|
// SetExpiry sets expiry time for the object
|
|
func (c *CertAuthorityV2) SetExpiry(expires time.Time) {
|
|
c.Metadata.SetExpiry(expires)
|
|
}
|
|
|
|
// Expires returns object expiry setting
|
|
func (c *CertAuthorityV2) Expiry() time.Time {
|
|
return c.Metadata.Expiry()
|
|
}
|
|
|
|
// SetTTL sets Expires header using realtime clock
|
|
func (c *CertAuthorityV2) SetTTL(clock clockwork.Clock, ttl time.Duration) {
|
|
c.Metadata.SetTTL(clock, ttl)
|
|
}
|
|
|
|
// GetResourceID returns resource ID
|
|
func (c *CertAuthorityV2) GetResourceID() int64 {
|
|
return c.Metadata.ID
|
|
}
|
|
|
|
// SetResourceID sets resource ID
|
|
func (c *CertAuthorityV2) SetResourceID(id int64) {
|
|
c.Metadata.ID = id
|
|
}
|
|
|
|
// WithoutSecrets returns an instance of resource without secrets.
|
|
func (c *CertAuthorityV2) WithoutSecrets() Resource {
|
|
c2 := c.Clone()
|
|
RemoveCASecrets(c2)
|
|
return c2
|
|
}
|
|
|
|
// V2 returns V2 version of the resouirce - itself
|
|
func (c *CertAuthorityV2) V2() *CertAuthorityV2 {
|
|
return c
|
|
}
|
|
|
|
// String returns human readable version of the CertAuthorityV2.
|
|
func (c *CertAuthorityV2) String() string {
|
|
return fmt.Sprintf("CA(name=%v, type=%v)", c.GetClusterName(), c.GetType())
|
|
}
|
|
|
|
// V1 returns V1 version of the object
|
|
func (c *CertAuthorityV2) V1() *CertAuthorityV1 {
|
|
return &CertAuthorityV1{
|
|
Type: c.Spec.Type,
|
|
DomainName: c.Spec.ClusterName,
|
|
CheckingKeys: c.Spec.CheckingKeys,
|
|
SigningKeys: c.Spec.SigningKeys,
|
|
}
|
|
}
|
|
|
|
// AddRole adds a role to ca role list
|
|
func (ca *CertAuthorityV2) AddRole(name string) {
|
|
for _, r := range ca.Spec.Roles {
|
|
if r == name {
|
|
return
|
|
}
|
|
}
|
|
ca.Spec.Roles = append(ca.Spec.Roles, name)
|
|
}
|
|
|
|
// GetSigning keys returns signing keys
|
|
func (ca *CertAuthorityV2) GetSigningKeys() [][]byte {
|
|
return ca.Spec.SigningKeys
|
|
}
|
|
|
|
// SetSigningKeys sets signing keys
|
|
func (ca *CertAuthorityV2) SetSigningKeys(keys [][]byte) error {
|
|
ca.Spec.SigningKeys = keys
|
|
return nil
|
|
}
|
|
|
|
// SetCheckingKeys sets SSH public keys
|
|
func (ca *CertAuthorityV2) SetCheckingKeys(keys [][]byte) error {
|
|
ca.Spec.CheckingKeys = keys
|
|
return nil
|
|
}
|
|
|
|
// GetID returns certificate authority ID -
|
|
// combined type and name
|
|
func (ca *CertAuthorityV2) GetID() CertAuthID {
|
|
return CertAuthID{Type: ca.Spec.Type, DomainName: ca.Metadata.Name}
|
|
}
|
|
|
|
// SetName sets cert authority name
|
|
func (ca *CertAuthorityV2) SetName(name string) {
|
|
ca.Metadata.SetName(name)
|
|
}
|
|
|
|
// GetName returns cert authority name
|
|
func (ca *CertAuthorityV2) GetName() string {
|
|
return ca.Metadata.Name
|
|
}
|
|
|
|
// GetType returns user or host certificate authority
|
|
func (ca *CertAuthorityV2) GetType() CertAuthType {
|
|
return ca.Spec.Type
|
|
}
|
|
|
|
// GetClusterName returns cluster name this cert authority
|
|
// is associated with.
|
|
func (ca *CertAuthorityV2) GetClusterName() string {
|
|
return ca.Spec.ClusterName
|
|
}
|
|
|
|
// GetCheckingKeys returns public keys to check signature
|
|
func (ca *CertAuthorityV2) GetCheckingKeys() [][]byte {
|
|
return ca.Spec.CheckingKeys
|
|
}
|
|
|
|
// GetRoles returns a list of roles assumed by users signed by this CA
|
|
func (ca *CertAuthorityV2) GetRoles() []string {
|
|
return ca.Spec.Roles
|
|
}
|
|
|
|
// SetRoles sets assigned roles for this certificate authority
|
|
func (ca *CertAuthorityV2) SetRoles(roles []string) {
|
|
ca.Spec.Roles = roles
|
|
}
|
|
|
|
// CombinedMapping is used to specify combined mapping from legacy property Roles
|
|
// and new property RoleMap
|
|
func (ca *CertAuthorityV2) CombinedMapping() RoleMap {
|
|
if len(ca.Spec.Roles) != 0 {
|
|
return RoleMap([]RoleMapping{{Remote: Wildcard, Local: ca.Spec.Roles}})
|
|
}
|
|
return RoleMap(ca.Spec.RoleMap)
|
|
}
|
|
|
|
// GetRoleMap returns role map property
|
|
func (ca *CertAuthorityV2) GetRoleMap() RoleMap {
|
|
return RoleMap(ca.Spec.RoleMap)
|
|
}
|
|
|
|
// SetRoleMap sets role map
|
|
func (c *CertAuthorityV2) SetRoleMap(m RoleMap) {
|
|
c.Spec.RoleMap = []RoleMapping(m)
|
|
}
|
|
|
|
// FirstSigningKey returns first signing key or returns error if it's not here
|
|
func (ca *CertAuthorityV2) FirstSigningKey() ([]byte, error) {
|
|
if len(ca.Spec.SigningKeys) == 0 {
|
|
return nil, trace.NotFound("%v has no signing keys", ca.Metadata.Name)
|
|
}
|
|
return ca.Spec.SigningKeys[0], nil
|
|
}
|
|
|
|
// ID returns id (consisting of domain name and type) that
|
|
// identifies the authority this key belongs to
|
|
func (ca *CertAuthorityV2) ID() *CertAuthID {
|
|
return &CertAuthID{DomainName: ca.Spec.ClusterName, Type: ca.Spec.Type}
|
|
}
|
|
|
|
// Checkers returns public keys that can be used to check cert authorities
|
|
func (ca *CertAuthorityV2) Checkers() ([]ssh.PublicKey, error) {
|
|
out := make([]ssh.PublicKey, 0, len(ca.Spec.CheckingKeys))
|
|
for _, keyBytes := range ca.Spec.CheckingKeys {
|
|
key, _, _, _, err := ssh.ParseAuthorizedKey(keyBytes)
|
|
if err != nil {
|
|
return nil, trace.BadParameter("invalid authority public key (len=%d): %v", len(keyBytes), err)
|
|
}
|
|
out = append(out, key)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// Signers returns a list of signers that could be used to sign keys.
|
|
func (ca *CertAuthorityV2) Signers() ([]ssh.Signer, error) {
|
|
out := make([]ssh.Signer, 0, len(ca.Spec.SigningKeys))
|
|
for _, keyBytes := range ca.Spec.SigningKeys {
|
|
signer, err := ssh.ParsePrivateKey(keyBytes)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
signer = sshutils.AlgSigner(signer, ca.GetSigningAlg())
|
|
out = append(out, signer)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (ca *CertAuthorityV2) GetSigningAlg() string {
|
|
switch ca.Spec.SigningAlg {
|
|
// UNKNOWN algorithm can come from a cluster that existed before SigningAlg
|
|
// field was added. Default to RSA-SHA1 to match the implicit algorithm
|
|
// used in those clusters.
|
|
case CertAuthoritySpecV2_RSA_SHA1, CertAuthoritySpecV2_UNKNOWN:
|
|
return ssh.SigAlgoRSA
|
|
case CertAuthoritySpecV2_RSA_SHA2_256:
|
|
return ssh.SigAlgoRSASHA2256
|
|
case CertAuthoritySpecV2_RSA_SHA2_512:
|
|
return ssh.SigAlgoRSASHA2512
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func (ca *CertAuthorityV2) SetSigningAlg(alg string) {
|
|
ca.Spec.SigningAlg = ParseSigningAlg(alg)
|
|
}
|
|
|
|
// Check checks if all passed parameters are valid
|
|
func (ca *CertAuthorityV2) Check() error {
|
|
err := ca.ID().Check()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
_, err = ca.Checkers()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
_, err = ca.Signers()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
// This is to force users to migrate
|
|
if len(ca.Spec.Roles) != 0 && len(ca.Spec.RoleMap) != 0 {
|
|
return trace.BadParameter("should set either 'roles' or 'role_map', not both")
|
|
}
|
|
if err := RoleMap(ca.Spec.RoleMap).Check(); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and set default values for any missing fields.
|
|
func (ca *CertAuthorityV2) CheckAndSetDefaults() error {
|
|
err := ca.Metadata.CheckAndSetDefaults()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
err = ca.Check()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ParseSigningAlg converts the SSH signature algorithm strings to the
|
|
// corresponding proto enum value.
|
|
//
|
|
// alg should be one of ssh.SigAlgo* constants. If it's not one of those
|
|
// constants, CertAuthoritySpecV2_UNKNOWN is returned.
|
|
func ParseSigningAlg(alg string) CertAuthoritySpecV2_SigningAlgType {
|
|
switch alg {
|
|
case ssh.SigAlgoRSA:
|
|
return CertAuthoritySpecV2_RSA_SHA1
|
|
case ssh.SigAlgoRSASHA2256:
|
|
return CertAuthoritySpecV2_RSA_SHA2_256
|
|
case ssh.SigAlgoRSASHA2512:
|
|
return CertAuthoritySpecV2_RSA_SHA2_512
|
|
default:
|
|
return CertAuthoritySpecV2_UNKNOWN
|
|
}
|
|
}
|
|
|
|
// RemoveCASecrets removes secret values and keys
|
|
// from the certificate authority
|
|
func RemoveCASecrets(ca CertAuthority) {
|
|
ca.SetSigningKeys(nil)
|
|
keyPairs := ca.GetTLSKeyPairs()
|
|
for i := range keyPairs {
|
|
keyPairs[i].Key = nil
|
|
}
|
|
ca.SetTLSKeyPairs(keyPairs)
|
|
}
|
|
|
|
const (
|
|
// RotationStateStandby is initial status of the rotation -
|
|
// nothing is being rotated.
|
|
RotationStateStandby = "standby"
|
|
// RotationStateInProgress - that rotation is in progress.
|
|
RotationStateInProgress = "in_progress"
|
|
// RotationPhaseStandby is the initial phase of the rotation
|
|
// it means no operations have started.
|
|
RotationPhaseStandby = "standby"
|
|
// RotationPhaseInit = is a phase of the rotation
|
|
// when new certificate authoirty is issued, but not used
|
|
// It is necessary for remote trusted clusters to fetch the
|
|
// new certificate authority, otherwise the new clients
|
|
// will reject it
|
|
RotationPhaseInit = "init"
|
|
// RotationPhaseUpdateClients is a phase of the rotation
|
|
// when client credentials will have to be updated and reloaded
|
|
// but servers will use and respond with old credentials
|
|
// because clients have no idea about new credentials at first.
|
|
RotationPhaseUpdateClients = "update_clients"
|
|
// RotationPhaseUpdateServers is a phase of the rotation
|
|
// when servers will have to reload and should start serving
|
|
// TLS and SSH certificates signed by new CA.
|
|
RotationPhaseUpdateServers = "update_servers"
|
|
// RotationPhaseRollback means that rotation is rolling
|
|
// back to the old certificate authority.
|
|
RotationPhaseRollback = "rollback"
|
|
// RotationModeManual is a manual rotation mode when all phases
|
|
// are set by the operator.
|
|
RotationModeManual = "manual"
|
|
// RotationModeAuto is set to go through all phases by the schedule.
|
|
RotationModeAuto = "auto"
|
|
)
|
|
|
|
// RotatePhases lists all supported rotation phases
|
|
var RotatePhases = []string{
|
|
RotationPhaseInit,
|
|
RotationPhaseStandby,
|
|
RotationPhaseUpdateClients,
|
|
RotationPhaseUpdateServers,
|
|
RotationPhaseRollback,
|
|
}
|
|
|
|
// Matches returns true if this state rotation matches
|
|
// external rotation state, phase and rotation ID should match,
|
|
// notice that matches does not behave like Equals because it does not require
|
|
// all fields to be the same.
|
|
func (s *Rotation) Matches(rotation Rotation) bool {
|
|
return s.CurrentID == rotation.CurrentID && s.State == rotation.State && s.Phase == rotation.Phase
|
|
}
|
|
|
|
// LastRotatedDescription returns human friendly description.
|
|
func (r *Rotation) LastRotatedDescription() string {
|
|
if r.LastRotated.IsZero() {
|
|
return "never updated"
|
|
}
|
|
return fmt.Sprintf("last rotated %v", r.LastRotated.Format(teleport.HumanDateFormatSeconds))
|
|
}
|
|
|
|
// PhaseDescription returns human friendly description of a current rotation phase.
|
|
func (r *Rotation) PhaseDescription() string {
|
|
switch r.Phase {
|
|
case RotationPhaseInit:
|
|
return "initialized"
|
|
case RotationPhaseStandby, "":
|
|
return "on standby"
|
|
case RotationPhaseUpdateClients:
|
|
return "rotating clients"
|
|
case RotationPhaseUpdateServers:
|
|
return "rotating servers"
|
|
case RotationPhaseRollback:
|
|
return "rolling back"
|
|
default:
|
|
return fmt.Sprintf("unknown phase: %q", r.Phase)
|
|
}
|
|
}
|
|
|
|
// String returns user friendly information about certificate authority.
|
|
func (r *Rotation) String() string {
|
|
switch r.State {
|
|
case "", RotationStateStandby:
|
|
if r.LastRotated.IsZero() {
|
|
return "never updated"
|
|
}
|
|
return fmt.Sprintf("rotated %v", r.LastRotated.Format(teleport.HumanDateFormatSeconds))
|
|
case RotationStateInProgress:
|
|
return fmt.Sprintf("%v (mode: %v, started: %v, ending: %v)",
|
|
r.PhaseDescription(),
|
|
r.Mode,
|
|
r.Started.Format(teleport.HumanDateFormatSeconds),
|
|
r.Started.Add(r.GracePeriod.Duration()).Format(teleport.HumanDateFormatSeconds),
|
|
)
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets default rotation parameters.
|
|
func (r *Rotation) CheckAndSetDefaults(clock clockwork.Clock) error {
|
|
switch r.Phase {
|
|
case "", RotationPhaseRollback, RotationPhaseUpdateClients, RotationPhaseUpdateServers:
|
|
default:
|
|
return trace.BadParameter("unsupported phase: %q", r.Phase)
|
|
}
|
|
switch r.Mode {
|
|
case "", RotationModeAuto, RotationModeManual:
|
|
default:
|
|
return trace.BadParameter("unsupported mode: %q", r.Mode)
|
|
}
|
|
switch r.State {
|
|
case "":
|
|
r.State = RotationStateStandby
|
|
case RotationStateStandby:
|
|
case RotationStateInProgress:
|
|
if r.CurrentID == "" {
|
|
return trace.BadParameter("set 'current_id' parameter for in progress rotation")
|
|
}
|
|
if r.Started.IsZero() {
|
|
return trace.BadParameter("set 'started' parameter for in progress rotation")
|
|
}
|
|
default:
|
|
return trace.BadParameter(
|
|
"unsupported rotation 'state': %q, supported states are: %q, %q",
|
|
r.State, RotationStateStandby, RotationStateInProgress)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GenerateSchedule generates schedule based on the time period, using
|
|
// even time periods between rotation phases.
|
|
func GenerateSchedule(clock clockwork.Clock, gracePeriod time.Duration) (*RotationSchedule, error) {
|
|
if gracePeriod <= 0 {
|
|
return nil, trace.BadParameter("invalid grace period %q, provide value > 0", gracePeriod)
|
|
}
|
|
return &RotationSchedule{
|
|
UpdateClients: clock.Now().UTC().Add(gracePeriod / 3).UTC(),
|
|
UpdateServers: clock.Now().UTC().Add((gracePeriod * 2) / 3).UTC(),
|
|
Standby: clock.Now().UTC().Add(gracePeriod).UTC(),
|
|
}, nil
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets default values of the rotation schedule.
|
|
func (s *RotationSchedule) CheckAndSetDefaults(clock clockwork.Clock) error {
|
|
if s.UpdateServers.IsZero() {
|
|
return trace.BadParameter("phase %q has no time switch scheduled", RotationPhaseUpdateServers)
|
|
}
|
|
if s.Standby.IsZero() {
|
|
return trace.BadParameter("phase %q has no time switch scheduled", RotationPhaseStandby)
|
|
}
|
|
if s.Standby.Before(s.UpdateServers) {
|
|
return trace.BadParameter("phase %q can not be scheduled before %q", RotationPhaseStandby, RotationPhaseUpdateServers)
|
|
}
|
|
if s.UpdateServers.Before(clock.Now()) {
|
|
return trace.BadParameter("phase %q can not be scheduled in the past", RotationPhaseUpdateServers)
|
|
}
|
|
if s.Standby.Before(clock.Now()) {
|
|
return trace.BadParameter("phase %q can not be scheduled in the past", RotationPhaseStandby)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CertAuthoritySpecV2Schema is JSON schema for cert authority V2
|
|
const CertAuthoritySpecV2Schema = `{
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"required": ["type", "cluster_name", "checking_keys"],
|
|
"properties": {
|
|
"type": {"type": "string"},
|
|
"cluster_name": {"type": "string"},
|
|
"checking_keys": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"signing_keys": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"roles": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"tls_key_pairs": {
|
|
"type": "array",
|
|
"items": {
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"cert": {"type": "string"},
|
|
"key": {"type": "string"}
|
|
}
|
|
}
|
|
},
|
|
"signing_alg": {"type": "integer"},
|
|
"rotation": %v,
|
|
"role_map": %v
|
|
}
|
|
}`
|
|
|
|
// RotationSchema is a JSON validation schema of the CA rotation state object.
|
|
const RotationSchema = `{
|
|
"type": "object",
|
|
"additionalProperties": false,
|
|
"properties": {
|
|
"state": {"type": "string"},
|
|
"phase": {"type": "string"},
|
|
"mode": {"type": "string"},
|
|
"current_id": {"type": "string"},
|
|
"started": {"type": "string"},
|
|
"grace_period": {"type": "string"},
|
|
"last_rotated": {"type": "string"},
|
|
"schedule": {
|
|
"type": "object",
|
|
"properties": {
|
|
"update_clients": {"type": "string"},
|
|
"update_servers": {"type": "string"},
|
|
"standby": {"type": "string"}
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
// CertAuthorityV1 is a host or user certificate authority that
|
|
// can check and if it has private key stored as well, sign it too
|
|
type CertAuthorityV1 struct {
|
|
// Type is either user or host certificate authority
|
|
Type CertAuthType `json:"type"`
|
|
// DomainName identifies domain name this authority serves,
|
|
// for host authorities that means base hostname of all servers,
|
|
// for user authorities that means organization name
|
|
DomainName string `json:"domain_name"`
|
|
// Checkers is a list of SSH public keys that can be used to check
|
|
// certificate signatures
|
|
CheckingKeys [][]byte `json:"checking_keys"`
|
|
// SigningKeys is a list of private keys used for signing
|
|
SigningKeys [][]byte `json:"signing_keys"`
|
|
// AllowedLogins is a list of allowed logins for users within
|
|
// this certificate authority
|
|
AllowedLogins []string `json:"allowed_logins"`
|
|
}
|
|
|
|
// CombinedMapping is used to specify combined mapping from legacy property Roles
|
|
// and new property RoleMap
|
|
func (ca *CertAuthorityV1) CombinedMapping() RoleMap {
|
|
return []RoleMapping{}
|
|
}
|
|
|
|
// GetRoleMap returns role map property
|
|
func (ca *CertAuthorityV1) GetRoleMap() RoleMap {
|
|
return nil
|
|
}
|
|
|
|
// SetRoleMap sets role map
|
|
func (c *CertAuthorityV1) SetRoleMap(m RoleMap) {
|
|
}
|
|
|
|
// V1 returns V1 version of the resource
|
|
func (c *CertAuthorityV1) V1() *CertAuthorityV1 {
|
|
return c
|
|
}
|
|
|
|
// V2 returns V2 version of the resource
|
|
func (c *CertAuthorityV1) V2() *CertAuthorityV2 {
|
|
return &CertAuthorityV2{
|
|
Kind: KindCertAuthority,
|
|
Version: V2,
|
|
Metadata: Metadata{
|
|
Name: c.DomainName,
|
|
Namespace: defaults.Namespace,
|
|
},
|
|
Spec: CertAuthoritySpecV2{
|
|
Type: c.Type,
|
|
ClusterName: c.DomainName,
|
|
CheckingKeys: c.CheckingKeys,
|
|
SigningKeys: c.SigningKeys,
|
|
},
|
|
}
|
|
}
|
|
|
|
// String returns human readable version of the CertAuthorityV1.
|
|
func (c *CertAuthorityV1) String() string {
|
|
return fmt.Sprintf("CA(name=%v, type=%v)", c.DomainName, c.Type)
|
|
}
|
|
|
|
var certAuthorityMarshaler CertAuthorityMarshaler = &TeleportCertAuthorityMarshaler{}
|
|
|
|
// SetCertAuthorityMarshaler sets global user marshaler
|
|
func SetCertAuthorityMarshaler(u CertAuthorityMarshaler) {
|
|
marshalerMutex.Lock()
|
|
defer marshalerMutex.Unlock()
|
|
certAuthorityMarshaler = u
|
|
}
|
|
|
|
// GetCertAuthorityMarshaler returns currently set user marshaler
|
|
func GetCertAuthorityMarshaler() CertAuthorityMarshaler {
|
|
marshalerMutex.RLock()
|
|
defer marshalerMutex.RUnlock()
|
|
return certAuthorityMarshaler
|
|
}
|
|
|
|
// CertAuthorityMarshaler implements marshal/unmarshal of User implementations
|
|
// mostly adds support for extended versions
|
|
type CertAuthorityMarshaler interface {
|
|
// UnmarshalCertAuthority unmarhsals cert authority from binary representation
|
|
UnmarshalCertAuthority(bytes []byte, opts ...MarshalOption) (CertAuthority, error)
|
|
// MarshalCertAuthority to binary representation
|
|
MarshalCertAuthority(c CertAuthority, opts ...MarshalOption) ([]byte, error)
|
|
// GenerateCertAuthority is used to generate new cert authority
|
|
// based on standard teleport one and is used to add custom
|
|
// parameters and extend it in extensions of teleport
|
|
GenerateCertAuthority(CertAuthority) (CertAuthority, error)
|
|
}
|
|
|
|
// GetCertAuthoritySchema returns JSON Schema for cert authorities
|
|
func GetCertAuthoritySchema() string {
|
|
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, fmt.Sprintf(CertAuthoritySpecV2Schema, RotationSchema, RoleMapSchema), DefaultDefinitions)
|
|
}
|
|
|
|
type TeleportCertAuthorityMarshaler struct{}
|
|
|
|
// GenerateCertAuthority is used to generate new cert authority
|
|
// based on standard teleport one and is used to add custom
|
|
// parameters and extend it in extensions of teleport
|
|
func (*TeleportCertAuthorityMarshaler) GenerateCertAuthority(ca CertAuthority) (CertAuthority, error) {
|
|
return ca, nil
|
|
}
|
|
|
|
// UnmarshalCertAuthority unmarshals cert authority from JSON
|
|
func (*TeleportCertAuthorityMarshaler) UnmarshalCertAuthority(bytes []byte, opts ...MarshalOption) (CertAuthority, error) {
|
|
cfg, err := collectOptions(opts)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var h ResourceHeader
|
|
err = utils.FastUnmarshal(bytes, &h)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
switch h.Version {
|
|
case "":
|
|
var ca CertAuthorityV1
|
|
err := json.Unmarshal(bytes, &ca)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return ca.V2(), nil
|
|
case V2:
|
|
var ca CertAuthorityV2
|
|
if cfg.SkipValidation {
|
|
if err := utils.FastUnmarshal(bytes, &ca); err != nil {
|
|
return nil, trace.BadParameter(err.Error())
|
|
}
|
|
} else {
|
|
if err := utils.UnmarshalWithSchema(GetCertAuthoritySchema(), &ca, bytes); err != nil {
|
|
return nil, trace.BadParameter(err.Error())
|
|
}
|
|
}
|
|
if err := ca.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if cfg.ID != 0 {
|
|
ca.SetResourceID(cfg.ID)
|
|
}
|
|
return &ca, nil
|
|
}
|
|
|
|
return nil, trace.BadParameter("cert authority resource version %v is not supported", h.Version)
|
|
}
|
|
|
|
// MarshalCertAuthority marshalls cert authority into JSON
|
|
func (*TeleportCertAuthorityMarshaler) MarshalCertAuthority(ca CertAuthority, opts ...MarshalOption) ([]byte, error) {
|
|
cfg, err := collectOptions(opts)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
type cav1 interface {
|
|
V1() *CertAuthorityV1
|
|
}
|
|
|
|
type cav2 interface {
|
|
V2() *CertAuthorityV2
|
|
}
|
|
version := cfg.GetVersion()
|
|
switch version {
|
|
case V1:
|
|
v, ok := ca.(cav1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("don't know how to marshal %v", V1)
|
|
}
|
|
return utils.FastMarshal(v.V1())
|
|
case V2:
|
|
v, ok := ca.(cav2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("don't know how to marshal %v", V2)
|
|
}
|
|
v2 := v.V2()
|
|
if !cfg.PreserveResourceID {
|
|
// avoid modifying the original object
|
|
// to prevent unexpected data races
|
|
copy := *v2
|
|
copy.SetResourceID(0)
|
|
v2 = ©
|
|
}
|
|
return utils.FastMarshal(v2)
|
|
default:
|
|
return nil, trace.BadParameter("version %v is not supported", version)
|
|
}
|
|
}
|