Skip to content

Commit

Permalink
SAML 2.0 initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
klizhentas authored and russjones committed May 12, 2017
1 parent 0cb6bd2 commit f864168
Show file tree
Hide file tree
Showing 74 changed files with 8,984 additions and 130 deletions.
35 changes: 34 additions & 1 deletion Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ const (
// ConnectorOIDC means connector type OIDC
ConnectorOIDC = "oidc"

// ConnectorSAML means connector type SAML
ConnectorSAML = "oidc"

// DataDirParameterName is the name of the data dir configuration parameter passed
// to all backends during initialization
DataDirParameterName = "data_dir"
Expand Down Expand Up @@ -115,6 +118,9 @@ const (

// OIDC means authentication will happen remotly using an OIDC connector.
OIDC = "oidc"

// SAML means authentication will happen remotly using an SAML connector.
SAML = "saml"
)

const (
Expand Down
162 changes: 161 additions & 1 deletion lib/auth/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,15 @@ func NewAPIServer(config *APIConfig) http.Handler {
srv.POST("/:version/oidc/requests/create", srv.withAuth(srv.createOIDCAuthRequest))
srv.POST("/:version/oidc/requests/validate", srv.withAuth(srv.validateOIDCAuthCallback))

// SAML handlers
srv.POST("/:version/saml/connectors", srv.withAuth(srv.createSAMLConnector))
srv.PUT("/:version/saml/connectors", srv.withAuth(srv.upsertSAMLConnector))
srv.GET("/:version/saml/connectors", srv.withAuth(srv.getSAMLConnectors))
srv.GET("/:version/saml/connectors/:id", srv.withAuth(srv.getSAMLConnector))
srv.DELETE("/:version/saml/connectors/:id", srv.withAuth(srv.deleteSAMLConnector))
srv.POST("/:version/saml/requests/create", srv.withAuth(srv.createSAMLAuthRequest))
srv.POST("/:version/saml/requests/validate", srv.withAuth(srv.validateSAMLResponse))

// U2F
srv.GET("/:version/u2f/signuptokens/:token", srv.withAuth(srv.getSignupU2FRegisterRequest))
srv.POST("/:version/u2f/users", srv.withAuth(srv.createUserWithU2FToken))
Expand Down Expand Up @@ -1128,7 +1137,7 @@ type oidcAuthRawResponse struct {
// Username is authenticated teleport username
Username string `json:"username"`
// Identity contains validated OIDC identity
Identity services.OIDCIdentity `json:"identity"`
Identity services.ExternalIdentity `json:"identity"`
// Web session will be generated by auth server if requested in OIDCAuthRequest
Session json.RawMessage `json:"session,omitempty"`
// Cert will be generated by certificate authority
Expand Down Expand Up @@ -1173,6 +1182,157 @@ func (s *APIServer) validateOIDCAuthCallback(auth ClientI, w http.ResponseWriter
return &raw, nil
}

type createSAMLConnectorRawReq struct {
Connector json.RawMessage `json:"connector"`
}

func (s *APIServer) createSAMLConnector(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req *createSAMLConnectorRawReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
connector, err := services.GetSAMLConnectorMarshaler().UnmarshalSAMLConnector(req.Connector)
if err != nil {
return nil, trace.Wrap(err)
}
err = auth.CreateSAMLConnector(connector)
if err != nil {
return nil, trace.Wrap(err)
}
return message("ok"), nil
}

type upsertSAMLConnectorRawReq struct {
Connector json.RawMessage `json:"connector"`
}

func (s *APIServer) upsertSAMLConnector(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req *upsertSAMLConnectorRawReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
connector, err := services.GetSAMLConnectorMarshaler().UnmarshalSAMLConnector(req.Connector)
if err != nil {
return nil, trace.Wrap(err)
}
err = auth.UpsertSAMLConnector(connector)
if err != nil {
return nil, trace.Wrap(err)
}
return message("ok"), nil
}

func (s *APIServer) getSAMLConnector(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
withSecrets, _, err := httplib.ParseBool(r.URL.Query(), "with_secrets")
if err != nil {
return nil, trace.Wrap(err)
}
connector, err := auth.GetSAMLConnector(p.ByName("id"), withSecrets)
if err != nil {
return nil, trace.Wrap(err)
}
return rawMessage(services.GetSAMLConnectorMarshaler().MarshalSAMLConnector(connector, services.WithVersion(version)))
}

func (s *APIServer) deleteSAMLConnector(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
err := auth.DeleteSAMLConnector(p.ByName("id"))
if err != nil {
return nil, trace.Wrap(err)
}
return message("ok"), nil
}

func (s *APIServer) getSAMLConnectors(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
withSecrets, _, err := httplib.ParseBool(r.URL.Query(), "with_secrets")
if err != nil {
return nil, trace.Wrap(err)
}
connectors, err := auth.GetSAMLConnectors(withSecrets)
if err != nil {
return nil, trace.Wrap(err)
}
items := make([]json.RawMessage, len(connectors))
for i, connector := range connectors {
data, err := services.GetSAMLConnectorMarshaler().MarshalSAMLConnector(connector, services.WithVersion(version))
if err != nil {
return nil, trace.Wrap(err)
}
items[i] = data
}
return items, nil
}

type createSAMLAuthRequestReq struct {
Req services.SAMLAuthRequest `json:"req"`
}

func (s *APIServer) createSAMLAuthRequest(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req *createSAMLAuthRequestReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
response, err := auth.CreateSAMLAuthRequest(req.Req)
if err != nil {
return nil, trace.Wrap(err)
}
return response, nil
}

type validateSAMLResponseReq struct {
Response string `json:"response"`
}

// samlAuthRawResponse is returned when auth server validated callback parameters
// returned from SAML provider
type samlAuthRawResponse struct {
// Username is authenticated teleport username
Username string `json:"username"`
// Identity contains validated OIDC identity
Identity services.ExternalIdentity `json:"identity"`
// Web session will be generated by auth server if requested in OIDCAuthRequest
Session json.RawMessage `json:"session,omitempty"`
// Cert will be generated by certificate authority
Cert []byte `json:"cert,omitempty"`
// Req is original oidc auth request
Req services.SAMLAuthRequest `json:"req"`
// HostSigners is a list of signing host public keys
// trusted by proxy, used in console login
HostSigners []json.RawMessage `json:"host_signers"`
}

func (s *APIServer) validateSAMLResponse(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req *validateSAMLResponseReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
response, err := auth.ValidateSAMLResponse(req.Response)
if err != nil {
return nil, trace.Wrap(err)
}
raw := samlAuthRawResponse{
Username: response.Username,
Identity: response.Identity,
Cert: response.Cert,
Req: response.Req,
}
if response.Session != nil {
rawSession, err := services.GetWebSessionMarshaler().MarshalWebSession(response.Session, services.WithVersion(version))
if err != nil {
return nil, trace.Wrap(err)
}
raw.Session = rawSession
}
raw.HostSigners = make([]json.RawMessage, len(response.HostSigners))
for i, ca := range response.HostSigners {
data, err := services.GetCertAuthorityMarshaler().MarshalCertAuthority(ca, services.WithVersion(version))
if err != nil {
return nil, trace.Wrap(err)
}
raw.HostSigners[i] = data
}
return &raw, nil
}

// HTTP GET /:version/events?query
//
// Query fields:
Expand Down
Loading

0 comments on commit f864168

Please sign in to comment.