Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
492 changes: 353 additions & 139 deletions api/gen/proto/go/teleport/plugins/v1/plugin_service.pb.go

Large diffs are not rendered by default.

53 changes: 47 additions & 6 deletions api/gen/proto/go/teleport/plugins/v1/plugin_service_grpc.pb.go

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

27 changes: 27 additions & 0 deletions api/proto/teleport/plugins/v1/plugin_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ import "teleport/legacy/types/types.proto";

option go_package = "github.com/gravitational/teleport/api/gen/proto/go/teleport/plugins/v1";

// PluginType represents a single type of hosted plugin
// that can be onboarded.
message PluginType {
// Type is a string corresponding to api.PluginTypeXXX constants
string type = 1;

// OAuthClientID contains the client ID of the OAuth application
// that is used with this plugin's API provider.
// For plugins that are not authenticated via OAuth,
// this will be empty.
string oauth_client_id = 2;
}

// CreatePluginRequest creates a new plugin from the given spec and initial credentials.
message CreatePluginRequest {
// Plugin is the plugin object without live credentials.
Expand Down Expand Up @@ -81,6 +94,16 @@ message SetPluginStatusRequest {
types.PluginStatusV1 status = 2;
}

// GetAvailablePluginTypesRequest is the request type for GetAvailablePluginTypes
message GetAvailablePluginTypesRequest {}

// GetAvailablePluginTypesResponse is a response to for GetAvailablePluginTypes
message GetAvailablePluginTypesResponse {
// PluginTypes is a list of hosted plugins
// that the auth service supports.
repeated PluginType plugin_types = 1;
}

// PluginService provides CRUD operations for Plugin resources.
service PluginService {
// CreatePlugin creates a new plugin instance.
Expand All @@ -100,4 +123,8 @@ service PluginService {

// SetPluginCredentials sets the status for the given plugin.
rpc SetPluginStatus(SetPluginStatusRequest) returns (google.protobuf.Empty);

// GetAvailablePluginTypes returns the types of plugins
// that the auth server supports onboarding.
rpc GetAvailablePluginTypes(GetAvailablePluginTypesRequest) returns (GetAvailablePluginTypesResponse);
}
17 changes: 17 additions & 0 deletions lib/httplib/csrf/csrf.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const (
CookieName = "__Host-grv_csrf"
// HeaderName is the default HTTP request header to inspect.
HeaderName = "X-CSRF-Token"
// FormFieldName is the default form field to inspect.
FormFieldName = "csrf_token"
// tokenLenBytes is CSRF token length in bytes.
tokenLenBytes = 32
// defaultMaxAge is the default MaxAge for cookies.
Expand Down Expand Up @@ -76,6 +78,21 @@ func VerifyHTTPHeader(r *http.Request) error {
return nil
}

// VerifyFormField checks if HTTP form value matches the cookie.
func VerifyFormField(r *http.Request) error {
token := r.FormValue(FormFieldName)
if len(token) == 0 {
return trace.BadParameter("cannot retrieve CSRF token from form field %q", FormFieldName)
}

err := VerifyToken(token, r)
if err != nil {
return trace.Wrap(err)
}

return nil
}

// VerifyToken validates given token based on HTTP request cookie
func VerifyToken(token string, r *http.Request) error {
realToken, err := ExtractTokenFromCookie(r)
Expand Down
17 changes: 10 additions & 7 deletions lib/httplib/httplib.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,18 @@ func MakeStdHandlerWithErrorWriter(fn StdHandlerFunc, errWriter ErrorWriter) htt

// WithCSRFProtection ensures that request to unauthenticated API is checked against CSRF attacks
func WithCSRFProtection(fn HandlerFunc) httprouter.Handle {
hanlderFn := MakeHandler(fn)
handlerFn := MakeHandler(fn)
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
err := csrf.VerifyHTTPHeader(r)
if err != nil {
log.Warningf("unable to validate CSRF token %v", err)
trace.WriteError(w, trace.AccessDenied("access denied"))
return
if r.Method != http.MethodGet && r.Method != http.MethodHead {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slightly unsure about this, but it shouldn't have any security consequences as long as these methods don't change state, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is common practice to exclude "safe" methods for CSRF checks, see e.g. gorilla/csrf's implementation https://github.com/gorilla/csrf/blob/93379db1992f1b2fbdd4ba1b55a1bf0414b0535e/csrf.go#L242-L244 .

OAuth2 flow operates strictly through GET (although it is doing mutable stuff), which is not quite REST-y. However OAuth uses its own state parameter (which we generate/verify, see the Enterprise counterpart) for those requests, which is really more or less just a CSRF token as well. https://stackoverflow.com/questions/26132066/what-is-the-purpose-of-the-state-parameter-in-oauth-authorization-request

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I also thought about the current usages of WithCSRFProtection, but I see it was used only for POST/PUT requests.

Thanks for the detailed explanation :)

errHeader := csrf.VerifyHTTPHeader(r)
errForm := csrf.VerifyFormField(r)
if errForm != nil && errHeader != nil {
log.Warningf("unable to validate CSRF token: %v, %v", errHeader, errForm)
trace.WriteError(w, trace.AccessDenied("access denied"))
return
}
}
hanlderFn(w, r, p)
handlerFn(w, r, p)
}
}

Expand Down
40 changes: 23 additions & 17 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,8 @@ import (
const (
// SSOLoginFailureMessage is a generic error message to avoid disclosing sensitive SSO failure messages.
SSOLoginFailureMessage = "Failed to login. Please check Teleport's log for more details."
metaRedirectHTML = `
<!DOCTYPE html>
<html lang="en">
<head>
<title>Teleport Redirection Service</title>
<meta http-equiv="cache-control" content="no-cache"/>
<meta http-equiv="refresh" content="0;URL='{{.}}'" />
</head>
<body></body>
</html>
`
)

var metaRedirectTemplate = template.Must(template.New("meta-redirect").Parse(metaRedirectHTML))

// healthCheckAppServerFunc defines a function used to perform a health check
// to AppServer that can handle application requests (based on cluster name and
// public address).
Expand Down Expand Up @@ -858,6 +845,11 @@ func (h *Handler) getUserContext(w http.ResponseWriter, r *http.Request, p httpr
return userContext, nil
}

// PublicProxyAddr returns the publicly advertised proxy address
func (h *Handler) PublicProxyAddr() string {
return h.cfg.PublicProxyAddr
}

func localSettings(cap types.AuthPreference) (webclient.AuthenticationSettings, error) {
as := webclient.AuthenticationSettings{
Type: constants.Local,
Expand Down Expand Up @@ -1569,7 +1561,7 @@ func (h *Handler) installer(w http.ResponseWriter, r *http.Request, p httprouter
}

tmpl := installers.Template{
PublicProxyAddr: h.cfg.PublicProxyAddr,
PublicProxyAddr: h.PublicProxyAddr(),
TeleportPackage: teleportPackage,
RepoChannel: repoChannel,
}
Expand Down Expand Up @@ -3577,14 +3569,13 @@ func (h *Handler) WithRedirect(fn redirectHandlerFunc) httprouter.Handle {
// See https://github.com/gravitational/teleport/issues/7467.
func (h *Handler) WithMetaRedirect(fn redirectHandlerFunc) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
app.SetRedirectPageHeaders(w.Header(), "")
redirectURL := fn(w, r, p)
if !isValidRedirectURL(redirectURL) {
redirectURL = client.LoginFailedRedirectURL
}
err := metaRedirectTemplate.Execute(w, redirectURL)
err := app.MetaRedirect(w, redirectURL)
if err != nil {
h.log.WithError(err).Warn("Failed to execute template.")
h.log.WithError(err).Warn("Failed to issue a redirect.")
}
}
}
Expand All @@ -3600,6 +3591,21 @@ func (h *Handler) WithAuth(fn ContextHandler) httprouter.Handle {
})
}

// WithAuthCookieAndCSRF ensures that a request is authenticated
// for plain old non-AJAX requests (does not check the Bearer header).
// It enforces CSRF checks (except for "safe" methods).
func (h *Handler) WithAuthCookieAndCSRF(fn ContextHandler) httprouter.Handle {
f := func(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
sctx, err := h.AuthenticateRequest(w, r, false)
if err != nil {
return nil, trace.Wrap(err)
}
return fn(w, r, p, sctx)
}

return httplib.WithCSRFProtection(f)
}

// WithLimiter adds IP-based rate limiting to fn.
func (h *Handler) WithLimiter(fn httplib.HandlerFunc) httprouter.Handle {
return httplib.MakeHandler(func(w http.ResponseWriter, r *http.Request, p httprouter.Params) (interface{}, error) {
Expand Down
23 changes: 23 additions & 0 deletions lib/web/app/redirect.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,29 @@ package app

import (
"fmt"
"html/template"
"net/http"
"strings"

"github.com/gravitational/trace"

"github.com/gravitational/teleport/lib/httplib"
)

const metaRedirectHTML = `
<!DOCTYPE html>
<html lang="en">
<head>
<title>Teleport Redirection Service</title>
<meta http-equiv="cache-control" content="no-cache"/>
<meta http-equiv="refresh" content="0;URL='{{.}}'" />
</head>
<body></body>
</html>
`

var metaRedirectTemplate = template.Must(template.New("meta-redirect").Parse(metaRedirectHTML))

func SetRedirectPageHeaders(h http.Header, nonce string) {
httplib.SetNoCacheHeaders(h)
httplib.SetDefaultSecurityHeaders(h)
Expand All @@ -42,3 +59,9 @@ func SetRedirectPageHeaders(h http.Header, nonce string) {
}, ";")
h.Set("Content-Security-Policy", csp)
}

// MetaRedirect issues a "meta refresh" redirect.
func MetaRedirect(w http.ResponseWriter, redirectURL string) error {
SetRedirectPageHeaders(w.Header(), "")
return trace.Wrap(metaRedirectTemplate.Execute(w, redirectURL))
}
2 changes: 1 addition & 1 deletion lib/web/connection_diagnostic.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (h *Handler) diagnoseConnection(w http.ResponseWriter, r *http.Request, p h
ResourceKind: req.ResourceKind,
UserClient: userClt,
ProxyHostPort: h.ProxyHostPort(),
PublicProxyAddr: h.cfg.PublicProxyAddr,
PublicProxyAddr: h.PublicProxyAddr(),
KubernetesPublicProxyAddr: h.kubeProxyHostPort(),
TLSRoutingEnabled: proxySettings.TLSRoutingEnabled,
}
Expand Down
1 change: 1 addition & 0 deletions web/packages/design/src/Icon/Icon.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ export const ForwarderAdded = makeFontIcon(
export const Github = makeFontIcon('Github', 'icon-github');
export const Google = makeFontIcon('Google', 'icon-google-plus');
export const Graph = makeFontIcon('Graph', 'icon-graph');
export const Hashtag = makeFontIcon('Hashtag', 'icon-hashtag');
export const Home = makeFontIcon('Home', 'icon-home3');
export const Info = makeFontIcon('Info', 'icon-info_outline');
export const InfoFilled = makeFontIcon('Info', 'icon-info');
Expand Down
1 change: 1 addition & 0 deletions web/packages/design/src/Icon/Icon.story.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const ListOfIcons = () => (
<IconBox IconCmpt={Icon.Github} text="GitHub" />
<IconBox IconCmpt={Icon.Google} text="Google" />
<IconBox IconCmpt={Icon.Graph} text="Graph" />
<IconBox IconCmpt={Icon.Hashtag} text="Hashtag" />
<IconBox IconCmpt={Icon.Home} text="Home" />
<IconBox IconCmpt={Icon.Key} text="Key" />
<IconBox IconCmpt={Icon.Keypair} text="Keypair" />
Expand Down
Loading