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)) -}