teleport/lib/services/saml.go

816 lines
25 KiB
Go

/*
Copyright 2015-2020 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"
"crypto/x509/pkix"
"encoding/base64"
"encoding/xml"
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
saml2 "github.com/russellhaering/gosaml2"
"github.com/russellhaering/gosaml2/types"
dsig "github.com/russellhaering/goxmldsig"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
log "github.com/sirupsen/logrus"
)
// SAMLConnector specifies configuration for SAML 2.0 dentity providers
type SAMLConnector interface {
// ResourceWithSecrets provides common methods for objects
ResourceWithSecrets
// GetDisplay returns display - friendly name for this provider.
GetDisplay() string
// SetDisplay sets friendly name for this provider.
SetDisplay(string)
// GetAttributesToRoles returns attributes to roles mapping
GetAttributesToRoles() []AttributeMapping
// SetAttributesToRoles sets attributes to roles mapping
SetAttributesToRoles(mapping []AttributeMapping)
// GetAttributes returns list of attributes expected by mappings
GetAttributes() []string
// MapAttributes maps attributes to roles
MapAttributes(assertionInfo saml2.AssertionInfo) []string
// Check checks SAML connector for errors
CheckAndSetDefaults() error
// SetIssuer sets issuer
SetIssuer(issuer string)
// GetIssuer returns issuer
GetIssuer() string
// GetSigningKeyPair returns signing key pair
GetSigningKeyPair() *SigningKeyPair
// GetSigningKeyPair sets signing key pair
SetSigningKeyPair(k *SigningKeyPair)
// Equals returns true if the connectors are identical
Equals(other SAMLConnector) bool
// GetSSO returns SSO service
GetSSO() string
// SetSSO sets SSO service
SetSSO(string)
// GetEntityDescriptor returns XML entity descriptor of the service
GetEntityDescriptor() string
// SetEntityDescriptor sets entity descritor of the service
SetEntityDescriptor(v string)
// GetEntityDescriptorURL returns the URL to obtain the entity descriptor.
GetEntityDescriptorURL() string
// SetEntityDescriptorURL sets the entity descriptor url.
SetEntityDescriptorURL(string)
// GetCert returns identity provider checking x509 certificate
GetCert() string
// SetCert sets identity provider checking certificate
SetCert(string)
// GetServiceProviderIssuer returns service provider issuer
GetServiceProviderIssuer() string
// SetServiceProviderIssuer sets service provider issuer
SetServiceProviderIssuer(v string)
// GetAudience returns audience
GetAudience() string
// SetAudience sets audience
SetAudience(v string)
// GetServiceProvider initialises service provider spec from settings
GetServiceProvider(clock clockwork.Clock) (*saml2.SAMLServiceProvider, error)
// GetAssertionConsumerService returns assertion consumer service URL
GetAssertionConsumerService() string
// SetAssertionConsumerService sets assertion consumer service URL
SetAssertionConsumerService(v string)
// GetProvider returns the identity provider.
GetProvider() string
// SetProvider sets the identity provider.
SetProvider(string)
}
// NewSAMLConnector returns a new SAMLConnector based off a name and SAMLConnectorSpecV2.
func NewSAMLConnector(name string, spec SAMLConnectorSpecV2) SAMLConnector {
return &SAMLConnectorV2{
Kind: KindSAMLConnector,
Version: V2,
Metadata: Metadata{
Name: name,
Namespace: defaults.Namespace,
},
Spec: spec,
}
}
var samlConnectorMarshaler SAMLConnectorMarshaler = &TeleportSAMLConnectorMarshaler{}
// SetSAMLConnectorMarshaler sets global user marshaler
func SetSAMLConnectorMarshaler(m SAMLConnectorMarshaler) {
marshalerMutex.Lock()
defer marshalerMutex.Unlock()
samlConnectorMarshaler = m
}
// GetSAMLConnectorMarshaler returns currently set user marshaler
func GetSAMLConnectorMarshaler() SAMLConnectorMarshaler {
marshalerMutex.RLock()
defer marshalerMutex.RUnlock()
return samlConnectorMarshaler
}
// SAMLConnectorMarshaler implements marshal/unmarshal of User implementations
// mostly adds support for extended versions
type SAMLConnectorMarshaler interface {
// UnmarshalSAMLConnector unmarshals connector from binary representation
UnmarshalSAMLConnector(bytes []byte, opts ...MarshalOption) (SAMLConnector, error)
// MarshalSAMLConnector marshals connector to binary representation
MarshalSAMLConnector(c SAMLConnector, opts ...MarshalOption) ([]byte, error)
}
// GetSAMLConnectorSchema returns schema for SAMLConnector
func GetSAMLConnectorSchema() string {
return fmt.Sprintf(SAMLConnectorV2SchemaTemplate, MetadataSchema, SAMLConnectorSpecV2Schema)
}
type TeleportSAMLConnectorMarshaler struct{}
// UnmarshalSAMLConnector unmarshals connector from
func (*TeleportSAMLConnectorMarshaler) UnmarshalSAMLConnector(bytes []byte, opts ...MarshalOption) (SAMLConnector, 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 V2:
var c SAMLConnectorV2
if cfg.SkipValidation {
if err := utils.FastUnmarshal(bytes, &c); err != nil {
return nil, trace.BadParameter(err.Error())
}
} else {
if err := utils.UnmarshalWithSchema(GetSAMLConnectorSchema(), &c, bytes); err != nil {
return nil, trace.BadParameter(err.Error())
}
}
if err := c.Metadata.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
if cfg.ID != 0 {
c.SetResourceID(cfg.ID)
}
if !cfg.Expires.IsZero() {
c.SetExpiry(cfg.Expires)
}
return &c, nil
}
return nil, trace.BadParameter("SAML connector resource version %v is not supported", h.Version)
}
// MarshalSAMLConnector marshals SAML connector into JSON
func (*TeleportSAMLConnectorMarshaler) MarshalSAMLConnector(c SAMLConnector, opts ...MarshalOption) ([]byte, error) {
cfg, err := collectOptions(opts)
if err != nil {
return nil, trace.Wrap(err)
}
type connv2 interface {
V2() *SAMLConnectorV2
}
version := cfg.GetVersion()
switch version {
case V2:
v, ok := c.(connv2)
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 = &copy
}
return utils.FastMarshal(v2)
default:
return nil, trace.BadParameter("version %v is not supported", version)
}
}
// SAMLConnectorV2 is version 1 resource spec for SAML connector
type SAMLConnectorV2 struct {
// Kind is a resource kind
Kind string `json:"kind"`
// SubKind is a resource sub kind
SubKind string `json:"sub_kind,omitempty"`
// Version is version
Version string `json:"version"`
// Metadata is connector metadata
Metadata Metadata `json:"metadata"`
// Spec contains connector specification
Spec SAMLConnectorSpecV2 `json:"spec"`
}
// GetVersion returns resource version
func (o *SAMLConnectorV2) GetVersion() string {
return o.Version
}
// GetKind returns resource kind
func (o *SAMLConnectorV2) GetKind() string {
return o.Kind
}
// GetSubKind returns resource sub kind
func (o *SAMLConnectorV2) GetSubKind() string {
return o.SubKind
}
// SetSubKind sets resource subkind
func (o *SAMLConnectorV2) SetSubKind(sk string) {
o.SubKind = sk
}
// GetResourceID returns resource ID
func (o *SAMLConnectorV2) GetResourceID() int64 {
return o.Metadata.ID
}
// SetResourceID sets resource ID
func (o *SAMLConnectorV2) SetResourceID(id int64) {
o.Metadata.ID = id
}
// WithoutSecrets returns an instance of resource without secrets.
func (o *SAMLConnectorV2) WithoutSecrets() Resource {
k := o.GetSigningKeyPair()
if k == nil {
return o
}
k2 := *k
k2.PrivateKey = ""
o2 := *o
o2.SetSigningKeyPair(&k2)
return &o2
}
// GetServiceProviderIssuer returns service provider issuer
func (o *SAMLConnectorV2) GetServiceProviderIssuer() string {
return o.Spec.ServiceProviderIssuer
}
// SetServiceProviderIssuer sets service provider issuer
func (o *SAMLConnectorV2) SetServiceProviderIssuer(v string) {
o.Spec.ServiceProviderIssuer = v
}
// GetAudience returns audience
func (o *SAMLConnectorV2) GetAudience() string {
return o.Spec.Audience
}
// SetAudience sets audience
func (o *SAMLConnectorV2) SetAudience(v string) {
o.Spec.Audience = v
}
// GetCert returns identity provider checking x509 certificate
func (o *SAMLConnectorV2) GetCert() string {
return o.Spec.Cert
}
// SetCert sets identity provider checking certificate
func (o *SAMLConnectorV2) SetCert(cert string) {
o.Spec.Cert = cert
}
// GetSSO returns SSO service
func (o *SAMLConnectorV2) GetSSO() string {
return o.Spec.SSO
}
// SetSSO sets SSO service
func (o *SAMLConnectorV2) SetSSO(sso string) {
o.Spec.SSO = sso
}
// GetEntityDescriptor returns XML entity descriptor of the service
func (o *SAMLConnectorV2) GetEntityDescriptor() string {
return o.Spec.EntityDescriptor
}
// SetEntityDescriptor sets entity descritor of the service
func (o *SAMLConnectorV2) SetEntityDescriptor(v string) {
o.Spec.EntityDescriptor = v
}
// GetEntityDescriptorURL returns the URL to obtain the entity descriptor.
func (o *SAMLConnectorV2) GetEntityDescriptorURL() string {
return o.Spec.EntityDescriptorURL
}
// SetEntityDescriptorURL sets the entity descriptor url.
func (o *SAMLConnectorV2) SetEntityDescriptorURL(v string) {
o.Spec.EntityDescriptorURL = v
}
// GetAssertionConsumerService returns assertion consumer service URL
func (o *SAMLConnectorV2) GetAssertionConsumerService() string {
return o.Spec.AssertionConsumerService
}
// SetAssertionConsumerService sets assertion consumer service URL
func (o *SAMLConnectorV2) SetAssertionConsumerService(v string) {
o.Spec.AssertionConsumerService = v
}
// Equals returns true if the connectors are identical
func (o *SAMLConnectorV2) Equals(other SAMLConnector) bool {
if o.GetName() != other.GetName() {
return false
}
if o.GetCert() != other.GetCert() {
return false
}
if o.GetAudience() != other.GetAudience() {
return false
}
if o.GetEntityDescriptor() != other.GetEntityDescriptor() {
return false
}
if o.Expiry() != other.Expiry() {
return false
}
if o.GetIssuer() != other.GetIssuer() {
return false
}
if (o.GetSigningKeyPair() == nil && other.GetSigningKeyPair() != nil) || (o.GetSigningKeyPair() != nil && other.GetSigningKeyPair() == nil) {
return false
}
if o.GetSigningKeyPair() != nil {
a, b := o.GetSigningKeyPair(), other.GetSigningKeyPair()
if a.Cert != b.Cert || a.PrivateKey != b.PrivateKey {
return false
}
}
mappings := o.GetAttributesToRoles()
otherMappings := other.GetAttributesToRoles()
if len(mappings) != len(otherMappings) {
return false
}
for i := range mappings {
a, b := mappings[i], otherMappings[i]
if a.Name != b.Name || a.Value != b.Value || !utils.StringSlicesEqual(a.Roles, b.Roles) {
return false
}
if (a.RoleTemplate != nil && b.RoleTemplate == nil) || (a.RoleTemplate == nil && b.RoleTemplate != nil) {
return false
}
if a.RoleTemplate != nil && !a.RoleTemplate.Equals(b.RoleTemplate.V3()) {
return false
}
}
return o.GetSSO() == other.GetSSO()
}
// V2 returns V2 version of the resource
func (o *SAMLConnectorV2) V2() *SAMLConnectorV2 {
return o
}
// SetDisplay sets friendly name for this provider.
func (o *SAMLConnectorV2) SetDisplay(display string) {
o.Spec.Display = display
}
// GetMetadata returns object metadata
func (o *SAMLConnectorV2) GetMetadata() Metadata {
return o.Metadata
}
// SetExpiry sets expiry time for the object
func (o *SAMLConnectorV2) SetExpiry(expires time.Time) {
o.Metadata.SetExpiry(expires)
}
// Expires returns object expiry setting
func (o *SAMLConnectorV2) Expiry() time.Time {
return o.Metadata.Expiry()
}
// SetTTL sets Expires header using realtime clock
func (o *SAMLConnectorV2) SetTTL(clock clockwork.Clock, ttl time.Duration) {
o.Metadata.SetTTL(clock, ttl)
}
// GetName returns the name of the connector
func (o *SAMLConnectorV2) GetName() string {
return o.Metadata.GetName()
}
// SetName sets client secret to some value
func (o *SAMLConnectorV2) SetName(name string) {
o.Metadata.SetName(name)
}
// SetIssuer sets issuer
func (o *SAMLConnectorV2) SetIssuer(issuer string) {
o.Spec.Issuer = issuer
}
// GetIssuer returns issuer
func (o *SAMLConnectorV2) GetIssuer() string {
return o.Spec.Issuer
}
// Display - Friendly name for this provider.
func (o *SAMLConnectorV2) GetDisplay() string {
if o.Spec.Display != "" {
return o.Spec.Display
}
return o.GetName()
}
// GetAttributesToRoles returns attributes to roles mapping
func (o *SAMLConnectorV2) GetAttributesToRoles() []AttributeMapping {
return o.Spec.AttributesToRoles
}
// SetAttributesToRoles sets attributes to roles mapping
func (o *SAMLConnectorV2) SetAttributesToRoles(mapping []AttributeMapping) {
o.Spec.AttributesToRoles = mapping
}
// SetProvider sets the identity provider.
func (o *SAMLConnectorV2) SetProvider(identityProvider string) {
o.Spec.Provider = identityProvider
}
// GetProvider returns the identity provider.
func (o *SAMLConnectorV2) GetProvider() string {
return o.Spec.Provider
}
// GetAttributes returns list of attributes expected by mappings
func (o *SAMLConnectorV2) GetAttributes() []string {
var out []string
for _, mapping := range o.Spec.AttributesToRoles {
out = append(out, mapping.Name)
}
return utils.Deduplicate(out)
}
// MapClaims maps SAML attributes to roles
func (o *SAMLConnectorV2) MapAttributes(assertionInfo saml2.AssertionInfo) []string {
var roles []string
for _, mapping := range o.Spec.AttributesToRoles {
for _, attr := range assertionInfo.Values {
if attr.Name != mapping.Name {
continue
}
mappingLoop:
for _, value := range attr.Values {
for _, role := range mapping.Roles {
outRole, err := utils.ReplaceRegexp(mapping.Value, role, value.Value)
switch {
case err != nil:
if !trace.IsNotFound(err) {
log.Debugf("Failed to match expression %v, replace with: %v input: %v, err: %v", mapping.Value, role, value.Value, err)
}
// if value input did not match, no need to apply
// to all roles
continue mappingLoop
case outRole == "":
// skip empty role matches
case outRole != "":
roles = append(roles, outRole)
}
}
}
}
}
return utils.Deduplicate(roles)
}
// GetServiceProvider initialises service provider spec from settings
func (o *SAMLConnectorV2) GetServiceProvider(clock clockwork.Clock) (*saml2.SAMLServiceProvider, error) {
if o.Metadata.Name == "" {
return nil, trace.BadParameter("ID: missing connector name, name your connector to refer to internally e.g. okta1")
}
if o.Metadata.Name == teleport.Local {
return nil, trace.BadParameter("ID: invalid connector name %v is a reserved name", teleport.Local)
}
if o.Spec.AssertionConsumerService == "" {
return nil, trace.BadParameter("missing acs - assertion consumer service parameter, set service URL that will receive POST requests from SAML")
}
if o.Spec.ServiceProviderIssuer == "" {
o.Spec.ServiceProviderIssuer = o.Spec.AssertionConsumerService
}
if o.Spec.Audience == "" {
o.Spec.Audience = o.Spec.AssertionConsumerService
}
certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{},
}
// if we have a entity descriptor url, fetch it first
if o.Spec.EntityDescriptorURL != "" {
resp, err := http.Get(o.Spec.EntityDescriptorURL)
if err != nil {
return nil, trace.Wrap(err)
}
if resp.StatusCode != http.StatusOK {
return nil, trace.BadParameter("status code %v when fetching from %q", resp.StatusCode, o.Spec.EntityDescriptorURL)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, trace.Wrap(err)
}
o.Spec.EntityDescriptor = string(body)
log.Debugf("[SAML] Successfully fetched entity descriptor from %q", o.Spec.EntityDescriptorURL)
}
if o.Spec.EntityDescriptor != "" {
metadata := &types.EntityDescriptor{}
err := xml.Unmarshal([]byte(o.Spec.EntityDescriptor), metadata)
if err != nil {
return nil, trace.Wrap(err, "failed to parse entity_descriptor")
}
for _, kd := range metadata.IDPSSODescriptor.KeyDescriptors {
for _, samlCert := range kd.KeyInfo.X509Data.X509Certificates {
certData, err := base64.StdEncoding.DecodeString(strings.TrimSpace(samlCert.Data))
if err != nil {
return nil, trace.Wrap(err)
}
cert, err := x509.ParseCertificate(certData)
if err != nil {
return nil, trace.Wrap(err, "failed to parse certificate in metadata")
}
certStore.Roots = append(certStore.Roots, cert)
}
}
o.Spec.Issuer = metadata.EntityID
o.Spec.SSO = metadata.IDPSSODescriptor.SingleSignOnService.Location
}
if o.Spec.Issuer == "" {
return nil, trace.BadParameter("no issuer or entityID set, either set issuer as a parameter or via entity_descriptor spec")
}
if o.Spec.SSO == "" {
return nil, trace.BadParameter("no SSO set either explicitly or via entity_descriptor spec")
}
if o.Spec.Cert != "" {
cert, err := tlsca.ParseCertificatePEM([]byte(o.Spec.Cert))
if err != nil {
return nil, trace.Wrap(err)
}
certStore.Roots = append(certStore.Roots, cert)
}
if len(certStore.Roots) == 0 {
return nil, trace.BadParameter(
"no identity provider certificate provided, either set certificate as a parameter or via entity_descriptor")
}
if o.Spec.SigningKeyPair == nil {
keyPEM, certPEM, err := utils.GenerateSelfSignedSigningCert(pkix.Name{
Organization: []string{"Teleport OSS"},
CommonName: "teleport.localhost.localdomain",
}, nil, 10*365*24*time.Hour)
if err != nil {
return nil, trace.Wrap(err)
}
o.Spec.SigningKeyPair = &SigningKeyPair{
PrivateKey: string(keyPEM),
Cert: string(certPEM),
}
}
keyStore, err := utils.ParseSigningKeyStorePEM(o.Spec.SigningKeyPair.PrivateKey, o.Spec.SigningKeyPair.Cert)
if err != nil {
return nil, trace.Wrap(err)
}
// make sure claim mappings have either roles or a role template
for _, v := range o.Spec.AttributesToRoles {
hasRoles := false
if len(v.Roles) > 0 {
hasRoles = true
}
hasRoleTemplate := false
if v.RoleTemplate != nil {
hasRoleTemplate = true
}
// we either need to have roles or role templates not both or neither
// ! ( hasRoles XOR hasRoleTemplate )
if hasRoles == hasRoleTemplate {
return nil, trace.BadParameter("need roles or role template (not both or none)")
}
}
log.Debugf("[SAML] SSO: %v", o.Spec.SSO)
log.Debugf("[SAML] Issuer: %v", o.Spec.Issuer)
log.Debugf("[SAML] ACS: %v", o.Spec.AssertionConsumerService)
sp := &saml2.SAMLServiceProvider{
IdentityProviderSSOURL: o.Spec.SSO,
IdentityProviderIssuer: o.Spec.Issuer,
ServiceProviderIssuer: o.Spec.ServiceProviderIssuer,
AssertionConsumerServiceURL: o.Spec.AssertionConsumerService,
SignAuthnRequests: true,
SignAuthnRequestsCanonicalizer: dsig.MakeC14N11Canonicalizer(),
AudienceURI: o.Spec.Audience,
IDPCertificateStore: &certStore,
SPKeyStore: keyStore,
Clock: dsig.NewFakeClock(clock),
NameIdFormat: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
}
// adfs specific settings
if o.Spec.Provider == teleport.ADFS {
if sp.SignAuthnRequests {
// adfs does not support C14N11, we have to use the C14N10 canonicalizer
sp.SignAuthnRequestsCanonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList(dsig.DefaultPrefix)
// at a minimum we require password protected transport
sp.RequestedAuthnContext = &saml2.RequestedAuthnContext{
Comparison: "minimum",
Contexts: []string{"urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"},
}
}
}
return sp, nil
}
// GetSigningKeyPair returns signing key pair
func (o *SAMLConnectorV2) GetSigningKeyPair() *SigningKeyPair {
return o.Spec.SigningKeyPair
}
// GetSigningKeyPair sets signing key pair
func (o *SAMLConnectorV2) SetSigningKeyPair(k *SigningKeyPair) {
o.Spec.SigningKeyPair = k
}
func (o *SAMLConnectorV2) CheckAndSetDefaults() error {
err := o.Metadata.CheckAndSetDefaults()
if err != nil {
return trace.Wrap(err)
}
_, err = o.GetServiceProvider(clockwork.NewRealClock())
if err != nil {
return trace.Wrap(err)
}
return nil
}
// SAMLConnectorV2SchemaTemplate is a template JSON Schema for user
const SAMLConnectorV2SchemaTemplate = `{
"type": "object",
"additionalProperties": false,
"required": ["kind", "spec", "metadata", "version"],
"properties": {
"kind": {"type": "string"},
"version": {"type": "string", "default": "v1"},
"metadata": %v,
"spec": %v
}
}`
// SAMLConnectorSpecV2 specifies configuration for Open ID Connect compatible external
// identity provider, e.g. google in some organisation
type SAMLConnectorSpecV2 struct {
// Issuer is identity provider issuer
Issuer string `json:"issuer"`
// SSO is URL of the identity provider SSO service
SSO string `json:"sso"`
// Cert is identity provider certificate PEM
// IDP signs <Response> responses using this certificate
Cert string `json:"cert"`
// Display controls how this connector is displayed
Display string `json:"display"`
// AssertionConsumerService is a URL for assertion consumer service
// on the service provider (Teleport's side)
AssertionConsumerService string `json:"acs"`
// Audience uniquely identifies our service provider
Audience string `json:"audience"`
// SertviceProviderIssuer is the issuer of the service provider (Teleport)
ServiceProviderIssuer string `json:"service_provider_issuer"`
// EntityDescriptor is XML with descriptor, can be used to supply configuration
// parameters in one XML files vs supplying them in the individual elelemtns
EntityDescriptor string `json:"entity_descriptor"`
// EntityDescriptor points to a URL that supplies a configuration XML.
EntityDescriptorURL string `json:"entity_descriptor_url"`
// AttriburesToRoles is a list of mappings of attribute statements to roles
AttributesToRoles []AttributeMapping `json:"attributes_to_roles"`
// SigningKeyPair is x509 key pair used to sign AuthnRequest
SigningKeyPair *SigningKeyPair `json:"signing_key_pair,omitempty"`
// Provider is the external identity provider.
Provider string `json:"provider,omitempty"`
}
// SAMLConnectorSpecV2Schema is a JSON Schema for SAML Connector
var SAMLConnectorSpecV2Schema = fmt.Sprintf(`{
"type": "object",
"additionalProperties": false,
"required": ["acs"],
"properties": {
"issuer": {"type": "string"},
"sso": {"type": "string"},
"cert": {"type": "string"},
"provider": {"type": "string"},
"display": {"type": "string"},
"acs": {"type": "string"},
"audience": {"type": "string"},
"service_provider_issuer": {"type": "string"},
"entity_descriptor": {"type": "string"},
"entity_descriptor_url": {"type": "string"},
"attributes_to_roles": {
"type": "array",
"items": %v
},
"signing_key_pair": %v
}
}`, AttributeMappingSchema, SigningKeyPairSchema)
// GetAttributeNames returns a list of claim names from the claim values
func GetAttributeNames(attributes map[string]types.Attribute) []string {
var out []string
for _, attr := range attributes {
out = append(out, attr.Name)
}
return out
}
// AttributeMapping is SAML Attribute statement mapping
// from SAML attribute statements to roles
type AttributeMapping struct {
// Name is attribute statement name
Name string `json:"name"`
// Value is attribute statement value to match
Value string `json:"value"`
// Roles is a list of teleport roles to map to
Roles []string `json:"roles,omitempty"`
// RoleTemplate is a template for a role that will be filled
// with data from claims.
RoleTemplate *RoleV2 `json:"role_template,omitempty"`
}
// AttribueMappingSchema is JSON schema for claim mapping
var AttributeMappingSchema = fmt.Sprintf(`{
"type": "object",
"additionalProperties": false,
"required": ["name", "value" ],
"properties": {
"name": {"type": "string"},
"value": {"type": "string"},
"roles": {
"type": "array",
"items": {
"type": "string"
}
},
"role_template": %v
}
}`, GetRoleSchema(V2, ""))
// SigningKeyPairSchema
var SigningKeyPairSchema = `{
"type": "object",
"additionalProperties": false,
"properties": {
"private_key": {"type": "string"},
"cert": {"type": "string"}
}
}`
// SigningKeyPair is a key pair used to sign SAML AuthnRequest
type SigningKeyPair struct {
// PrivateKey is PEM encoded x509 private key
PrivateKey string `json:"private_key"`
// Cert is certificate in OpenSSH authorized keys format
Cert string `json:"cert"`
}