diff --git a/e b/e
index b37a41b87ee3d..254b78ea7b39d 160000
--- a/e
+++ b/e
@@ -1 +1 @@
-Subproject commit b37a41b87ee3dfa55fe4569cbb3553e4ee51697d
+Subproject commit 254b78ea7b39db29e92fc6a32833ce0d6f3c165b
diff --git a/fuzz/oss-fuzz-build.sh b/fuzz/oss-fuzz-build.sh
index 8b0e7cb52874c..25586702b2eaa 100755
--- a/fuzz/oss-fuzz-build.sh
+++ b/fuzz/oss-fuzz-build.sh
@@ -26,9 +26,6 @@ build_teleport_fuzzers() {
compile_native_go_fuzzer $TELEPORT_PREFIX/lib/services \
FuzzParserEvalBoolPredicate fuzz_parser_eval_bool_predicate
- compile_native_go_fuzzer $TELEPORT_PREFIX/lib/auth \
- FuzzParseSAMLInResponseTo fuzz_parse_saml_in_response_to
-
compile_native_go_fuzzer $TELEPORT_PREFIX/lib/restrictedsession \
FuzzParseIPSpec fuzz_parse_ip_spec
diff --git a/lib/auth/auth.go b/lib/auth/auth.go
index 8cf18b11c2e6a..53fad13b82583 100644
--- a/lib/auth/auth.go
+++ b/lib/auth/auth.go
@@ -290,13 +290,6 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) {
)
}
}
- // Plug in SAML auth service
- sas := NewSAMLAuthService(&SAMLAuthServiceConfig{
- Auth: &as,
- Emitter: as.emitter,
- AssertionReplayService: as.Unstable.AssertionReplayService,
- })
- as.SetSAMLService(sas)
return &as, nil
}
diff --git a/lib/auth/auth_with_roles_test.go b/lib/auth/auth_with_roles_test.go
index a94a470b22ea7..10fcde6c2baef 100644
--- a/lib/auth/auth_with_roles_test.go
+++ b/lib/auth/auth_with_roles_test.go
@@ -44,7 +44,6 @@ import (
"github.com/gravitational/teleport/lib/auth/testauthority"
libdefaults "github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/events"
- "github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/tlsca"
@@ -156,181 +155,6 @@ func TestSSOUserCanReissueCert(t *testing.T) {
require.NoError(t, err)
}
-func TestSAMLAuthRequest(t *testing.T) {
- ctx := context.Background()
- srv := newTestTLSServer(t)
-
- emptyRole, err := CreateRole(ctx, srv.Auth(), "test-empty", types.RoleSpecV5{})
- require.NoError(t, err)
-
- _, err = CreateRole(ctx, srv.Auth(), "baz", types.RoleSpecV5{})
- require.NoError(t, err)
-
- access1Role, err := CreateRole(ctx, srv.Auth(), "test-access-1", types.RoleSpecV5{
- Allow: types.RoleConditions{
- Rules: []types.Rule{
- {
- Resources: []string{types.KindSAMLRequest},
- Verbs: []string{types.VerbCreate},
- },
- },
- },
- })
- require.NoError(t, err)
-
- access2Role, err := CreateRole(ctx, srv.Auth(), "test-access-2", types.RoleSpecV5{
- Allow: types.RoleConditions{
- Rules: []types.Rule{
- {
- Resources: []string{types.KindSAML},
- Verbs: []string{types.VerbCreate},
- },
- },
- },
- })
- require.NoError(t, err)
-
- access3Role, err := CreateRole(ctx, srv.Auth(), "test-access-3", types.RoleSpecV5{
- Allow: types.RoleConditions{
- Rules: []types.Rule{
- {
- Resources: []string{types.KindSAML, types.KindSAMLRequest},
- Verbs: []string{types.VerbCreate},
- },
- },
- },
- })
- require.NoError(t, err)
-
- readerRole, err := CreateRole(ctx, srv.Auth(), "test-access-4", types.RoleSpecV5{
- Allow: types.RoleConditions{
- Rules: []types.Rule{
- {
- Resources: []string{types.KindSAMLRequest},
- Verbs: []string{types.VerbRead},
- },
- },
- },
- })
- require.NoError(t, err)
-
- conn, err := types.NewSAMLConnector("foo", types.SAMLConnectorSpecV2{
- Issuer: "test",
- SSO: "test",
- Cert: fixtures.TLSCACertPEM,
- AssertionConsumerService: "test",
- AttributesToRoles: []types.AttributeMapping{{
- Name: "foo",
- Value: "bar",
- Roles: []string{"baz"},
- }},
- })
- require.NoError(t, err)
-
- err = srv.Auth().UpsertSAMLConnector(ctx, conn)
- require.NoError(t, err)
-
- reqNormal := types.SAMLAuthRequest{ConnectorID: conn.GetName(), Type: constants.SAML}
- reqTest := types.SAMLAuthRequest{ConnectorID: conn.GetName(), Type: constants.SAML, SSOTestFlow: true, ConnectorSpec: &types.SAMLConnectorSpecV2{
- Issuer: "test",
- Audience: "test",
- ServiceProviderIssuer: "test",
- SSO: "test",
- Cert: fixtures.TLSCACertPEM,
- AssertionConsumerService: "test",
- AttributesToRoles: []types.AttributeMapping{{
- Name: "foo",
- Value: "bar",
- Roles: []string{"baz"},
- }},
- }}
-
- tests := []struct {
- desc string
- roles []string
- request types.SAMLAuthRequest
- expectAccessDenied bool
- }{
- {
- desc: "empty role - no access",
- roles: []string{emptyRole.GetName()},
- request: reqNormal,
- expectAccessDenied: true,
- },
- {
- desc: "can create regular request with normal access",
- roles: []string{access1Role.GetName()},
- request: reqNormal,
- expectAccessDenied: false,
- },
- {
- desc: "cannot create sso test request with normal access",
- roles: []string{access1Role.GetName()},
- request: reqTest,
- expectAccessDenied: true,
- },
- {
- desc: "cannot create normal request with connector access",
- roles: []string{access2Role.GetName()},
- request: reqNormal,
- expectAccessDenied: true,
- },
- {
- desc: "cannot create sso test request with connector access",
- roles: []string{access2Role.GetName()},
- request: reqTest,
- expectAccessDenied: true,
- },
- {
- desc: "can create regular request with combined access",
- roles: []string{access3Role.GetName()},
- request: reqNormal,
- expectAccessDenied: false,
- },
- {
- desc: "can create sso test request with combined access",
- roles: []string{access3Role.GetName()},
- request: reqTest,
- expectAccessDenied: false,
- },
- }
-
- user, err := CreateUser(srv.Auth(), "dummy")
- require.NoError(t, err)
-
- userReader, err := CreateUser(srv.Auth(), "dummy-reader", readerRole)
- require.NoError(t, err)
-
- clientReader, err := srv.NewClient(TestUser(userReader.GetName()))
- require.NoError(t, err)
-
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- user.SetRoles(tt.roles)
- err = srv.Auth().UpsertUser(user)
- require.NoError(t, err)
-
- client, err := srv.NewClient(TestUser(user.GetName()))
- require.NoError(t, err)
-
- request, err := client.CreateSAMLAuthRequest(ctx, tt.request)
- if tt.expectAccessDenied {
- require.Error(t, err)
- require.True(t, trace.IsAccessDenied(err), "expected access denied, got: %v", err)
- return
- }
-
- require.NoError(t, err)
- require.NotEmpty(t, request.ID)
- require.Equal(t, tt.request.ConnectorID, request.ConnectorID)
-
- requestCopy, err := clientReader.GetSAMLAuthRequest(ctx, request.ID)
- require.NoError(t, err)
- require.Equal(t, request, requestCopy)
- })
- }
-}
-
func TestInstaller(t *testing.T) {
ctx := context.Background()
srv := newTestTLSServer(t)
diff --git a/lib/auth/fuzz_test.go b/lib/auth/fuzz_test.go
index 5948092de34b7..3c3fd8d758db3 100644
--- a/lib/auth/fuzz_test.go
+++ b/lib/auth/fuzz_test.go
@@ -17,24 +17,11 @@ limitations under the License.
package auth
import (
- "encoding/base64"
"testing"
- "github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
)
-func FuzzParseSAMLInResponseTo(f *testing.F) {
- // Disable Go App Engine logging
- logrus.SetLevel(logrus.PanicLevel)
-
- f.Fuzz(func(t *testing.T, response string) {
- require.NotPanics(t, func() {
- ParseSAMLInResponseTo(base64.StdEncoding.EncodeToString([]byte(response)))
- })
- })
-}
-
func FuzzParseAndVerifyIID(f *testing.F) {
f.Fuzz(func(t *testing.T, iidBytes []byte) {
require.NotPanics(t, func() {
diff --git a/lib/auth/saml.go b/lib/auth/saml.go
index 1b6082251c512..3cf652a3e24a3 100644
--- a/lib/auth/saml.go
+++ b/lib/auth/saml.go
@@ -17,31 +17,16 @@ limitations under the License.
package auth
import (
- "bytes"
- "compress/flate"
"context"
- "encoding/base64"
"encoding/json"
"fmt"
- "io"
- "sync"
- "github.com/beevik/etree"
- "github.com/google/go-cmp/cmp"
"github.com/gravitational/trace"
- saml2 "github.com/russellhaering/gosaml2"
- "github.com/gravitational/teleport"
- "github.com/gravitational/teleport/api/constants"
- apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
- "github.com/gravitational/teleport/api/utils/keys"
- "github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/services"
- "github.com/gravitational/teleport/lib/services/local"
- "github.com/gravitational/teleport/lib/utils"
)
// ErrSAMLRequiresEnterprise is the error returned by the SAML methods when not
@@ -129,307 +114,6 @@ func (a *Server) ValidateSAMLResponse(ctx context.Context, re string, connectorI
return resp, trace.Wrap(err)
}
-// SAMLAuthService implements the logic of the SAML connector, allowing SSO
-// logins using the SAML protocol.
-//
-// SAMLAuthService implements the SAMLService interface.
-type SAMLAuthService struct {
- auth *Server
- emitter apievents.Emitter
- assertionReplayService *local.AssertionReplayService
- samlProviders map[string]*samlProvider
- lock sync.Mutex
-}
-
-type SAMLAuthServiceConfig struct {
- Auth *Server
- Emitter apievents.Emitter
- AssertionReplayService *local.AssertionReplayService
-}
-
-// NewSAMLAuthService returns a SAMLAuthService configured to use the
-// services given in the config.
-func NewSAMLAuthService(cfg *SAMLAuthServiceConfig) *SAMLAuthService {
- return &SAMLAuthService{
- auth: cfg.Auth,
- emitter: cfg.Emitter,
- assertionReplayService: cfg.AssertionReplayService,
-
- samlProviders: make(map[string]*samlProvider),
- }
-}
-
-// samlProvider is internal structure that stores SAML client and its config
-type samlProvider struct {
- provider *saml2.SAMLServiceProvider
- connector types.SAMLConnector
-}
-
-// ErrSAMLNoRoles results from not mapping any roles from SAML claims.
-var ErrSAMLNoRoles = trace.AccessDenied("No roles mapped from claims. The mappings may contain typos.")
-
-func (sas *SAMLAuthService) CreateSAMLAuthRequest(ctx context.Context, req types.SAMLAuthRequest) (*types.SAMLAuthRequest, error) {
- connector, provider, err := sas.getSAMLConnectorAndProvider(ctx, req)
- if err != nil {
- return nil, trace.Wrap(err)
- }
-
- doc, err := provider.BuildAuthRequestDocument()
- if err != nil {
- return nil, trace.Wrap(err)
- }
-
- attr := doc.Root().SelectAttr("ID")
- if attr == nil || attr.Value == "" {
- return nil, trace.BadParameter("missing auth request ID")
- }
-
- req.ID = attr.Value
-
- // Workaround for Ping: Ping expects `SigAlg` and `Signature` query
- // parameters when "Enforce Signed Authn Request" is enabled, but gosaml2
- // only provides these parameters when binding == BindingHttpRedirect.
- // Luckily, BuildAuthURLRedirect sets this and is otherwise identical to
- // the standard BuildAuthURLFromDocument.
- if connector.GetProvider() == teleport.Ping {
- req.RedirectURL, err = provider.BuildAuthURLRedirect("", doc)
- } else {
- req.RedirectURL, err = provider.BuildAuthURLFromDocument("", doc)
- }
-
- if err != nil {
- return nil, trace.Wrap(err)
- }
-
- err = sas.auth.Services.CreateSAMLAuthRequest(ctx, req, defaults.SAMLAuthRequestTTL)
- if err != nil {
- return nil, trace.Wrap(err)
- }
- return &req, nil
-}
-
-func (sas *SAMLAuthService) getSAMLConnectorAndProviderByID(ctx context.Context, connectorID string) (types.SAMLConnector, *saml2.SAMLServiceProvider, error) {
- connector, err := sas.auth.Identity.GetSAMLConnector(ctx, connectorID, true)
- if err != nil {
- return nil, nil, trace.Wrap(err)
- }
- provider, err := sas.getSAMLProvider(connector)
- if err != nil {
- return nil, nil, trace.Wrap(err)
- }
-
- return connector, provider, nil
-}
-
-func (sas *SAMLAuthService) getSAMLConnectorAndProvider(ctx context.Context, req types.SAMLAuthRequest) (types.SAMLConnector, *saml2.SAMLServiceProvider, error) {
- if req.SSOTestFlow {
- if req.ConnectorSpec == nil {
- return nil, nil, trace.BadParameter("ConnectorSpec cannot be nil when SSOTestFlow is true")
- }
-
- if req.ConnectorID == "" {
- return nil, nil, trace.BadParameter("ConnectorID cannot be empty")
- }
-
- // stateless test flow
- connector, err := types.NewSAMLConnector(req.ConnectorID, *req.ConnectorSpec)
- if err != nil {
- return nil, nil, trace.Wrap(err)
- }
-
- // validate, set defaults for connector
- err = services.ValidateSAMLConnector(connector, sas.auth)
- if err != nil {
- return nil, nil, trace.Wrap(err)
- }
-
- // we don't want to cache the provider. construct it directly instead of using sas.getSAMLProvider()
- provider, err := services.GetSAMLServiceProvider(connector, sas.auth.GetClock())
- if err != nil {
- return nil, nil, trace.Wrap(err)
- }
-
- return connector, provider, nil
- }
-
- // regular execution flow
- return sas.getSAMLConnectorAndProviderByID(ctx, req.ConnectorID)
-}
-
-func (sas *SAMLAuthService) getSAMLProvider(conn types.SAMLConnector) (*saml2.SAMLServiceProvider, error) {
- sas.lock.Lock()
- defer sas.lock.Unlock()
-
- providerPack, ok := sas.samlProviders[conn.GetName()]
- if ok && cmp.Equal(providerPack.connector, conn) {
- return providerPack.provider, nil
- }
- delete(sas.samlProviders, conn.GetName())
-
- serviceProvider, err := services.GetSAMLServiceProvider(conn, sas.auth.GetClock())
- if err != nil {
- return nil, trace.Wrap(err)
- }
-
- sas.samlProviders[conn.GetName()] = &samlProvider{connector: conn, provider: serviceProvider}
-
- return serviceProvider, nil
-}
-
-func (sas *SAMLAuthService) calculateSAMLUser(diagCtx *SSODiagContext, connector types.SAMLConnector, assertionInfo saml2.AssertionInfo, request *types.SAMLAuthRequest) (*CreateUserParams, error) {
- p := CreateUserParams{
- ConnectorName: connector.GetName(),
- Username: assertionInfo.NameID,
- }
-
- p.Traits = services.SAMLAssertionsToTraits(assertionInfo)
-
- diagCtx.Info.SAMLTraitsFromAssertions = p.Traits
- diagCtx.Info.SAMLConnectorTraitMapping = connector.GetTraitMappings()
-
- var warnings []string
- warnings, p.Roles = services.TraitsToRoles(connector.GetTraitMappings(), p.Traits)
- if len(p.Roles) == 0 {
- if len(warnings) != 0 {
- log.WithField("connector", connector).Warnf("No roles mapped from claims. Warnings: %q", warnings)
- diagCtx.Info.SAMLAttributesToRolesWarnings = &types.SSOWarnings{
- Message: "No roles mapped for the user",
- Warnings: warnings,
- }
- } else {
- log.WithField("connector", connector).Warnf("No roles mapped from claims.")
- diagCtx.Info.SAMLAttributesToRolesWarnings = &types.SSOWarnings{
- Message: "No roles mapped for the user. The mappings may contain typos.",
- }
- }
- return nil, trace.Wrap(ErrSAMLNoRoles)
- }
-
- // Pick smaller for role: session TTL from role or requested TTL.
- roles, err := services.FetchRoles(p.Roles, sas.auth, p.Traits)
- if err != nil {
- return nil, trace.Wrap(err)
- }
- roleTTL := roles.AdjustSessionTTL(apidefaults.MaxCertDuration)
-
- if request != nil {
- p.SessionTTL = utils.MinTTL(roleTTL, request.CertTTL)
- } else {
- p.SessionTTL = roleTTL
- }
-
- return &p, nil
-}
-
-func (sas *SAMLAuthService) createSAMLUser(p *CreateUserParams, dryRun bool) (types.User, error) {
- expires := sas.auth.GetClock().Now().UTC().Add(p.SessionTTL)
-
- log.Debugf("Generating dynamic SAML identity %v/%v with roles: %v. Dry run: %v.", p.ConnectorName, p.Username, p.Roles, dryRun)
-
- user := &types.UserV2{
- Kind: types.KindUser,
- Version: types.V2,
- Metadata: types.Metadata{
- Name: p.Username,
- Namespace: apidefaults.Namespace,
- Expires: &expires,
- },
- Spec: types.UserSpecV2{
- Roles: p.Roles,
- Traits: p.Traits,
- SAMLIdentities: []types.ExternalIdentity{
- {
- ConnectorID: p.ConnectorName,
- Username: p.Username,
- },
- },
- CreatedBy: types.CreatedBy{
- User: types.UserRef{
- Name: teleport.UserSystem,
- },
- Time: sas.auth.GetClock().Now().UTC(),
- Connector: &types.ConnectorRef{
- Type: constants.SAML,
- ID: p.ConnectorName,
- Identity: p.Username,
- },
- },
- },
- }
-
- if dryRun {
- return user, nil
- }
-
- // Get the user to check if it already exists or not.
- existingUser, err := sas.auth.Services.GetUser(p.Username, false)
- if err != nil && !trace.IsNotFound(err) {
- return nil, trace.Wrap(err)
- }
-
- ctx := context.TODO()
-
- // Overwrite exisiting user if it was created from an external identity provider.
- if existingUser != nil {
- connectorRef := existingUser.GetCreatedBy().Connector
-
- // If the exisiting user is a local user, fail and advise how to fix the problem.
- if connectorRef == nil {
- return nil, trace.AlreadyExists("local user with name %q already exists. Either change "+
- "NameID in assertion or remove local user and try again.", existingUser.GetName())
- }
-
- log.Debugf("Overwriting existing user %q created with %v connector %v.",
- existingUser.GetName(), connectorRef.Type, connectorRef.ID)
-
- if err := sas.auth.UpdateUser(ctx, user); err != nil {
- return nil, trace.Wrap(err)
- }
- } else {
- if err := sas.auth.CreateUser(ctx, user); err != nil {
- return nil, trace.Wrap(err)
- }
- }
-
- return user, nil
-}
-
-func ParseSAMLInResponseTo(response string) (string, error) {
- raw, _ := base64.StdEncoding.DecodeString(response)
-
- doc := etree.NewDocument()
- err := doc.ReadFromBytes(raw)
- if err != nil {
- // Attempt to inflate the response in case it happens to be compressed (as with one case at saml.oktadev.com)
- buf, err := io.ReadAll(flate.NewReader(bytes.NewReader(raw)))
- if err != nil {
- return "", trace.Wrap(err)
- }
-
- doc = etree.NewDocument()
- err = doc.ReadFromBytes(buf)
- if err != nil {
- return "", trace.Wrap(err)
- }
- }
-
- if doc.Root() == nil {
- return "", trace.BadParameter("unable to parse response")
- }
-
- // Try to find the InResponseTo attribute in the SAML response. If we can't find this, return
- // a predictable error message so the caller may choose interpret it as an IdP-initiated payload.
- el := doc.Root()
- responseTo := el.SelectAttr("InResponseTo")
- if responseTo == nil {
- return "", trace.NotFound("missing InResponseTo attribute")
- }
- if responseTo.Value == "" {
- return "", trace.BadParameter("InResponseTo can not be empty")
- }
- return responseTo.Value, nil
-}
-
// SAMLAuthResponse is returned when auth server validated callback parameters
// returned from SAML identity provider
type SAMLAuthResponse struct {
@@ -494,256 +178,3 @@ type SAMLAuthRawResponse struct {
// TLSCert is TLS certificate authority certificate
TLSCert []byte `json:"tls_cert,omitempty"`
}
-
-// SAMLAuthRequestFromProto converts the types.SAMLAuthRequest to SAMLAuthRequestData.
-func SAMLAuthRequestFromProto(req *types.SAMLAuthRequest) SAMLAuthRequest {
- return SAMLAuthRequest{
- ID: req.ID,
- PublicKey: req.PublicKey,
- CSRFToken: req.CSRFToken,
- CreateWebSession: req.CreateWebSession,
- ClientRedirectURL: req.ClientRedirectURL,
- }
-}
-
-// ValidateSAMLResponse consumes attribute statements from SAML identity provider
-func (sas *SAMLAuthService) ValidateSAMLResponse(ctx context.Context, samlResponse string, connectorID string) (*SAMLAuthResponse, error) {
- event := &apievents.UserLogin{
- Metadata: apievents.Metadata{
- Type: events.UserLoginEvent,
- },
- Method: events.LoginMethodSAML,
- }
-
- diagCtx := NewSSODiagContext(types.KindSAML, sas.auth)
-
- auth, err := sas.validateSAMLResponse(ctx, diagCtx, samlResponse, connectorID)
- diagCtx.Info.Error = trace.UserMessage(err)
-
- diagCtx.WriteToBackend(ctx)
-
- attributeStatements := diagCtx.Info.SAMLAttributeStatements
- if attributeStatements != nil {
- attributes, err := apievents.EncodeMapStrings(attributeStatements)
- if err != nil {
- event.Status.UserMessage = fmt.Sprintf("Failed to encode identity attributes: %v", err.Error())
- log.WithError(err).Debug("Failed to encode identity attributes.")
- } else {
- event.IdentityAttributes = attributes
- }
- }
-
- if err != nil {
- event.Code = events.UserSSOLoginFailureCode
- if diagCtx.Info.TestFlow {
- event.Code = events.UserSSOTestFlowLoginFailureCode
- }
- event.Status.Success = false
- event.Status.Error = trace.Unwrap(err).Error()
- event.Status.UserMessage = err.Error()
- if err := sas.emitter.EmitAuditEvent(ctx, event); err != nil {
- log.WithError(err).Warn("Failed to emit SAML login failed event.")
- }
- return nil, trace.Wrap(err)
- }
-
- event.Status.Success = true
- event.User = auth.Username
- event.Code = events.UserSSOLoginCode
- if diagCtx.Info.TestFlow {
- event.Code = events.UserSSOTestFlowLoginCode
- }
-
- if err := sas.emitter.EmitAuditEvent(ctx, event); err != nil {
- log.WithError(err).Warn("Failed to emit SAML login event.")
- }
-
- return auth, nil
-}
-
-func (sas *SAMLAuthService) checkIDPInitiatedSAML(ctx context.Context, connector types.SAMLConnector, assertion *saml2.AssertionInfo) error {
- if !connector.GetAllowIDPInitiated() {
- return trace.AccessDenied("IdP initiated SAML is not allowed by the connector configuration")
- }
-
- // Not all IdP's provide these variables, replay mitigation is best effort.
- if assertion.SessionIndex != "" || assertion.SessionNotOnOrAfter == nil {
- return nil
- }
-
- err := sas.assertionReplayService.RecognizeSSOAssertion(ctx, connector.GetName(), assertion.SessionIndex, assertion.NameID, *assertion.SessionNotOnOrAfter)
- return trace.Wrap(err)
-}
-
-func (sas *SAMLAuthService) validateSAMLResponse(ctx context.Context, diagCtx *SSODiagContext, samlResponse string, connectorID string) (*SAMLAuthResponse, error) {
- idpInitiated := false
- var connector types.SAMLConnector
- var provider *saml2.SAMLServiceProvider
- var request *types.SAMLAuthRequest
- requestID, err := ParseSAMLInResponseTo(samlResponse)
- switch {
- case trace.IsNotFound(err):
- if connectorID == "" {
- return nil, trace.BadParameter("ACS URI did not include a valid SAML connector ID parameter")
- }
-
- idpInitiated = true
- connector, provider, err = sas.getSAMLConnectorAndProviderByID(ctx, connectorID)
- if err != nil {
- return nil, trace.Wrap(err, "Failed to get SAML connector and provider")
- }
- case err != nil:
- return nil, trace.Wrap(err)
- default:
- diagCtx.RequestID = requestID
- request, err = sas.auth.Identity.GetSAMLAuthRequest(ctx, requestID)
- if err != nil {
- return nil, trace.Wrap(err, "Failed to get SAML Auth Request")
- }
-
- diagCtx.Info.TestFlow = request.SSOTestFlow
- connector, provider, err = sas.getSAMLConnectorAndProvider(ctx, *request)
- if err != nil {
- return nil, trace.Wrap(err, "Failed to get SAML connector and provider")
- }
- }
-
- assertionInfo, err := provider.RetrieveAssertionInfo(samlResponse)
- if err != nil {
- return nil, trace.AccessDenied("received response with incorrect or missing attribute statements, please check the identity provider configuration to make sure that mappings for claims/attribute statements are set up correctly. , failed to retrieve SAML assertion info from response: %v.", err).AddUserMessage("Failed to retrieve assertion info. This may indicate IdP configuration error.")
- }
-
- if assertionInfo != nil {
- diagCtx.Info.SAMLAssertionInfo = (*types.AssertionInfo)(assertionInfo)
- }
-
- if idpInitiated {
- if err := sas.checkIDPInitiatedSAML(ctx, connector, assertionInfo); err != nil {
- if trace.IsAccessDenied(err) {
- log.Warnf("Failed to process IdP-initiated login request. IdP-initiated login is disabled for this connector: %v.", err)
- }
-
- return nil, trace.Wrap(err)
- }
- }
-
- if assertionInfo.WarningInfo.InvalidTime {
- return nil, trace.AccessDenied("invalid time in SAML assertion info").AddUserMessage("SAML assertion info contained warning: invalid time.")
- }
-
- if assertionInfo.WarningInfo.NotInAudience {
- return nil, trace.AccessDenied("no audience in SAML assertion info").AddUserMessage("SAML: not in expected audience. Check auth connector audience field and IdP configuration for typos and other errors.")
- }
-
- log.Debugf("Obtained SAML assertions for %q.", assertionInfo.NameID)
- log.Debugf("SAML assertion warnings: %+v.", assertionInfo.WarningInfo)
-
- attributeStatements := map[string][]string{}
-
- for key, val := range assertionInfo.Values {
- var vals []string
- for _, vv := range val.Values {
- vals = append(vals, vv.Value)
- }
- log.Debugf("SAML assertion: %q: %q.", key, vals)
- attributeStatements[key] = vals
- }
-
- diagCtx.Info.SAMLAttributeStatements = attributeStatements
- diagCtx.Info.SAMLAttributesToRoles = connector.GetAttributesToRoles()
-
- if len(connector.GetAttributesToRoles()) == 0 {
- return nil, trace.BadParameter("no attributes to roles mapping, check connector documentation").AddUserMessage("Attributes-to-roles mapping is empty, SSO user will never have any roles.")
- }
-
- log.Debugf("Applying %v SAML attribute to roles mappings.", len(connector.GetAttributesToRoles()))
-
- // Calculate (figure out name, roles, traits, session TTL) of user and
- // create the user in the backend.
- params, err := sas.calculateSAMLUser(diagCtx, connector, *assertionInfo, request)
- if err != nil {
- return nil, trace.Wrap(err, "Failed to calculate user attributes.")
- }
-
- diagCtx.Info.CreateUserParams = &types.CreateUserParams{
- ConnectorName: params.ConnectorName,
- Username: params.Username,
- KubeGroups: params.KubeGroups,
- KubeUsers: params.KubeUsers,
- Roles: params.Roles,
- Traits: params.Traits,
- SessionTTL: types.Duration(params.SessionTTL),
- }
-
- user, err := sas.createSAMLUser(params, request != nil && request.SSOTestFlow)
- if err != nil {
- return nil, trace.Wrap(err, "Failed to create user from provided parameters.")
- }
-
- // Auth was successful, return session, certificate, etc. to caller.
- auth := &SAMLAuthResponse{
- Identity: types.ExternalIdentity{
- ConnectorID: params.ConnectorName,
- Username: params.Username,
- },
- Username: user.GetName(),
- }
-
- if request != nil {
- auth.Req = SAMLAuthRequestFromProto(request)
- } else {
- auth.Req = SAMLAuthRequest{
- CreateWebSession: true,
- }
- }
-
- // In test flow skip signing and creating web sessions.
- if request != nil && request.SSOTestFlow {
- diagCtx.Info.Success = true
- return auth, nil
- }
-
- // If the request is coming from a browser, create a web session.
- if request == nil || request.CreateWebSession {
- session, err := sas.auth.CreateWebSessionFromReq(ctx, types.NewWebSessionRequest{
- User: user.GetName(),
- Roles: user.GetRoles(),
- Traits: user.GetTraits(),
- SessionTTL: params.SessionTTL,
- LoginTime: sas.auth.GetClock().Now().UTC(),
- })
- if err != nil {
- return nil, trace.Wrap(err, "Failed to create web session.")
- }
-
- auth.Session = session
- }
-
- // If a public key was provided, sign it and return a certificate.
- if request != nil && len(request.PublicKey) != 0 {
- sshCert, tlsCert, err := sas.auth.CreateSessionCert(user, params.SessionTTL, request.PublicKey, request.Compatibility, request.RouteToCluster,
- request.KubernetesCluster, keys.AttestationStatementFromProto(request.AttestationStatement))
- if err != nil {
- return nil, trace.Wrap(err, "Failed to create session certificate.")
- }
- clusterName, err := sas.auth.GetClusterName()
- if err != nil {
- return nil, trace.Wrap(err, "Failed to obtain cluster name.")
- }
- auth.Cert = sshCert
- auth.TLSCert = tlsCert
-
- // Return the host CA for this cluster only.
- authority, err := sas.auth.GetCertAuthority(ctx, types.CertAuthID{
- Type: types.HostCA,
- DomainName: clusterName.GetClusterName(),
- }, false)
- if err != nil {
- return nil, trace.Wrap(err, "Failed to obtain cluster's host CA.")
- }
- auth.HostSigners = append(auth.HostSigners, authority)
- }
-
- diagCtx.Info.Success = true
- return auth, nil
-}
diff --git a/lib/auth/saml_test.go b/lib/auth/saml_test.go
deleted file mode 100644
index 6f384de385fb4..0000000000000
--- a/lib/auth/saml_test.go
+++ /dev/null
@@ -1,677 +0,0 @@
-/*
-Copyright 2019-2021 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 auth
-
-import (
- "context"
- "crypto/rand"
- "crypto/rsa"
- "crypto/x509/pkix"
- "encoding/base64"
- "encoding/xml"
- "net/url"
- "testing"
- "time"
-
- "github.com/jonboulle/clockwork"
- saml2 "github.com/russellhaering/gosaml2"
- samltypes "github.com/russellhaering/gosaml2/types"
- "github.com/stretchr/testify/require"
-
- apidefaults "github.com/gravitational/teleport/api/defaults"
- "github.com/gravitational/teleport/api/types"
- "github.com/gravitational/teleport/lib/auth/keystore"
- authority "github.com/gravitational/teleport/lib/auth/testauthority"
- "github.com/gravitational/teleport/lib/backend/memory"
- "github.com/gravitational/teleport/lib/defaults"
- "github.com/gravitational/teleport/lib/fixtures"
- "github.com/gravitational/teleport/lib/services"
- "github.com/gravitational/teleport/lib/tlsca"
- "github.com/gravitational/teleport/lib/utils"
-)
-
-func TestCreateSAMLUser(t *testing.T) {
- t.Parallel()
-
- ctx := context.Background()
- clock := clockwork.NewFakeClockAt(time.Now())
-
- b, err := memory.New(memory.Config{
- Context: ctx,
- Clock: clock,
- })
- require.NoError(t, err)
-
- clusterName, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{
- ClusterName: "me.localhost",
- })
- require.NoError(t, err)
-
- authConfig := &InitConfig{
- ClusterName: clusterName,
- Backend: b,
- Authority: authority.New(),
- SkipPeriodicOperations: true,
- KeyStoreConfig: keystore.Config{
- Software: keystore.SoftwareConfig{
- RSAKeyPairSource: authority.New().GenerateKeyPair,
- },
- },
- }
-
- a, err := NewServer(authConfig)
- require.NoError(t, err)
-
- sas, ok := a.samlAuthService.(*SAMLAuthService)
- require.True(t, ok, "Server.samlAuthServer is not type *samlAuthServer")
-
- // Dry-run creation of SAML user.
- user, err := sas.createSAMLUser(&CreateUserParams{
- ConnectorName: "samlService",
- Username: "foo@example.com",
- Roles: []string{"admin"},
- SessionTTL: 1 * time.Minute,
- }, true)
- require.NoError(t, err)
- require.Equal(t, "foo@example.com", user.GetName())
-
- // Dry-run must not create a user.
- _, err = a.GetUser("foo@example.com", false)
- require.Error(t, err)
-
- // Create SAML user with 1 minute expiry.
- _, err = sas.createSAMLUser(&CreateUserParams{
- ConnectorName: "samlService",
- Username: "foo@example.com",
- Roles: []string{"admin"},
- SessionTTL: 1 * time.Minute,
- }, false)
- require.NoError(t, err)
-
- // Within that 1 minute period the user should still exist.
- _, err = a.GetUser("foo@example.com", false)
- require.NoError(t, err)
-
- // Advance time 2 minutes, the user should be gone.
- clock.Advance(2 * time.Minute)
- _, err = a.GetUser("foo@example.com", false)
- require.Error(t, err)
-}
-
-func TestEncryptedSAML(t *testing.T) {
- t.Parallel()
-
- // This Base64 encoded XML blob is a signed SAML response with an encrypted assertion for testing decryption and parsing.
- const EncryptedResponse = `PD94bWwgdmVyc2lvbj0iMS4wIj8+DQo8c2FtbHA6UmVzcG9uc2UgeG1sbnM6c2FtbHA9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgeG1sbnM6c2FtbD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiIgSUQ9InBmeDBmNTBiYTg0LWVmNjctNTQyZi1kZDgyLTI4NTU0MzVlMGM4MCIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTQtMDctMTdUMDE6MDE6NDhaIiBEZXN0aW5hdGlvbj0iaHR0cDovL3NwLmV4YW1wbGUuY29tL2RlbW8xL2luZGV4LnBocD9hY3MiIEluUmVzcG9uc2VUbz0iT05FTE9HSU5fNGZlZTNiMDQ2Mzk1YzRlNzUxMDExZTk3Zjg5MDBiNTI3M2Q1NjY4NSI+DQogIDxzYW1sOklzc3Vlcj5odHRwOi8vaWRwLmV4YW1wbGUuY29tL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PGRzOlNpZ25hdHVyZSB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+DQogIDxkczpTaWduZWRJbmZvPjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+DQogICAgPGRzOlNpZ25hdHVyZU1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNyc2Etc2hhMSIvPg0KICA8ZHM6UmVmZXJlbmNlIFVSST0iI3BmeDBmNTBiYTg0LWVmNjctNTQyZi1kZDgyLTI4NTU0MzVlMGM4MCI+PGRzOlRyYW5zZm9ybXM+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyNlbnZlbG9wZWQtc2lnbmF0dXJlIi8+PGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPjwvZHM6VHJhbnNmb3Jtcz48ZHM6RGlnZXN0TWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI3NoYTEiLz48ZHM6RGlnZXN0VmFsdWU+TUxic3U4WFFOcW4xWE8walUzeHZIL0pPalZnPTwvZHM6RGlnZXN0VmFsdWU+PC9kczpSZWZlcmVuY2U+PC9kczpTaWduZWRJbmZvPjxkczpTaWduYXR1cmVWYWx1ZT5yVTVDUzhWQnZGVjl3RkUvOEY1NHROQTd3UVFWbG9UZkRsL0h1amJwRzJBWTNZcExtdWxzU2pOdngvc0F4a3luZ0lLTVE2dHphZkN3KzZjaGNldzh4bUNOcWdSNWNiQ09DbzB2UUJXaXhINm9jU2FKWDRTU21WeEhhU2p1clRNRkZnamFFYktiM2duV21haGpDb093TU9MZHJtWlprYkp2OWQrWTVUR0VYL2hhUmMvbXU2b05WT3dCL0xMdURDdzk3RTkxdVNUVUpvL1RPS0tVRjJYenZhVEEwMXZobzM5OTYvalpFWkRYR1ZyTGlkOTg5NDJXWWVjT3F6ZnZTWWtLemNaRGd2ZE1udlR1M20yVHpXQ1RqaEVzVDN1cjQ2OThIUmlyZUZTbnFINldhYUVMYjFFeGFiemdxNGFsRG9ma1J3ZU14YWJleVV6aUVBSWhKOGYrNVE9PTwvZHM6U2lnbmF0dXJlVmFsdWU+DQo8ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE+PGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlES2pDQ0FoS2dBd0lCQWdJUUp0SkRKWlpCa2cvYWZNOGQyWkpDVGpBTkJna3Foa2lHOXcwQkFRc0ZBREJBTVJVd0V3WURWUVFLRXd4VVpXeGxjRzl5ZENCUFUxTXhKekFsQmdOVkJBTVRIblJsYkdWd2IzSjBMbXh2WTJGc2FHOXpkQzVzYjJOaGJHUnZiV0ZwYmpBZUZ3MHhOekExTURreE9UUXdNelphRncweU56QTFNRGN4T1RRd016WmFNRUF4RlRBVEJnTlZCQW9UREZSbGJHVndiM0owSUU5VFV6RW5NQ1VHQTFVRUF4TWVkR1ZzWlhCdmNuUXViRzlqWVd4b2IzTjBMbXh2WTJGc1pHOXRZV2x1TUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF1S0ZMYWYyaUlJL3hEUittMllqNlBuVUVhK3F6cXd4c2RMVWpudW5GWmFBWEcraFptNE1sODBTQ2lCZ0lnVEhRbEp5TElrVHR1Um9INWFlTXl6MUVSVUN0aWk0WnNUcURyampVeWJ4UDRyKzRIVlg2bTM0czZod0VyOEZpZnRzOXBNcDRpUzN0UWd1UmMyOGdQZERvL1Q2VnJKVFZZVWZVVXNORFJ0SXJsQjVPOWlncXFMbnVhWTllcUdpNFBVeDBHMHdSWUpwUnl3b2o4RzBJa3BmUVRpWCtDQUM3ZHQ1d3M3WnJuR3FDTkJMR2k1YkdzYU1tcHRWYnNTRXAxVGVubnRGNTRWMWlSNDlJVjVKcURobTFTMEhta2xlb0p6S2RjKzZzUC94TmVwejlQSnp1RjlkOU51YlRMV2dCc0syOFlJdGNtV0hkSFhEL09EeFZhZWhSandJREFRQUJveUF3SGpBT0JnTlZIUThCQWY4RUJBTUNCNEF3REFZRFZSMFRBUUgvQkFJd0FEQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFBVlU2c05CZGo3NnNhSHdPeEdTZG5FcVFvMnRNdVIzbXNTTTRGNndGSzJVa0tlcHNEN0NZSWYvUHpOU05VcUE1SklFVVZlTXFHeWlIdUFiVTRDNjU1blQxSXlKWDFELytyNzNzU3A1amJJcFFtMnhvUUdabmo2Zy9LbHR3OE9TT0F3K0RzTUYvUExWcW9XSnAwN3U2ZXcvbU54V3NKS2NaNWsrcTRlTXhjaTltS1JISHFzcXVXS1h6UWxVUk1ORkkrbUdhRndyS000ZG16YVIwQkVjK2lsU3hRcVV2UTc0c21zTEsremhOaWttZ2psR0M1b2I5ZzhYa2hWQWtKTUFoMnJiOW9uRE5pUmw2OGlBZ2N6UDg4bVh1dk4vbzk4ZHlwenNQeFhtdzZ0a0RxSVJQVUFVYmg0NjVybFk1c0tNbVJnWGkyclVmbC9RVjVuYm96VW8vSFE9PTwvZHM6WDUwOUNlcnRpZmljYXRlPjwvZHM6WDUwOURhdGE+PC9kczpLZXlJbmZvPjwvZHM6U2lnbmF0dXJlPg0KICA8c2FtbHA6U3RhdHVzPg0KICAgIDxzYW1scDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz4NCiAgPC9zYW1scDpTdGF0dXM+DQogIA0KPHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIvPjxkc2lnOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PHhlbmM6RW5jcnlwdGVkS2V5Pjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNyc2Etb2FlcC1tZ2YxcCIvPjx4ZW5jOkNpcGhlckRhdGE+PHhlbmM6Q2lwaGVyVmFsdWU+TjlLaHFKeWtJdGk1eVZETzdzT0VpT2lMb1VWL2p5aEdITU0wZmFTUzZnWVJ5RUZqaFR2RzNCUEpsd1RTTXpzTTFuY1pwTGVBd0FKaFVzci9mT0pCVGtQbjA3UzZqZGsxYTBMaU9EbjkrcDlCVXlidjRXYWsyWGduMnhXNytDNVQ5bGhvQ0dHRThrdHh6Q0tXL1FhWUNWV3RsMEp1TGdNYWIyWHUzL1dJdVlSWDhKbVZ2ckdPWTlOd0hpeFhFT09PbDFSUUNnbXpNaCtxUno5eFhwWGU0Sk1XRGNqQ0g2blk5V2Fxem4yQTNJVnJzS1V3bUZuTWFaM1lOM04ybmNROWo2QXRYSThoWEErMjBvRlhBQWx2c3JVK0xOek9hTFRzb1QySFZVUER0YldhQm9tS1cxdW9lc1hPZG1KNnVDUVc4Zk9BL3p0QStjL1JERTdyaTZiSFNERCs3YW5uQlNaRzVzL1lrdm5PT2wxRjRFYndLdVpMd3RWdjQza1B1dnRVeUVxTE1HRFlhNmtXczlLRjRsR0dqa0YxMXpqTmVnSTRFd09EVXhZSm14QldRNXhvVm1sOGVvK0VNdGt5NzFGeUttMFhpSGo0cWw5Qnl4dUtaVHJrTXdjQjlPSkNFKzcwM1dpdW5uZy9OSGkrV1IrTmhORlZqUm40SWQ3UTVFTEZZNFRNMklNQld6Y3R2cEd6TGVHdjF2L2RXanVodU1aVjJpeG5nMzRxNXg3VVVnODAwNVlRTnNwOVcwYVZKdDRnY0tUeFAyRnJTVGVjM0MxRktVT1JXSCtVc2ZpZ21GY09YdHFvQXgyK3dDZGs3MkVTS3Z2WFYvWEVFSmd0aTRiVldUK0dzSmc2eGRhTjBPNnlpWTg4eE5BcWZMUEcwajJTb0k1bUcxYzM0YVE9PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWRLZXk+PC9kc2lnOktleUluZm8+DQogICA8eGVuYzpDaXBoZXJEYXRhPg0KICAgICAgPHhlbmM6Q2lwaGVyVmFsdWU+a1dWbkpzTkZZZ1JTNlMvTlFERVlsN3RTTjVsTzR6YWtqdXdxTDROMlFHbW9rdnBWRlJreFp3bjVhOXkrVDhLUGZpOWt2bEhlNktzL1I1UTJ2NkdVd0Z4V1dPYVZrOFJDMUh1blN1ZnZ5Tks1Y21hRmJSM0t3OFZZZnNPRVV2T0d6Y2pqTFFjSFFFUEZUMXJsMW0vazc3dExzbFlxV1B6WkRhUFpkU1lkd0kvdlo0OThIeXF3b0Y4U2tCcFVQcW02bnYwVGFVdU5pOWMzbkdDUTBWejE3UGFnLzN3SERWTnA3RlNsSFJlSStySkZsS0RXalF4MStDZjF6U3pjTG5Ecll1aEt3MWY3WVVTYUh0enlBL2l5MkhRMDdSZjRyNUpRaUorR2FOSUkwYk44RTZ0QXJqbjFZSGZWMDRQdWNwa0xDTEJTRndiOERXZE9wMnFEN3pvcEJvc2g2YVF6Vjh3QjhsUEFqUUd2aFhlQVdwRmsxWkVaUG5SRkkydkdkaGFHL2o5TkQvM0EvbzlvZFZpd1ZBdFd4SG9WVzZWcWtLOG5GQlVyL25IVDZTYmdsOFlUd2s4N0hRaldHUEdSaksyNEp3YXc0VjV6djN4bG9zSlFpdnc2dGwyVXk5YWROdUlyOUFCRGJuc3RmdXlQNEZaenR2Q0RTUHFPZlVVQlhya0thczNJRE1iNm9KREQ3a003QVdHcEU5K0lJT0NoVzdiRk5hb3RndHN1OTVqd1ZNQ3BVL0NkNkhPbURrMWpDYVc5RzhlQWJVdjhaUEVmN1Q5c0Z1VnZOaVBVbkFyMWV1VEl6WFNPekFtZk5jdXZpZ1BnOUlCYzAvT0pYWjBBVTgvQWxyRTZRSldwL2pDYThUNTF0YTFjbVN6SGQ4SG0zTEt5aDhsYjljUG5RR3RCeW9LcFUzZEMwQWk3OU1Lc1NhekRTalByY2l6ZUdhS0Vzd1NCTWtWRGtDWFMybGJicXBrckxvN2tMdy9TNG1OMWZCVjU5a2txd1ZlL3pKQXJNNllIckNJUURwRkRCSWtHeXE1WVl1VkQyeXk4YnJmeFNBemMyK2ZpeWQ5OVJndEtTeGZhVkROV3VJZzJnVTRQME40TnVTOCtrb0MzclF6eWovOGdWSlRpSGg5UTFEOVRJbjhjZGllTE12ZlZLT25oMHZBTnR0MDN2YnRHVkJxTTQwa3VLeUxKRWE5TWY0N2xvWk5qamUxd2VvWW1wblZScGVxVGxzUDRzVmwzd3QwYWlDZG5mdHVaVlVWb0M3Um51VDRidVRWZVgybUdNMElxR242czJwZ0xsSU5xVEFTY0F0K3FlVEhycmVTcUhFSmovZnhyM3NySEpyMHZjQ0w2enZZMWtOUi9taGcyL25Bc0hwTkc3c21ITS9oQ3gzOS9HZ01FeFpXbU5lVXV3amhhOHpWc3FRdnZ6cDM3Nk1OWC9xeERuU0JxTEorTERlRXg0SnYweGJxd0tvVE01L3BSQThDOVA4NWRqZXYwT1RKWVBEOGZzNHhabmV6NHRQTlZhcEc1RzEwWG1Wem9TYm1MNXdYamV0bXJJY0NMZ0laeEZnZ1NFSmsxUGFtdThzU2VraHBNOE1EbVZXejZrRDZxdUxyRXNqc0h2K0Rxc1REMkJIRjJMOVVuMjlaL0NvbFBNaDRuT0NRZHpjcjNUT0dEalJaMk1lanJHb2VnblVORW5MaGhwK2luWTB5L2lsODZYV2FweERoUEFLclIxS3pTRUJ5Yk5UUE81S0o4M0pBMGx0dE5JVk9YZ3FkZkQxSy9KYnM4ZHdYY3pRenZCRXBMOXlEZVN4d0wzVXR2VG80NEJLVkYxUVdwUDVvdHU5ZndpZXNkZlJLZm1zM3pteFZiQTZ4dkprK0M2MkE2YXBDcjhqL3FaUldNTUczNytnMUI4bU13eGdabkxyOE9ic2JMQjBVV3JOYWdMbldpc29ZQTVmclcxN21wbW9tc3V1cmhQQ2IrbGczbmFkK1BCRXpXaUZISnJvVkdQMFRwZ1NWZlZvSU9wYkZCS2JpWjVoUnd5YURuaFMrL1UreXNUZ1hXdE1qUk9QYjRROVZiUW8vSXd0NEpVZjZNUlVlN0FoaTNUbTUyOUR5Q1ppOFNydTBtRDF4ZHArdjlkSXF2ZEoxZXROYXZFMTlCSmRDSzN0VHhIZHk2ODh6bEo4NFJOUTlxYzI1R3pudXVFNFg3ZjBkUkQrdEoyNkJNVmh5L3RLeDRzdUpIVURzS05yUmZ4aGM4czNwU2FhUTFZS0E4eVpnQnBIN2tDY0FJZ2g5NlpEU3NuUngzdU5Zc0txakFmd0x3TkNQam02bHY1YVV1azBtYklMelBwMDhFRWFzRWNhWE1CcEZraERKTGhIS1l2WUhPdjlQYTZvWEFOVzZSOC9rSUxoT2ppbnl3RmZUZ2FDci96R20zcitjc2VoM0RxTGNjZjRteXY3Tmp5eERUSWtVeUVDdDJLYnI3S2ZPckh3T3I0L3M0ODVhVVVJVDRxYml5Lzd4U0E1NXlDaFUzV0RkaGpDQ3l2WXNSa3cvUjhVQUMyRExmZWxDVlducWdoTnE3blhNdU9zM3h4L3hpLzE5eEVYK3RxTWNqUHVsc3FQejk4WDF1UE5iZDluYXpSUnFEaXQ2R0Y2NHNlY1ZKVmVjZlpHNTJpTHpRbzZLNzVtdnU0M00rNlI5MkhDdkVWd09sRDhCRWoxYncrUWFaL3FDNVF4UjNtMkR5VHlmd20wVVVUM25yOG0yTDdaR3ozTUU2RXViTzZFWFBVR0JpRnlUeTNNUzRxVGUrbTJoWHdwd0xhTXM3SXFjRVBvMW5KeUJmN1plWm1kZVpsLzJHZ1pVNk94YmxiTmtUZUJORlRWVjhzK0Y2K2h3ZVArMzdBbEVsVWNGNTZaUk9NU0Nmemw0OFRtR1BZNHdpRmlMWGErMXZqTFN6NGdlYzhlVGZJcXczdEdaNjAvUTJGRGV6TUV2MEd0cSsxbTU3VXpqWGtLSVoyNzJBZ3VUNzZsbWUyd1B3dUVtOER2ZHE0Z21ZZlE5MVZ5M3J3aElsVVpvakRxU2R0UHJiTU4zK0JvR1Y0SVFTdUExNlN0UUtJWkg3VVNkckZBZ1dhLzVjQzUwVjIvMUVMMWZRWjJFa01LbktFczRnc2hZNU5YRTYzTmJTSjZiMnpKQll6MDdRcXVTemhycGxUeTR3dVpqcytRd082bUhDZkdEaFY4R2I1RG1HcnExMVVJNVllNVp4RXNLS0FvSlRYdWFOZlRpamFKb2l5L2lvcmFZZk00eUNHUVIyU04wdnc4Zk9LdXA3SlIrejk5TXdmSEJwV21LaHhFM3dITWpZOWpJa3Nyc3JLWnk3MnFRaGROMDZCdEo2SFdpN25PNWJUdHVQZlpmUFdPOFVEZUpKZFdLdUVUNUdpeWFxWGhOM0VwQUQvcDA5RjZkOEgrb1gyRVAzQjRZWXE2cTlSOHpJZG9kZ3cxbncxZjM3MERpL0pSSG1PbnQ5VEord05FS0x5eW42dFVodXRpOGZMY2VKYkk2dHJFb1I2S3NZWGJUeXprVXg5TGtiVkNFelVicVV1YmtrdG5WVm5rcHFJSmNFTGdpZWl0TFRTYW80ZkdOaFROVDVCWDEySWtoQWsrSEhSZlZtWjdhY0lBNDlLYkhlRElpUGxOR3A1WGxMQ1luZGxlOFZlWDF4bEw4Y0duZlB5QWtiSTRLN3RQR2h4S0YzSWFoazFtTVFGWFRhQ3FvdS9pYm9ZQ0F0R3pod3pOaUtLcXhRT2FSWFpQWkY3TlVKbE13NlR1VFhhaU9pM0dLMDZ4eXY2ZEcxTk9ya2daRmhNSS9CZHdjWVcxaHdCaElXWHBVTnpNUyt6cDVTS1BOQW5BSHE4aW16OU1JcGNQajFkYXo0cUNQd3N4dXMwYXBnMkdZQ0xYTW8zdnlTRXd3WTAwQjZzUUlrRHhLTUc4TWtvQjFpMU5vVGMwNnJxTkFOL2szZ3h4UnpBNUFpNHU4NGhpd3dxOXlMWC9oNjFGRnpySklEY3gydHl4MHdUTFd2SzNBWCtOZStQMDZvWWEyVHN4R0RkV0RQR1BFc2U0aFZlTUl6PC94ZW5jOkNpcGhlclZhbHVlPg0KICAgPC94ZW5jOkNpcGhlckRhdGE+DQo8L3hlbmM6RW5jcnlwdGVkRGF0YT48L3NhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjwvc2FtbHA6UmVzcG9uc2U+`
-
- // This XML blob is a sample EntityDescriptor made to satisfy the connector validator for testing.
- const EntityDescriptor = `
-
-
-
-
-
- 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
-
-
-
- urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
-
-
- `
-
- signingKeypair := &types.AsymmetricKeyPair{
- Cert: fixtures.TLSCACertPEM,
- PrivateKey: fixtures.TLSCAKeyPEM,
- }
-
- encryptionKeypair := &types.AsymmetricKeyPair{
- Cert: fixtures.EncryptionCertPEM,
- PrivateKey: fixtures.EncryptionKeyPEM,
- }
-
- connector, err := types.NewSAMLConnector("spongebob", types.SAMLConnectorSpecV2{
- Cert: signingKeypair.Cert,
- Issuer: "http://idp.example.com/metadata.php",
- SSO: "nil",
- AssertionConsumerService: "http://sp.example.com/demo1/index.php?acs",
- EntityDescriptor: EntityDescriptor,
- })
- require.NoError(t, err)
-
- connector.SetSigningKeyPair(signingKeypair)
- connector.SetEncryptionKeyPair(encryptionKeypair)
-
- clock := clockwork.NewFakeClockAt(time.Date(2021, time.April, 4, 0, 0, 0, 0, time.UTC))
- provider, err := services.GetSAMLServiceProvider(connector, clock)
- require.NoError(t, err)
- assertionInfo, err := provider.RetrieveAssertionInfo(EncryptedResponse)
- require.NoError(t, err)
- require.NotEmpty(t, assertionInfo.Assertions)
-}
-
-// TestPingSAMLWorkaround ensures we provide required additional authn query
-// parameters for Ping backends (PingOne, PingFederate, etc) when
-// `provider: ping` is set.
-func TestPingSAMLWorkaround(t *testing.T) {
- t.Parallel()
-
- ctx := context.Background()
- clock := clockwork.NewFakeClockAt(time.Now())
-
- // Create a Server instance for testing.
- b, err := memory.New(memory.Config{
- Context: ctx,
- Clock: clock,
- })
- require.NoError(t, err)
-
- clusterName, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{
- ClusterName: "me.localhost",
- })
- require.NoError(t, err)
-
- authConfig := &InitConfig{
- ClusterName: clusterName,
- Backend: b,
- Authority: authority.New(),
- SkipPeriodicOperations: true,
- KeyStoreConfig: keystore.Config{
- Software: keystore.SoftwareConfig{
- RSAKeyPairSource: authority.New().GenerateKeyPair,
- },
- },
- }
-
- a, err := NewServer(authConfig)
- require.NoError(t, err)
-
- // Create a new SAML connector for Ping.
- const entityDescriptor = `
-
-
-
-
- MIIDejCCAmKgAwIBAgIGAXnsYbiQMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKDA1QaW5nIElkZW50aXR5MRYwFAYDVQQLDA1QaW5nIElkZW50aXR5MT8wPQYDVQQDDDZQaW5nT25lIFNTTyBDZXJ0aWZpY2F0ZSBmb3IgQWRtaW5pc3RyYXRvcnMgZW52aXJvbm1lbnQwHhcNMjEwNjA4MTYwODE3WhcNMjIwNjA4MTYwODE3WjB+MQswCQYDVQQGEwJVUzEWMBQGA1UECgwNUGluZyBJZGVudGl0eTEWMBQGA1UECwwNUGluZyBJZGVudGl0eTE/MD0GA1UEAww2UGluZ09uZSBTU08gQ2VydGlmaWNhdGUgZm9yIEFkbWluaXN0cmF0b3JzIGVudmlyb25tZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArqJP+9QA8rzt9lLrKQigkT1HxCP5qIQH9vKgIhCDx5q7eSHOlxQ7MMa+1v1WQq1y5mgNG1zxe+cEaJ646JHQLoa0yj+rXsfCsUsKG7qceHzMR8p4y74x77PHTBJEviS9g/+fMGq7eaSK/F8ksPBfBjHnWv+lvnzrAGhxEuBXfFPf5Gb2Vr5LYurZEu9lIdFtSnFCVjzUIC1SMyovl92K4WdJpZ60N8FUSR6Jb7b8gWjnNHNc1iwr5C2b8+HUuWhqCIc0TQygEilZAdJhpYkeCQMiSqySsV+cmJ1vdjsV0HXX0YREDq6koklnw1hyTe1AckcH6qfWyBcoG2VYORjZPQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQA0eVvkB+/RSIEs7CXje7KKFGO99X7nIBNcpztp6kevxTDFHKsVlGFfl/mkksw9SjzdWSMDgGxxy6riYnScQD0FdyxaKzM0CRFfqdHf2+qVnK4GbiodqLOVp1dDE6CSQuPp7inQr+JDO/xD1WUAyMSC+ouFRdHq2O7MCYolEcyWiZoTTcch8RhLo5nqueKQfP0vaJwzAPgpXxAuabVuyrtN0BZHixO/sjjg9yup8/esCMBB/RR90PxzbI+8ZX5g1MxZZwSaXauQFyOjm5/t+JEisZf8rzrrhDd2GzWrYngB8DJLxCUK1JTM5SO/k3TqeDHLHi202P7AN2S/1CqzCaGb
-
-
-
-
-
-
-
-
- `
-
- signingKeypair := &types.AsymmetricKeyPair{
- Cert: fixtures.TLSCACertPEM,
- PrivateKey: fixtures.TLSCAKeyPEM,
- }
-
- encryptionKeypair := &types.AsymmetricKeyPair{
- Cert: fixtures.EncryptionCertPEM,
- PrivateKey: fixtures.EncryptionKeyPEM,
- }
-
- // SAML connector validation requires the roles in mappings exist.
- role, err := types.NewRole("admin", types.RoleSpecV5{})
- require.NoError(t, err)
- err = a.CreateRole(ctx, role)
- require.NoError(t, err)
-
- connector, err := types.NewSAMLConnector("ping", types.SAMLConnectorSpecV2{
- AssertionConsumerService: "https://proxy.example.com:3080/v1/webapi/saml/acs",
- Provider: "ping",
- Display: "Ping",
- AttributesToRoles: []types.AttributeMapping{
- {Name: "groups", Value: "ping-admin", Roles: []string{role.GetName()}},
- },
- EntityDescriptor: entityDescriptor,
- SigningKeyPair: signingKeypair,
- EncryptionKeyPair: encryptionKeypair,
- })
- require.NoError(t, err)
-
- err = a.UpsertSAMLConnector(ctx, connector)
- require.NoError(t, err)
-
- // Create an auth request that we can inspect.
- req, err := a.CreateSAMLAuthRequest(ctx, types.SAMLAuthRequest{
- ConnectorID: "ping",
- })
- require.NoError(t, err)
-
- // Parse the generated redirection URL.
- parsed, err := url.Parse(req.RedirectURL)
- require.NoError(t, err)
-
- require.Equal(t, "auth.pingone.com", parsed.Host)
- require.Equal(t, "/8be7412d-7d2f-4392-90a4-07458d3dee78/saml20/idp/sso", parsed.Path)
-
- // SigAlg and Signature must be added when `provider: ping`.
- require.NotEmpty(t, parsed.Query().Get("SigAlg"), "SigAlg is required for provider: ping")
- require.NotEmpty(t, parsed.Query().Get("Signature"), "Signature is required for provider: ping")
-}
-
-func TestServer_getConnectorAndProvider(t *testing.T) {
- t.Parallel()
-
- ctx := context.Background()
- clock := clockwork.NewFakeClockAt(time.Now())
-
- // Create a Server instance for testing.
- b, err := memory.New(memory.Config{
- Context: ctx,
- Clock: clock,
- })
- require.NoError(t, err)
-
- clusterName, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{
- ClusterName: "me.localhost",
- })
- require.NoError(t, err)
-
- authConfig := &InitConfig{
- ClusterName: clusterName,
- Backend: b,
- Authority: authority.New(),
- SkipPeriodicOperations: true,
- KeyStoreConfig: keystore.Config{
- Software: keystore.SoftwareConfig{
- RSAKeyPairSource: authority.New().GenerateKeyPair,
- },
- },
- }
-
- a, err := NewServer(authConfig)
- require.NoError(t, err)
-
- sas, ok := a.samlAuthService.(*SAMLAuthService)
- require.True(t, ok, "Server.samlAuthServer is not type *samlAuthServer")
-
- _, err = CreateRole(ctx, a, "baz", types.RoleSpecV5{})
- require.NoError(t, err)
-
- caKey, err := rsa.GenerateKey(rand.Reader, 2048)
- require.NoError(t, err)
-
- tlsCert, err := tlsca.GenerateSelfSignedCAWithSigner(
- caKey,
- pkix.Name{
- CommonName: "server1",
- Organization: []string{"server1"},
- }, nil, defaults.CATTL)
- require.NoError(t, err)
- require.NotNil(t, tlsCert)
-
- keyPEM, certPEM, err := utils.GenerateSelfSignedSigningCert(pkix.Name{
- Organization: []string{"Teleport OSS"},
- CommonName: "teleport.localhost.localdomain",
- }, nil, 10*365*24*time.Hour)
- require.NoError(t, err)
-
- request := types.SAMLAuthRequest{
- ID: "ABC",
- ConnectorID: "zzz",
- CheckUser: false,
- PublicKey: nil,
- CertTTL: 0,
- CreateWebSession: false,
- SSOTestFlow: true,
- ConnectorSpec: &types.SAMLConnectorSpecV2{
- Issuer: "test",
- Audience: "test",
- ServiceProviderIssuer: "test",
- SSO: "test",
- Cert: string(tlsCert),
- AssertionConsumerService: "test",
- AttributesToRoles: []types.AttributeMapping{{
- Name: "foo",
- Value: "bar",
- Roles: []string{"baz"},
- }},
- SigningKeyPair: &types.AsymmetricKeyPair{
- PrivateKey: string(keyPEM),
- Cert: string(certPEM),
- },
- },
- }
-
- connector, provider, err := sas.getSAMLConnectorAndProvider(context.Background(), request)
- require.NoError(t, err)
- require.NotNil(t, connector)
- require.NotNil(t, provider)
-
- expectedConnector := &types.SAMLConnectorV2{Kind: "saml", Version: "v2", Metadata: types.Metadata{Name: "zzz", Namespace: apidefaults.Namespace}, Spec: *request.ConnectorSpec}
- require.Equal(t, expectedConnector, connector)
-
- require.Equal(t, "test", provider.IdentityProviderSSOURL)
- require.Equal(t, "test", provider.IdentityProviderIssuer)
- require.Equal(t, "test", provider.AssertionConsumerServiceURL)
- require.Equal(t, "test", provider.ServiceProviderIssuer)
-
- conn, err := types.NewSAMLConnector("foo", types.SAMLConnectorSpecV2{
- Issuer: "test",
- SSO: "test",
- Cert: string(tlsCert),
- AssertionConsumerService: "test",
- AttributesToRoles: []types.AttributeMapping{{
- Name: "foo",
- Value: "bar",
- Roles: []string{"baz"},
- }},
- })
- require.NoError(t, err)
-
- err = a.UpsertSAMLConnector(ctx, conn)
- require.NoError(t, err)
-
- request2 := types.SAMLAuthRequest{
- ID: "ABC",
- ConnectorID: "foo",
- SSOTestFlow: false,
- }
-
- connector, provider, err = sas.getSAMLConnectorAndProvider(context.Background(), request2)
- require.NoError(t, err)
- require.NotNil(t, connector)
- require.NotNil(t, provider)
-}
-
-func TestServer_ValidateSAMLResponse(t *testing.T) {
- t.Parallel()
-
- ctx := context.Background()
- clock := clockwork.NewFakeClockAt(time.Date(2022, 4, 25, 9, 0, 0, 0, time.UTC))
-
- // Create a Server instance for testing.
- b, err := memory.New(memory.Config{
- Context: ctx,
- Clock: clock,
- })
- require.NoError(t, err)
-
- clusterName, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{
- ClusterName: "me.localhost",
- })
- require.NoError(t, err)
-
- authConfig := &InitConfig{
- ClusterName: clusterName,
- Backend: b,
- Authority: authority.New(),
- SkipPeriodicOperations: true,
- KeyStoreConfig: keystore.Config{
- Software: keystore.SoftwareConfig{
- RSAKeyPairSource: authority.New().GenerateKeyPair,
- },
- },
- }
-
- a, err := NewServer(authConfig, WithClock(clock))
- require.NoError(t, err)
-
- sas, ok := a.samlAuthService.(*SAMLAuthService)
- require.True(t, ok, "Server.samlAuthServer is not type *samlAuthServer")
-
- // empty response gives error.
- response, err := a.ValidateSAMLResponse(context.Background(), "", "")
- require.Nil(t, response)
- require.Error(t, err)
-
- // create role referenced in request.
- role, err := types.NewRole("access", types.RoleSpecV5{
- Allow: types.RoleConditions{
- Logins: []string{"dummy"},
- },
- })
- require.NoError(t, err)
- err = a.CreateRole(ctx, role)
- require.NoError(t, err)
-
- // real response from Okta
- respOkta := `http://www.okta.com/exk14fxcpjuKMcor30h8uBRfvYvl5C/LPCh36uAmRLHW76+aDP3ngChtIwP3/Fc=M1VfkOOBH6r7niHhfGvf4OJ1HH5QJl83aD/b+mTDUUnXzHXgXlkb0BGQkSFn6ixojwCoXchpxCNzVLPN/tvfyY1dxP4MO8b+/07bGuVD2yTNlhN43/FFcDpmZ1ZDW8w2nPF1E5gy1lR8Wx2NgT3kQ2Ui1vRNX/KeX/P9NnABj4AjcshyHK2e49WLM/D4U84XOl7ODtzS7PTvtB0SGIwRE25G//8AsAv81eBfHL54Nz1HAqinMhxQtz32ZDXpKaAV6GypyBTvk6vo7Pkk4OiL6G9VIGC8Bd/gnavsc+Ickfuo7KTq8NDKTLB5WG34XKJqq6dGopSMrxr67oYjCEDZfw==MIIDpDCCAoygAwIBAgIGAX4zyofpMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG
-A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
-MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi04MTMzNTQxHDAaBgkqhkiG9w0BCQEW
-DWluZm9Ab2t0YS5jb20wHhcNMjIwMTA3MDkwNTU4WhcNMzIwMTA3MDkwNjU4WjCBkjELMAkGA1UE
-BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
-BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtODEzMzU0MRwwGgYJ
-KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
-xQz+tLD5cNlOBfdohHvqNWIfC13OCSnUAe20qA0K8y+jtZrpwjtjjLX8iRuCx8dYc/nd6zYOhhSq
-2sLmrRa09wUXXTgnLGcj50gePTaroYLyF4FNgQWLvPHJk0FGcx6JvD6L+V5RzYwH87Fhg8niP4LZ
-EBw3iZnsIJN9KOuLuQeXTW0PIlMFzpCwT9aUCHCoLepe5Ou8oi8XcOCmsOESHPchV2RC/xQDIqRP
-Lp1Sf7NNJ6mTmP2gOoLwsz95beOLrEI+PI/GgZBqM3OutWA0L9mAbJK9T5dPAvhnwCV+SK2HvicJ
-T8c6uJxuKmoWv1t3SyaN0cIbmw6vj9CIf4DTwQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCWGgLL
-f3tgUZRGjmR5iiKeOeaEWG/eaF1nfenVfSaWT9ckimcXyLCY/P7CXEBiioVrxjky07iceJpi4rVE
-RcVZ8SGXCa0NroESmIFlIHez6vRTrqUsfDmidxsSCwY02eaBq+9gK5iXV5WeXMKbn0yeGwF+3PkU
-RAH1HuypwMH0FJRLIdW36pw7FCrGrXpk3UC6mEumXC9FptjSK1FlW+ZckgDprePOoUpypEygr2UC
-XXOsqT0dwBUUttdOQMZHqIiXS5VPJ8zhYPHBGYI8WGk5FWVuXIXhgRm7LN/EyXIvCOFmDH0tVnQL
-V115UGOwvjOOxmOFbYBn865SHgMndFtrhttp://www.okta.com/exk14fxcpjuKMcor30h8XwJSotSzU2qLdzu/WDk8dpQ/Cy1Id88932S/95+N+Ds=qyIvGi1+w93AdGUj0+T5RYAq+CAjLSScMTMc7dLTEze6qr3mP51W/bCoZz8E47lpsbLeh0EiATa6h2Uaj6/34rILfCt3aQRNjNicu0gBKhePyNraapdnoyeqJEV8UrAOOKFiH30e5AvQ1nRZqfgY7KMt6cZH5/eXjUS63lPJJn4yr9vLw9loCdHCoHlaseh2IHi7CickyyxSMTX+Y58zpBy2g/KwN3K4oZM4a10ZYWkZpzkZJXDRSUkEc/wTTO7IPPY7Zv7R7UC+zjf5Px1sYeKTkkIxlZViZmtqjYuhibnTmhroJx7wX/LtOPxCkwLHlQRDACBNbP/UtrudU1ZMxA==MIIDpDCCAoygAwIBAgIGAX4zyofpMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG
-A1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU
-MBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi04MTMzNTQxHDAaBgkqhkiG9w0BCQEW
-DWluZm9Ab2t0YS5jb20wHhcNMjIwMTA3MDkwNTU4WhcNMzIwMTA3MDkwNjU4WjCBkjELMAkGA1UE
-BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV
-BAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtODEzMzU0MRwwGgYJ
-KoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
-xQz+tLD5cNlOBfdohHvqNWIfC13OCSnUAe20qA0K8y+jtZrpwjtjjLX8iRuCx8dYc/nd6zYOhhSq
-2sLmrRa09wUXXTgnLGcj50gePTaroYLyF4FNgQWLvPHJk0FGcx6JvD6L+V5RzYwH87Fhg8niP4LZ
-EBw3iZnsIJN9KOuLuQeXTW0PIlMFzpCwT9aUCHCoLepe5Ou8oi8XcOCmsOESHPchV2RC/xQDIqRP
-Lp1Sf7NNJ6mTmP2gOoLwsz95beOLrEI+PI/GgZBqM3OutWA0L9mAbJK9T5dPAvhnwCV+SK2HvicJ
-T8c6uJxuKmoWv1t3SyaN0cIbmw6vj9CIf4DTwQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCWGgLL
-f3tgUZRGjmR5iiKeOeaEWG/eaF1nfenVfSaWT9ckimcXyLCY/P7CXEBiioVrxjky07iceJpi4rVE
-RcVZ8SGXCa0NroESmIFlIHez6vRTrqUsfDmidxsSCwY02eaBq+9gK5iXV5WeXMKbn0yeGwF+3PkU
-RAH1HuypwMH0FJRLIdW36pw7FCrGrXpk3UC6mEumXC9FptjSK1FlW+ZckgDprePOoUpypEygr2UC
-XXOsqT0dwBUUttdOQMZHqIiXS5VPJ8zhYPHBGYI8WGk5FWVuXIXhgRm7LN/EyXIvCOFmDH0tVnQL
-V115UGOwvjOOxmOFbYBn865SHgMndFtrops@gravitational.iohttps://boson.tener.io:3080/v1/webapi/saml/acsurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportops@gravitational.ioEveryoneokta-adminokta-dev`
-
- caKey, err := rsa.GenerateKey(rand.Reader, 2048)
- require.NoError(t, err)
-
- tlsCert, err := tlsca.GenerateSelfSignedCAWithSigner(
- caKey,
- pkix.Name{
- CommonName: "server1",
- Organization: []string{"server1"},
- }, nil, defaults.CATTL)
- require.NoError(t, err)
- require.NotNil(t, tlsCert)
-
- keyPEM, certPEM, err := utils.GenerateSelfSignedSigningCert(pkix.Name{
- Organization: []string{"Teleport OSS"},
- CommonName: "teleport.localhost.localdomain",
- }, nil, 10*365*24*time.Hour)
- require.NoError(t, err)
-
- // SAML connector validation requires the roles in mappings exist.
- connectorRole, err := types.NewRole("baz", types.RoleSpecV5{})
- require.NoError(t, err)
- err = a.CreateRole(ctx, connectorRole)
- require.NoError(t, err)
-
- conn, err := types.NewSAMLConnector("saml-test-conn", types.SAMLConnectorSpecV2{
- Issuer: "test",
- SSO: "test",
- Cert: string(tlsCert),
- AssertionConsumerService: "test",
- AttributesToRoles: []types.AttributeMapping{{
- Name: "foo",
- Value: "bar",
- Roles: []string{connectorRole.GetName()},
- }},
- SigningKeyPair: &types.AsymmetricKeyPair{
- PrivateKey: string(keyPEM),
- Cert: string(certPEM),
- },
- })
- require.NoError(t, err)
-
- err = a.UpsertSAMLConnector(ctx, conn)
- require.NoError(t, err)
-
- err = a.Services.CreateSAMLAuthRequest(ctx, types.SAMLAuthRequest{
- ID: "_4f256462-6c2d-466d-afc0-6ee36602b6f2",
- ConnectorID: "saml-test-conn",
- SSOTestFlow: true,
- RedirectURL: "https://dev-813354.oktapreview.com/app/dev-813354_krzysztofssodev_1/exk14fxcpjuKMcor30h8/sso/saml?SAMLRequest=lFZZk6JKE%2F0rHc6jYbOIiMbtiSgWEREEQVBfbrAUUMiiFFLgr%2F9i7Jm%2BPXf75j5mUifz5MmMJH%2FDQVlcl%2BDeZtUe3u4Qty99WVR4%2BfzwNro31bIOMMLLKighXrbR0gHGdsm%2B0strU7d1VBejT5B%2FRwQYw6ZFdTV60eS30e9cws54jmcnfMTGE47n40mQRPSEh3DK8zQb8gk7evFgg1FdvY3YV3r0Yn3PKqIqRlX67wnD90d4uXZda2LtHHf0An6QkOoK30vYOLDpUAQP%2B%2B3bKGvbK15SVFjjunptYQWbV1Qvp7RAUx1DERgGV0R9q5QKIjx60TC%2BQ63CbVC1byOWZtkJzU3YmUsLy9lsyQjn0YsMcYuqoH3W8CNBDLuJwEynM%2B61vrTBtYEdguQ1qksquF4%2Fff790jwG%2FGjrBOM6ht3vDAX7C8MlfXTN77oR1c2UzgQK4%2FrJa%2FT12dTlk1nz9b8V9Bv1GftbjJcOSqugvTfwe5Nj%2FF7DkqIIIa9k%2Blo3KcXSNE3RC6ovixij9MvoAwtjrUrqpykFVV2hKCjQ4ymGAdusjl9AkdYNarPyHwLzFMN%2BCzyJGK5imBH1M69fjMJQNPeD3qSsG%2FilwcEEZwE747%2BH3MMENrCK4Mthr72NvvzaeD6hbhNUOKmbEv9s%2Fl9aP6kGqw4W9RXGE%2Fyjuu%2FUfj3g36hF%2FZWgjFKI2%2F8oHayiLz8J9h7FC4o7%2FGogv2z54Iyj8bXj7FAoDc5Dwf3O0atwvsq3Ju%2FKBm0Bcnh7MvoMfjo%2B5H83%2FzQ8H%2F1%2BR2wJqAs5PUmLBA%2B5pPtrwRn4rhbER6QzrNHr9h1nztqm1S0kJT1Oo0NfxHRdnvx%2B27Lurbdl8FgdEZvM3FBqnV1DeHisSX7ezTHT24DMvdJBpb%2FlrVj0UmQk%2BVHhB1MyU65CSl2t1cHyBANokpgwPbrm86ne%2BBssK0UEVoAqlKFREkvel%2BWM7Qkv7MVOsUg0X%2FM3Yc1x2prVjXw4CUVxccn6UO53BsObdG4MpcBNGcYaWyp9NBQXcRTKtqSdVpyo7zM7PA6OQ53nJ0%2BMTGtqHpFmVLfxdnaKUpMa32Wt2N%2Bsy0NYr8aC7gxb5KPAnR%2Flta%2B0VaWa8I5XdyHSZ3XZiMM4Ho9tKaHA29uH9J%2B0%2Fia%2FDoePVhxn9EIO2uDDkL7t0wRFQQu%2FGpom6w9JAtkuBUQTQartwVbT871EMqWEfmR2ZGASUToQ2T5t9PqsZV1kAlvZijYgtqx4hmiogDkoUmYYnurh81HsospOXcZ0DSciG%2Fske7YtK%2F2MPvt9EamLIZZmOGTNLFSzLpra91Bd5Ce%2Fv4QskwU%2BR9ZZZBq5RkxZYww5ZYyHwvnffI%2Bnb%2Fjw5SIw9geikGcOXSH94Y8conVgjH7zAIWYmp4IDHdd7YtQ9Ug43dDbsu9O7AoH6uLxB599F%2Fqra5hLEnC0P9csijaQ01SxgCxJwK6lNFVEYAZRd7v7M%2BFkb1nIzriDojbZoOe3vb2z8xUppklq%2BWaQ9tNq84j53vOPQqEG7KbjFFo93MIT2GoBVXgNOCVuy9oqdDirWoiHq6pTV9NKdySK52MZjvO5hjMLye6miZrHoqR7nN96vqwyTKsb%2FZTz7W63urT12CWsdlmV05vNRw6rcpcdjw26i3w%2FgNhShGyfn%2BVNfM%2Bgf7xk1IWiu16cx7QVxoXZFJej1yyctR%2FHM51Hjqe4xgy4sraoSXSSbuJj8O%2F0GMw4xO38u5s6F3Z6m7GPqddGEb3SRKII6%2FMBGHGHUkd30Km51Xi%2FSFhm3%2FN%2BRBToqKs5ZeJzCQKj7u1wk%2FYL9mblYsBeBAkQBYDASA2RI3J6kr09bQF7TYnAlkEKgQHIt7mLFaKIFLElA4C%2Fm1H52SP56MXrtT1di%2FxxbqOHV9mUlKYPZacgQ2KkAveXIzxXIrPm3etKKRd5787nrSeqIt0vYjAopNyRE0iT8WrmbrSCL2%2FJoIVHfXxc9bcgquX1tWmt9lbe1ky7Z2l1EVlC2SdhOQ5v3LzZ%2BSbTpqDUir4P9vyZVV2Ffehk1g15fMYOiqX%2BEixOsS%2BAVImuldfCW583mHN2UYjijYbHC7PqW3DdWIa%2B2l3CS5dv%2BtkCXxIJm1VzEeSpBJGM3Dm1NuStwUXCVHFVmpMo52E6nVpcE54uXS2%2FDI8hBnqnIR2hBWZLRkfuDqu6d5GVZmyFRl1zA3%2Fci3Tr1WUs2f1DOm9lzCPSIVU9wGZOtPcF9Oel8uF8XzvU54X008L6%2Bv2gNYMSarJVFygaXkBR1ERqYNDCt1Hb3OHoZVU3ZdD%2B8%2B3IvDJPD4onyfPp8l7hK4xQgmD8%2FKf%2B9XD%2B%2Br8AAAD%2F%2Fw%3D%3D",
- ClientRedirectURL: "http://127.0.0.1:57293/callback?secret_key=70e98f8871530e66e6a136ae71fe002454dc1c76d754f090f895508a2226c36b",
- ConnectorSpec: &types.SAMLConnectorSpecV2{
- Issuer: "http://www.okta.com/exk14fxcpjuKMcor30h8",
- SSO: "https://dev-813354.oktapreview.com/app/dev-813354_krzysztofssodev_1/exk14fxcpjuKMcor30h8/sso/saml",
- Cert: "",
- Display: "Okta",
- AssertionConsumerService: "https://boson.tener.io:3080/v1/webapi/saml/acs",
- Audience: "https://boson.tener.io:3080/v1/webapi/saml/acs",
- ServiceProviderIssuer: "https://boson.tener.io:3080/v1/webapi/saml/acs",
- EntityDescriptor: "\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\u003cmd:EntityDescriptor entityID=\"http://www.okta.com/exk14fxcpjuKMcor30h8\" xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\"\u003e\u003cmd:IDPSSODescriptor WantAuthnRequestsSigned=\"false\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\"\u003e\u003cmd:KeyDescriptor use=\"signing\"\u003e\u003cds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"\u003e\u003cds:X509Data\u003e\u003cds:X509Certificate\u003eMIIDpDCCAoygAwIBAgIGAX4zyofpMA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG\nA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\nMBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi04MTMzNTQxHDAaBgkqhkiG9w0BCQEW\nDWluZm9Ab2t0YS5jb20wHhcNMjIwMTA3MDkwNTU4WhcNMzIwMTA3MDkwNjU4WjCBkjELMAkGA1UE\nBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV\nBAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtODEzMzU0MRwwGgYJ\nKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\nxQz+tLD5cNlOBfdohHvqNWIfC13OCSnUAe20qA0K8y+jtZrpwjtjjLX8iRuCx8dYc/nd6zYOhhSq\n2sLmrRa09wUXXTgnLGcj50gePTaroYLyF4FNgQWLvPHJk0FGcx6JvD6L+V5RzYwH87Fhg8niP4LZ\nEBw3iZnsIJN9KOuLuQeXTW0PIlMFzpCwT9aUCHCoLepe5Ou8oi8XcOCmsOESHPchV2RC/xQDIqRP\nLp1Sf7NNJ6mTmP2gOoLwsz95beOLrEI+PI/GgZBqM3OutWA0L9mAbJK9T5dPAvhnwCV+SK2HvicJ\nT8c6uJxuKmoWv1t3SyaN0cIbmw6vj9CIf4DTwQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCWGgLL\nf3tgUZRGjmR5iiKeOeaEWG/eaF1nfenVfSaWT9ckimcXyLCY/P7CXEBiioVrxjky07iceJpi4rVE\nRcVZ8SGXCa0NroESmIFlIHez6vRTrqUsfDmidxsSCwY02eaBq+9gK5iXV5WeXMKbn0yeGwF+3PkU\nRAH1HuypwMH0FJRLIdW36pw7FCrGrXpk3UC6mEumXC9FptjSK1FlW+ZckgDprePOoUpypEygr2UC\nXXOsqT0dwBUUttdOQMZHqIiXS5VPJ8zhYPHBGYI8WGk5FWVuXIXhgRm7LN/EyXIvCOFmDH0tVnQL\nV115UGOwvjOOxmOFbYBn865SHgMndFtr\u003c/ds:X509Certificate\u003e\u003c/ds:X509Data\u003e\u003c/ds:KeyInfo\u003e\u003c/md:KeyDescriptor\u003e\u003cmd:NameIDFormat\u003eurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\u003c/md:NameIDFormat\u003e\u003cmd:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"https://dev-813354.oktapreview.com/app/dev-813354_krzysztofssodev_1/exk14fxcpjuKMcor30h8/sso/saml\"/\u003e\u003cmd:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"https://dev-813354.oktapreview.com/app/dev-813354_krzysztofssodev_1/exk14fxcpjuKMcor30h8/sso/saml\"/\u003e\u003c/md:IDPSSODescriptor\u003e\u003c/md:EntityDescriptor\u003e",
- EntityDescriptorURL: "",
- AttributesToRoles: []types.AttributeMapping{
- {
- Name: "groups",
- Value: "okta-admin",
- Roles: []string{"access"},
- },
- },
- SigningKeyPair: &types.AsymmetricKeyPair{
- PrivateKey: "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA1py+q5bnxhAvZ7bnhQQauHIqOpFA5CMXCXd+A9Y1qDHecnN3\nrFVZfyUZrYm/gTQZSptgAshr+VWsBh9O3ZAZ5Lg+f0FSkYr+k0+A7Bx3v4N76Psi\nyE+INMmtyvP2bTGyOrHqaeGzQYkpiPq044WS2j5PDYiQWbepDpxLYbiQ7qwzS9xZ\nZp6w8TyFGNkMl26F5ZeSH+T/S/EHt3Q9t2U2uWRdWv1IdZ13krqJJURMzkBMMj2j\nBxgKoHPJa7T4DniLg5a5OBKTbernbPdW1xzQUgHATwdlQAx2+KBIpKJiuqixH1/b\nVHHpZzAR5IYXv82xmYBoyjFBsmDH3ao+MFraTwIDAQABAoIBAQCEXZbINEHtiiwC\n1u/Cvb5RRrC/ALm6O95Ii3egnCzp+SAPDSKhmt6hKdvFifEgmmaC+oPkE4Ns/Ccm\ne4bj5q3hwLVjPYHUnJrZdq64cfJ1n338O3C/hTYoAL/9Li0uOfmIdBV1iqxJ3nRM\ntPx+W/MwQj/1w+XsP/e4ODPSKMjTOyZkLVhArLA2qBM3l1NBWQw4EV96m1Dvjq2o\nxFYhSODZOYDYXq82NZya3cBzj30kEB/6fNk6qPAsMa3Ck7F/9mx3MA2XM/S0aB/U\nq+5I1g+mTAMPKa2c2Tv2hwWuRU9ddKGiXuw/gHPoBwEU7AVNk+nNSVDhj5/pqcFB\nM/lPNg6xAoGBAPu/oICyXwlXYfsvkTx+HHpY7Imq8RtBUjVpD3z+LC6+QydlF2Z/\nNqLDxBPZAAdA7VGzw5QdNR8DKY9EgeRQAcci8FIqjueTPQDVKl9kwJzOxqg8DM8t\nR8YpIOtP22JvjHAkFafBq9cWYZNLQwVabIkCdc0jUruN53WKRkj+b0npAoGBANo8\nkX7ypsS+riLu6Ez89tXHV78CW8eMb/Wxsa2hgrzd7KmgjYdj2wlfRaEY6pSvBA1Y\nMvy/Emvm2KhwuGzWWPJvoaQq/Hr6Puns9DVP8U3TTJDC3R5dDZUQ0DiVUGyez9Qu\nXV5aNMnXFjGPdRQYjGM1zG6677dM0CVYu/6MVSd3AoGBAN5X3tALufgsHzOUTXfa\nAhjk1PS573yc8piNk8pXSnp2PCVdGY/DJ2QV9uV4sJe3dmLEnCYCrdoYFuqcHQSi\nzQ8uAobvY4uP9T75BhV+jMdxsO8BKmcInO2dgZ+SxjZoQucAV8f0O2saL0/CFw1x\nUY6oh5aIbheMOzMKzwzE+1GRAoGAYm5FFWPuUfjK49irj+XckulZKz6uFJ/D86YU\nxIJ/TB4wWwWeL/2a0mxVJGbvjuYtRrOMM7EeZup0t+w3UmePMLGmzzvQKstpyupj\n7xPCe16dPwGU59gCg0RVFeBKqOMsS8Apvp+jBZJsYSgaH1k/IJQoQ50u95a+nsmZ\n6SJ0WdsCgYBfTcIJ456LNPQ7sjDtcqIQcbBgXYIt/u4dIhz0zuLSfvTrXAtcy7nU\nEDU+N2Ay715GmS+/iwc592Itam93t3sl1ql/Y+SrrxGQ7zRd6MALKIxM0+4qptB5\nDFXT1B5qhKHdZFr71AIGPrUjfsRQV8tPKFjRkuQ0zqEPvi4g06RMRw==\n-----END RSA PRIVATE KEY-----\n",
- Cert: "-----BEGIN CERTIFICATE-----\nMIIDKzCCAhOgAwIBAgIRALIKjRCwhEmeWcNvwy1fBCUwDQYJKoZIhvcNAQELBQAw\nQDEVMBMGA1UEChMMVGVsZXBvcnQgT1NTMScwJQYDVQQDEx50ZWxlcG9ydC5sb2Nh\nbGhvc3QubG9jYWxkb21haW4wHhcNMjIwNDI1MDg1MzE4WhcNMzIwNDIyMDg1MzE4\nWjBAMRUwEwYDVQQKEwxUZWxlcG9ydCBPU1MxJzAlBgNVBAMTHnRlbGVwb3J0Lmxv\nY2FsaG9zdC5sb2NhbGRvbWFpbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBANacvquW58YQL2e254UEGrhyKjqRQOQjFwl3fgPWNagx3nJzd6xVWX8lGa2J\nv4E0GUqbYALIa/lVrAYfTt2QGeS4Pn9BUpGK/pNPgOwcd7+De+j7IshPiDTJrcrz\n9m0xsjqx6mnhs0GJKYj6tOOFkto+Tw2IkFm3qQ6cS2G4kO6sM0vcWWaesPE8hRjZ\nDJduheWXkh/k/0vxB7d0PbdlNrlkXVr9SHWdd5K6iSVETM5ATDI9owcYCqBzyWu0\n+A54i4OWuTgSk23q52z3Vtcc0FIBwE8HZUAMdvigSKSiYrqosR9f21Rx6WcwEeSG\nF7/NsZmAaMoxQbJgx92qPjBa2k8CAwEAAaMgMB4wDgYDVR0PAQH/BAQDAgeAMAwG\nA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBADXVdHHQ3HB6X7QizVnQ/Cgg\nzEOEiMC1ClsxkXeZnB1H6TpFEm9jxT77tVBGB0x9dAyEwmOwYAgf+F5TJIl6mqfy\nIbXK+XFxqacoDHprtPtqmqH1tR20G9cP8mxfbm+bq47rOWN1tgAmIlxxaR6Z2GTE\n2zKw5vyjdZsSidCxka9YdW8AgEcpnVteqxjrs4SOcbidJIs+9NnxtApJPMKFOkbk\nvjJx59skfCsNnrk8D3CeiDiT7/HMDLM4c83ETG04C/SzNSvGlpf60mTIjkyzydAK\nvIiKii9s2m1KiTOsGKVkDEr+PbMoo4y6XRB0tVomdCQxzCZLDs6iwviGGUer7wI=\n-----END CERTIFICATE-----\n",
- },
- Provider: "",
- EncryptionKeyPair: nil,
- },
- }, defaults.SAMLAuthRequestTTL)
- require.NoError(t, err)
-
- // check ValidateSAMLResponse
- response, err = sas.ValidateSAMLResponse(context.Background(), base64.StdEncoding.EncodeToString([]byte(respOkta)), "")
- require.NoError(t, err)
- require.NotNil(t, response)
-
- // check internal method, validate diagnostic outputs.
- diagCtx := NewSSODiagContext(types.KindSAML, a)
- auth, err := sas.validateSAMLResponse(context.Background(), diagCtx, base64.StdEncoding.EncodeToString([]byte(respOkta)), "")
- require.NoError(t, err)
-
- // ensure diag info got stored and is identical.
- infoFromBackend, err := a.GetSSODiagnosticInfo(context.Background(), types.KindSAML, auth.Req.ID)
- require.NoError(t, err)
- require.Equal(t, &diagCtx.Info, infoFromBackend)
-
- // verify values
- require.Equal(t, "ops@gravitational.io", auth.Username)
- require.Equal(t, "ops@gravitational.io", auth.Identity.Username)
- require.Equal(t, "saml-test-conn", auth.Identity.ConnectorID)
- require.Equal(t, "_4f256462-6c2d-466d-afc0-6ee36602b6f2", auth.Req.ID)
- require.Equal(t, 0, len(auth.HostSigners))
-
- authnInstant := time.Date(2022, 4, 25, 8, 3, 11, 779000000, time.UTC)
-
- // ignore, this is boring and very complex.
- require.NotNil(t, diagCtx.Info.SAMLAssertionInfo.Assertions)
- diagCtx.Info.SAMLAssertionInfo.Assertions = nil
-
- require.Equal(t, types.SSODiagnosticInfo{
- TestFlow: true,
- Error: "",
- Success: true,
- CreateUserParams: &types.CreateUserParams{
- ConnectorName: "saml-test-conn",
- Username: "ops@gravitational.io",
- Roles: []string{"access"},
- Traits: map[string][]string{
- "groups": {"Everyone", "okta-admin", "okta-dev"},
- "username": {"ops@gravitational.io"},
- },
- SessionTTL: 108000000000000,
- },
- SAMLAttributesToRoles: []types.AttributeMapping{
- {
- Name: "groups",
- Value: "okta-admin",
- Roles: []string{"access"},
- },
- },
- SAMLAttributesToRolesWarnings: nil,
- SAMLAttributeStatements: map[string][]string{
- "groups": {"Everyone", "okta-admin", "okta-dev"},
- "username": {"ops@gravitational.io"},
- },
- SAMLAssertionInfo: &types.AssertionInfo{
- NameID: "ops@gravitational.io",
- Values: map[string]samltypes.Attribute{
- "groups": {
- XMLName: xml.Name{Space: "urn:oasis:names:tc:SAML:2.0:assertion", Local: "Attribute"},
- Name: "groups",
- NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified",
- Values: []samltypes.AttributeValue{
- {
- XMLName: xml.Name{Space: "urn:oasis:names:tc:SAML:2.0:assertion", Local: "AttributeValue"},
- Value: "Everyone",
- },
- {
- XMLName: xml.Name{Space: "urn:oasis:names:tc:SAML:2.0:assertion", Local: "AttributeValue"},
- Value: "okta-admin",
- },
- {
- XMLName: xml.Name{Space: "urn:oasis:names:tc:SAML:2.0:assertion", Local: "AttributeValue"},
- Value: "okta-dev",
- },
- },
- },
- "username": {
- XMLName: xml.Name{Space: "urn:oasis:names:tc:SAML:2.0:assertion", Local: "Attribute"},
- Name: "username",
- NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified",
- Values: []samltypes.AttributeValue{
- {
- XMLName: xml.Name{Space: "urn:oasis:names:tc:SAML:2.0:assertion", Local: "AttributeValue"},
- Value: "ops@gravitational.io",
- },
- },
- },
- },
- WarningInfo: &saml2.WarningInfo{},
- SessionIndex: "_4f256462-6c2d-466d-afc0-6ee36602b6f2",
- AuthnInstant: &authnInstant,
- SessionNotOnOrAfter: nil,
- Assertions: nil,
- ResponseSignatureValidated: true,
- },
- SAMLTraitsFromAssertions: map[string][]string{
- "groups": {"Everyone", "okta-admin", "okta-dev"},
- "username": {"ops@gravitational.io"},
- },
- SAMLConnectorTraitMapping: []types.TraitMapping{
- {
- Trait: "groups",
- Value: "okta-admin",
- Roles: []string{"access"},
- },
- },
- }, diagCtx.Info)
-
- // make sure no users have been created.
- users, err := a.GetUsers(false)
- require.NoError(t, err)
- require.Equal(t, 0, len(users))
-}