teleport/lib/services/authority.go
2017-05-17 20:43:21 -07:00

601 lines
18 KiB
Go

package services
import (
"encoding/json"
"fmt"
"time"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
"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
// 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
// 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
}
func (c *HostCertParams) Check() error {
if c.HostID == "" || c.ClusterName == "" {
return trace.BadParameter("HostID [%q] and ClusterName [%q] are required",
c.HostID, c.ClusterName)
}
if err := c.Roles.Check(); err != nil {
return trace.Wrap(err)
}
return nil
}
// 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
// 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
// PermitAgentForwarding permits agent forwarding for this cert
PermitAgentForwarding bool
// Roles is a list of roles assigned to this user
Roles []string
}
// 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{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 {
// Resource sets common resource properties
Resource
// 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
// GetSigning keys 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)
// GetRawObject returns raw object data, used for migrations
GetRawObject() interface{}
// Check checks object for errors
Check() error
// SetSigningKeys sets signing keys
SetSigningKeys([][]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
}
// NewCertAuthority returns new cert authority
func NewCertAuthority(caType CertAuthType, clusterName string, signingKeys, checkingKeys [][]byte, roles []string) CertAuthority {
return &CertAuthorityV2{
Kind: KindCertAuthority,
Version: V2,
Metadata: Metadata{
Name: clusterName,
Namespace: defaults.Namespace,
},
Spec: CertAuthoritySpecV2{
Roles: roles,
Type: caType,
ClusterName: clusterName,
CheckingKeys: checkingKeys,
SigningKeys: signingKeys,
},
}
}
// 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
}
// CertAuthorityV2 is version 1 resource spec for Cert Authority
type CertAuthorityV2 struct {
// Kind is a resource kind
Kind string `json:"kind"`
// Version is version
Version string `json:"version"`
// Metadata is connector metadata
Metadata Metadata `json:"metadata"`
// Spec contains cert authority specification
Spec CertAuthoritySpecV2 `json:"spec"`
// rawObject is object that is raw object stored in DB
// without any conversions applied, used in migrations
rawObject interface{}
}
// 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 retuns 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)
}
// V2 returns V2 version of the resouirce - itself
func (c *CertAuthorityV2) V2() *CertAuthorityV2 {
return c
}
// 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
}
// 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 []RoleMapping{{Remote: Wildcard, Local: ca.Spec.Roles}}
}
return ca.Spec.RoleMap
}
// GetRoleMap returns role map property
func (ca *CertAuthorityV2) GetRoleMap() RoleMap {
return ca.Spec.RoleMap
}
// SetRoleMap sets role map
func (c *CertAuthorityV2) SetRoleMap(m RoleMap) {
c.Spec.RoleMap = m
}
// GetRawObject returns raw object data, used for migrations
func (ca *CertAuthorityV2) GetRawObject() interface{} {
return ca.rawObject
}
// 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)
}
out = append(out, signer)
}
return out, nil
}
// 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 := ca.Spec.RoleMap.Check(); err != nil {
return trace.Wrap(err)
}
return nil
}
// CertAuthoritySpecV2 is a host or user certificate authority that
// can check and if it has private key stored as well, sign it too
type CertAuthoritySpecV2 struct {
// Type is either user or host certificate authority
Type CertAuthType `json:"type"`
// ClusterName identifies cluster name this authority serves,
// for host authorities that means base hostname of all servers,
// for user authorities that means organization name
ClusterName string `json:"cluster_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,omitempty"`
// Roles is a list of roles assumed by users signed by this CA
Roles []string `json:"roles,omitempty"`
// RoleMap specifies role mappings to remote roles
RoleMap RoleMap `json:"role_map,omitempty"`
}
// 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"
}
},
"role_map": %v
}
}`
// 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,
},
rawObject: *c,
}
}
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) (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, RoleMapSchema))
}
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
}
// UnmarshalUser unmarshals user from JSON
func (*TeleportCertAuthorityMarshaler) UnmarshalCertAuthority(bytes []byte) (CertAuthority, error) {
var h ResourceHeader
err := json.Unmarshal(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 err := utils.UnmarshalWithSchema(GetCertAuthoritySchema(), &ca, bytes); err != nil {
return nil, trace.BadParameter(err.Error())
}
utils.UTC(&ca.Metadata.Expires)
return &ca, nil
}
return nil, trace.BadParameter("cert authority resource version %v is not supported", h.Version)
}
// MarshalUser 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 json.Marshal(v.V1())
case V2:
v, ok := ca.(cav2)
if !ok {
return nil, trace.BadParameter("don't know how to marshal %v", V2)
}
return json.Marshal(v.V2())
default:
return nil, trace.BadParameter("version %v is not supported", version)
}
}