Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Github connector #1512

Merged
merged 5 commits into from
Dec 15, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ const (
ConnectorOIDC = "oidc"

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

// ConnectorGithub means connector type Github
ConnectorGithub = "github"

// DataDirParameterName is the name of the data dir configuration parameter passed
// to all backends during initialization
Expand Down Expand Up @@ -148,11 +151,14 @@ const (
// Local means authentication will happen locally within the Teleport cluster.
Local = "local"

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

// SAML means authentication will happen remotely using a SAML connector.
SAML = ConnectorSAML

// SAML means authentication will happen remotly using an SAML connector.
SAML = "saml"
// Github means authentication will happen remotely using a Github connector.
Github = ConnectorGithub

// JSON means JSON serialization format
JSON = "json"
Expand Down
71 changes: 71 additions & 0 deletions docs/2.3/admin-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ they execute `tsh login` command. There are three types of authentication connec
Local authentication is used to authenticate against a local Teleport user database. This database
is managed by `tctl users` command. Teleport also supports second factor authentication
(2FA) for the local connector. There are two types of 2FA:

* [TOTP](https://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm)
is the default. You can use [Google Authenticator](https://en.wikipedia.org/wiki/Google_Authenticator) or
[Authy](https://www.authy.com/) or any other TOTP client.
Expand All @@ -367,6 +368,22 @@ auth_service:
second_factor: u2f
```

**Github OAuth 2.0**

This connector implements Github OAuth 2.0 authentication flow. Please refer
to Github documentation on [Creating an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/)
to learn how to create and register an OAuth app.

Here is an example of this setting in the `teleport.yaml`:

```yaml
auth_service:
authentication:
type: github
```

See [Github OAuth 2.0](#github-oauth-20) for details on how to configure it.

**SAML**

This connector type implements SAML authentication. It can be configured
Expand Down Expand Up @@ -857,6 +874,7 @@ user | A user record in the internal Teleport user DB.
node | A registered SSH node. The same record is displayed via `tctl nodes ls`
trusted_cluster | A trusted cluster. See [here](#trusted-clusters) for more details on connecting clusters together.
role | A role assumed by users. The open source Teleport only includes one role: "admin", but Enterprise teleport users can define their own roles.
github | A Github auth connector. See [here](#github-auth-connector) for details on configuring it.

## Trusted Clusters

Expand Down Expand Up @@ -984,6 +1002,59 @@ db2.east 3879d133-fe81-3212 10.0.5.3:3022 role=db-slave
$ tsh --cluster=east ssh root@db1.east
```

## Github OAuth 2.0

Teleport supports authentication and authorization via external identity
providers such as Github. It can be configured by creating a Github connector
resource:

```bash
# github.yaml
kind: github
version: v3
metadata:
# connector name that will be used with `tsh login`
name: github
spec:
# client ID of Github OAuth app
client_id: <client-id>
# client secret of Github OAuth app
client_secret: <client-secret>
# connector display name that will be shown on web UI login screen
display: Github
# callback URL that will be called after successful authentication
redirect_url: https://<proxy-address>/v1/webapi/github/callback
# mapping of org/team memberships onto allowed logins and roles
teams_to_logins:
- organization: octocats # Github organization name
team: admins # Github team name within that organization
# allowed logins for users in this org/team
logins:
- root
```

!!! note
For open-source Teleport the `logins` field contains a list of allowed
OS logins. For paid Teleport plans such as Enterprise, Pro or Business
that support role-based access control, the same field is treated as a
list of _roles_ that users from matching org/team assume after going
through the authorization flow.

To obtain client ID and client secret, please follow Github documentation
on how to [create and register an OAuth app](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/).
Be sure to set the "Authorization callback URL" to the same value as `redirect_url`
in the resource spec. Create the resource:

```bash
$ tctl create github.yaml
```

!!! tip
When going through the Github authentication flow for the first time,
the application must be granted the access to all organizations that
are present in the "teams to logins" mapping, otherwise Teleport will
not be able to determine team memberships for these orgs.

## HTTP CONNECT Proxies

Some networks funnel all connections through a proxy server where they can be
Expand Down
2 changes: 1 addition & 1 deletion e
Submodule e updated from 252d93 to 413ede
209 changes: 209 additions & 0 deletions lib/auth/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,15 @@ func NewAPIServer(config *APIConfig) http.Handler {
srv.POST("/:version/saml/requests/create", srv.withAuth(srv.createSAMLAuthRequest))
srv.POST("/:version/saml/requests/validate", srv.withAuth(srv.validateSAMLResponse))

// Github connector
srv.POST("/:version/github/connectors", srv.withAuth(srv.createGithubConnector))
srv.PUT("/:version/github/connectors", srv.withAuth(srv.upsertGithubConnector))
srv.GET("/:version/github/connectors", srv.withAuth(srv.getGithubConnectors))
srv.GET("/:version/github/connectors/:id", srv.withAuth(srv.getGithubConnector))
srv.DELETE("/:version/github/connectors/:id", srv.withAuth(srv.deleteGithubConnector))
srv.POST("/:version/github/requests/create", srv.withAuth(srv.createGithubAuthRequest))
srv.POST("/:version/github/requests/validate", srv.withAuth(srv.validateGithubAuthCallback))

// U2F
srv.GET("/:version/u2f/signuptokens/:token", srv.withAuth(srv.getSignupU2FRegisterRequest))
srv.POST("/:version/u2f/users", srv.withAuth(srv.createUserWithU2FToken))
Expand Down Expand Up @@ -1423,6 +1432,206 @@ func (s *APIServer) validateSAMLResponse(auth ClientI, w http.ResponseWriter, r
return &raw, nil
}

// createGithubConnectorRawReq is a request to create a new Github connector
type createGithubConnectorRawReq struct {
// Connector is the connector data
Connector json.RawMessage `json:"connector"`
}

/* createGithubConnector creates a new Github connector

POST /:version/github/connectors

Success response: {"message": "ok"}
*/
func (s *APIServer) createGithubConnector(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req createGithubConnectorRawReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
connector, err := services.GetGithubConnectorMarshaler().Unmarshal(req.Connector)
if err != nil {
return nil, trace.Wrap(err)
}
if err := auth.CreateGithubConnector(connector); err != nil {
return nil, trace.Wrap(err)
}
return message("ok"), nil
}

// upsertGithubConnectorRawReq is a request to upsert a Github connector
type upsertGithubConnectorRawReq struct {
// Connector is the connector data
Connector json.RawMessage `json:"connector"`
}

/* upsertGithubConnector creates or updates a Github connector

PUT /:version/github/connectors

Success response: {"message": "ok"}
*/
func (s *APIServer) upsertGithubConnector(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req upsertGithubConnectorRawReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
connector, err := services.GetGithubConnectorMarshaler().Unmarshal(req.Connector)
if err != nil {
return nil, trace.Wrap(err)
}
if err := auth.UpsertGithubConnector(connector); err != nil {
return nil, trace.Wrap(err)
}
return message("ok"), nil
}

/* getGithubConnectors returns a list of all configured Github connectors

GET /:version/github/connectors

Success response: []services.GithubConnector
*/
func (s *APIServer) getGithubConnectors(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.GetGithubConnectors(withSecrets)
if err != nil {
return nil, trace.Wrap(err)
}
items := make([]json.RawMessage, len(connectors))
for i, connector := range connectors {
bytes, err := services.GetGithubConnectorMarshaler().Marshal(connector)
if err != nil {
return nil, trace.Wrap(err)
}
items[i] = bytes
}
return items, nil
}

/* getGithubConnector returns the specified Github connector

GET /:version/github/connectors/:id

Success response: services.GithubConnector
*/
func (s *APIServer) getGithubConnector(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.GetGithubConnector(p.ByName("id"), withSecrets)
if err != nil {
return nil, trace.Wrap(err)
}
return rawMessage(services.GetGithubConnectorMarshaler().Marshal(connector))
}

/* deleteGithubConnector deletes the specified Github connector

DELETE /:version/github/connectors/:id

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

// createGithubAuthRequestReq is a request to start Github OAuth2 flow
type createGithubAuthRequestReq struct {
// Req is the request parameters
Req services.GithubAuthRequest `json:"req"`
}

/* createGithubAuthRequest creates a new request for Github OAuth2 flow

POST /:version/github/requests/create

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

// validateGithubAuthCallbackReq is a request to validate Github OAuth2 callback
type validateGithubAuthCallbackReq struct {
// Query is the callback query string
Query url.Values `json:"query"`
}

// githubAuthRawResponse is returned when auth server validated callback
// parameters returned from Github during OAuth2 flow
type githubAuthRawResponse 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.GithubAuthRequest `json:"req"`
// HostSigners is a list of signing host public keys
// trusted by proxy, used in console login
HostSigners []json.RawMessage `json:"host_signers"`
}

/* validateGithubAuthRequest validates Github auth callback redirect

POST /:version/github/requests/validate

Success response: githubAuthRawResponse
*/
func (s *APIServer) validateGithubAuthCallback(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
var req validateGithubAuthCallbackReq
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}
response, err := auth.ValidateGithubAuthCallback(req.Query)
if err != nil {
return nil, trace.Wrap(err)
}
raw := githubAuthRawResponse{
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