Revert "AUTH-3394: Creates a token per app instead of per path"

This reverts commit 8e340d9598.
This commit is contained in:
Adam Chalmers 2021-03-10 13:39:33 -06:00
parent aa5ebb817a
commit b0e69c4b8a
8 changed files with 84 additions and 130 deletions

View File

@ -21,7 +21,6 @@ import (
const LogFieldOriginURL = "originURL" const LogFieldOriginURL = "originURL"
type StartOptions struct { type StartOptions struct {
AppInfo *token.AppInfo
OriginURL string OriginURL string
Headers http.Header Headers http.Header
Host string Host string
@ -124,7 +123,7 @@ func IsAccessResponse(resp *http.Response) bool {
if err != nil || location == nil { if err != nil || location == nil {
return false return false
} }
if strings.HasPrefix(location.Path, token.AccessLoginWorkerPath) { if strings.HasPrefix(location.Path, "/cdn-cgi/access/login") {
return true return true
} }
@ -138,7 +137,7 @@ func BuildAccessRequest(options *StartOptions, log *zerolog.Logger) (*http.Reque
return nil, err return nil, err
} }
token, err := token.FetchTokenWithRedirect(req.URL, options.AppInfo, log) token, err := token.FetchTokenWithRedirect(req.URL, log)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -116,7 +116,11 @@ func createAccessAuthenticatedStream(options *StartOptions, log *zerolog.Logger)
} }
// Access Token is invalid for some reason. Go through regen flow // Access Token is invalid for some reason. Go through regen flow
if err := token.RemoveTokenIfExists(options.AppInfo); err != nil { originReq, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
if err != nil {
return nil, err
}
if err := token.RemoveTokenIfExists(originReq.URL); err != nil {
return nil, err return nil, err
} }
wsConn, resp, err = createAccessWebSocketStream(options, log) wsConn, resp, err = createAccessWebSocketStream(options, log)

View File

@ -10,7 +10,6 @@ import (
"github.com/cloudflare/cloudflared/config" "github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/h2mux" "github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger" "github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/token"
"github.com/cloudflare/cloudflared/validation" "github.com/cloudflare/cloudflared/validation"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -109,17 +108,6 @@ func ssh(c *cli.Context) error {
} }
} }
originReq, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
if err != nil {
return err
}
appInfo, err := token.GetAppInfo(originReq.URL)
if err != nil {
return err
}
options.AppInfo = appInfo
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side // we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
wsConn := carrier.NewWSConnection(log) wsConn := carrier.NewWSConnection(log)

View File

@ -167,9 +167,9 @@ func Commands() []*cli.Command {
Usage: "Application logging level {fatal, error, info, debug}. ", Usage: "Application logging level {fatal, error, info, debug}. ",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: sshConnectTo, Name: sshConnectTo,
Hidden: true, Hidden: true,
Usage: "Connect to alternate location for testing, value is host, host:port, or sni:port:host", Usage: "Connect to alternate location for testing, value is host, host:port, or sni:port:host",
}, },
}, },
}, },
@ -221,18 +221,12 @@ func login(c *cli.Context) error {
log.Error().Msg("Please provide the url of the Access application") log.Error().Msg("Please provide the url of the Access application")
return err return err
} }
if err := verifyTokenAtEdge(appURL, c, log); err != nil {
appInfo, err := token.GetAppInfo(appURL)
if err != nil {
return err
}
if err := verifyTokenAtEdge(appURL, appInfo, c, log); err != nil {
log.Err(err).Msg("Could not verify token") log.Err(err).Msg("Could not verify token")
return err return err
} }
cfdToken, err := token.GetAppTokenIfExists(appInfo) cfdToken, err := token.GetAppTokenIfExists(appURL)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, "Unable to find token for provided application.") fmt.Fprintln(os.Stderr, "Unable to find token for provided application.")
return err return err
@ -274,17 +268,13 @@ func curl(c *cli.Context) error {
return err return err
} }
appInfo, err := token.GetAppInfo(appURL) tok, err := token.GetAppTokenIfExists(appURL)
if err != nil {
return err
}
tok, err := token.GetAppTokenIfExists(appInfo)
if err != nil || tok == "" { if err != nil || tok == "" {
if allowRequest { if allowRequest {
log.Info().Msg("You don't have an Access token set. Please run access token <access application> to fetch one.") log.Info().Msg("You don't have an Access token set. Please run access token <access application> to fetch one.")
return run("curl", cmdArgs...) return run("curl", cmdArgs...)
} }
tok, err = token.FetchToken(appURL, appInfo, log) tok, err = token.FetchToken(appURL, log)
if err != nil { if err != nil {
log.Err(err).Msg("Failed to refresh token") log.Err(err).Msg("Failed to refresh token")
return err return err
@ -328,12 +318,7 @@ func generateToken(c *cli.Context) error {
fmt.Fprintln(os.Stderr, "Please provide a url.") fmt.Fprintln(os.Stderr, "Please provide a url.")
return err return err
} }
tok, err := token.GetAppTokenIfExists(appURL)
appInfo, err := token.GetAppInfo(appURL)
if err != nil {
return err
}
tok, err := token.GetAppTokenIfExists(appInfo)
if err != nil || tok == "" { if err != nil || tok == "" {
fmt.Fprintln(os.Stderr, "Unable to find token for provided application. Please run login command to generate token.") fmt.Fprintln(os.Stderr, "Unable to find token for provided application. Please run login command to generate token.")
return err return err
@ -384,24 +369,19 @@ func sshGen(c *cli.Context) error {
// this fetchToken function mutates the appURL param. We should refactor that // this fetchToken function mutates the appURL param. We should refactor that
fetchTokenURL := &url.URL{} fetchTokenURL := &url.URL{}
*fetchTokenURL = *originURL *fetchTokenURL = *originURL
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, log)
appInfo, err := token.GetAppInfo(fetchTokenURL)
if err != nil {
return err
}
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, appInfo, log)
if err != nil { if err != nil {
return err return err
} }
if err := sshgen.GenerateShortLivedCertificate(appInfo, cfdToken); err != nil { if err := sshgen.GenerateShortLivedCertificate(originURL, cfdToken); err != nil {
return err return err
} }
return nil return nil
} }
// getAppURL will pull the request URL needed for fetching a user's Access token // getAppURL will pull the appURL needed for fetching a user's Access token
func getAppURL(cmdArgs []string, log *zerolog.Logger) (*url.URL, error) { func getAppURL(cmdArgs []string, log *zerolog.Logger) (*url.URL, error) {
if len(cmdArgs) < 1 { if len(cmdArgs) < 1 {
log.Error().Msg("Please provide a valid URL as the first argument to curl.") log.Error().Msg("Please provide a valid URL as the first argument to curl.")
@ -476,7 +456,7 @@ func isFileThere(candidate string) bool {
// verifyTokenAtEdge checks for a token on disk, or generates a new one. // verifyTokenAtEdge checks for a token on disk, or generates a new one.
// Then makes a request to to the origin with the token to ensure it is valid. // Then makes a request to to the origin with the token to ensure it is valid.
// Returns nil if token is valid. // Returns nil if token is valid.
func verifyTokenAtEdge(appUrl *url.URL, appInfo *token.AppInfo, c *cli.Context, log *zerolog.Logger) error { func verifyTokenAtEdge(appUrl *url.URL, c *cli.Context, log *zerolog.Logger) error {
headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag)) headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag))
if c.IsSet(sshTokenIDFlag) { if c.IsSet(sshTokenIDFlag) {
headers.Add(h2mux.CFAccessClientIDHeader, c.String(sshTokenIDFlag)) headers.Add(h2mux.CFAccessClientIDHeader, c.String(sshTokenIDFlag))
@ -484,7 +464,7 @@ func verifyTokenAtEdge(appUrl *url.URL, appInfo *token.AppInfo, c *cli.Context,
if c.IsSet(sshTokenSecretFlag) { if c.IsSet(sshTokenSecretFlag) {
headers.Add(h2mux.CFAccessClientSecretHeader, c.String(sshTokenSecretFlag)) headers.Add(h2mux.CFAccessClientSecretHeader, c.String(sshTokenSecretFlag))
} }
options := &carrier.StartOptions{AppInfo: appInfo, OriginURL: appUrl.String(), Headers: headers} options := &carrier.StartOptions{OriginURL: appUrl.String(), Headers: headers}
if valid, err := isTokenValid(options, log); err != nil { if valid, err := isTokenValid(options, log); err != nil {
return err return err
@ -492,7 +472,7 @@ func verifyTokenAtEdge(appUrl *url.URL, appInfo *token.AppInfo, c *cli.Context,
return nil return nil
} }
if err := token.RemoveTokenIfExists(appInfo); err != nil { if err := token.RemoveTokenIfExists(appUrl); err != nil {
return err return err
} }

View File

@ -12,6 +12,7 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url"
"time" "time"
"github.com/coreos/go-oidc/jose" "github.com/coreos/go-oidc/jose"
@ -51,8 +52,8 @@ type errorResponse struct {
var mockRequest func(url, contentType string, body io.Reader) (*http.Response, error) = nil var mockRequest func(url, contentType string, body io.Reader) (*http.Response, error) = nil
// GenerateShortLivedCertificate generates and stores a keypair for short lived certs // GenerateShortLivedCertificate generates and stores a keypair for short lived certs
func GenerateShortLivedCertificate(appInfo *cfpath.AppInfo, token string) error { func GenerateShortLivedCertificate(appURL *url.URL, token string) error {
fullName, err := cfpath.GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName) fullName, err := cfpath.GenerateAppTokenFilePathFromURL(appURL, keyName)
if err != nil { if err != nil {
return err return err
} }

View File

@ -9,6 +9,7 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url"
"os" "os"
"testing" "testing"
"time" "time"
@ -32,10 +33,10 @@ type signingArguments struct {
} }
func TestCertGenSuccess(t *testing.T) { func TestCertGenSuccess(t *testing.T) {
appInfo := &cfpath.AppInfo{AppAUD: "abcd1234", AppDomain: "mySite.com"} url, _ := url.Parse("https://cf-test-access.com/testpath")
token := tokenGenerator() token := tokenGenerator()
fullName, err := cfpath.GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName) fullName, err := cfpath.GenerateAppTokenFilePathFromURL(url, keyName)
assert.NoError(t, err) assert.NoError(t, err)
pubKeyName := fullName + ".pub" pubKeyName := fullName + ".pub"
@ -65,7 +66,7 @@ func TestCertGenSuccess(t *testing.T) {
return w.Result(), nil return w.Result(), nil
} }
err = GenerateShortLivedCertificate(appInfo, token) err = GenerateShortLivedCertificate(url, token)
assert.NoError(t, err) assert.NoError(t, err)
exist, err := config.FileExists(fullName) exist, err := config.FileExists(fullName)

View File

@ -2,6 +2,7 @@ package token
import ( import (
"fmt" "fmt"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -12,13 +13,12 @@ import (
) )
// GenerateAppTokenFilePathFromURL will return a filepath for given Access org token // GenerateAppTokenFilePathFromURL will return a filepath for given Access org token
func GenerateAppTokenFilePathFromURL(appDomain, aud string, suffix string) (string, error) { func GenerateAppTokenFilePathFromURL(url *url.URL, suffix string) (string, error) {
configPath, err := getConfigPath() configPath, err := getConfigPath()
if err != nil { if err != nil {
return "", err return "", err
} }
name := fmt.Sprintf("%s-%s-%s", appDomain, aud, suffix) name := strings.Replace(fmt.Sprintf("%s%s-%s", url.Hostname(), url.EscapedPath(), suffix), "/", "-", -1)
name = strings.Replace(strings.Replace(name, "/", "-", -1), "*", "-", -1)
return filepath.Join(configPath, name), nil return filepath.Join(configPath, name), nil
} }

View File

@ -22,18 +22,10 @@ import (
) )
const ( const (
keyName = "token" keyName = "token"
tokenCookie = "CF_Authorization" tokenHeader = "CF_Authorization"
appDomainHeader = "CF-Access-Domain"
AccessLoginWorkerPath = "/cdn-cgi/access/login"
) )
type AppInfo struct {
AuthDomain string
AppAUD string
AppDomain string
}
type lock struct { type lock struct {
lockFilePath string lockFilePath string
backoff *origin.BackoffHandler backoff *origin.BackoffHandler
@ -150,23 +142,23 @@ func isTokenLocked(lockFilePath string) bool {
// FetchTokenWithRedirect will either load a stored token or generate a new one // FetchTokenWithRedirect will either load a stored token or generate a new one
// it appends the full url as the redirect URL to the access cli request if opening the browser // it appends the full url as the redirect URL to the access cli request if opening the browser
func FetchTokenWithRedirect(appURL *url.URL, appInfo *AppInfo, log *zerolog.Logger) (string, error) { func FetchTokenWithRedirect(appURL *url.URL, log *zerolog.Logger) (string, error) {
return getToken(appURL, appInfo, false, log) return getToken(appURL, false, log)
} }
// FetchToken will either load a stored token or generate a new one // FetchToken will either load a stored token or generate a new one
// it appends the host of the appURL as the redirect URL to the access cli request if opening the browser // it appends the host of the appURL as the redirect URL to the access cli request if opening the browser
func FetchToken(appURL *url.URL, appInfo *AppInfo, log *zerolog.Logger) (string, error) { func FetchToken(appURL *url.URL, log *zerolog.Logger) (string, error) {
return getToken(appURL, appInfo, true, log) return getToken(appURL, true, log)
} }
// getToken will either load a stored token or generate a new one // getToken will either load a stored token or generate a new one
func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, log *zerolog.Logger) (string, error) { func getToken(appURL *url.URL, useHostOnly bool, log *zerolog.Logger) (string, error) {
if token, err := GetAppTokenIfExists(appInfo); token != "" && err == nil { if token, err := GetAppTokenIfExists(appURL); token != "" && err == nil {
return token, nil return token, nil
} }
appTokenPath, err := GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName) appTokenPath, err := GenerateAppTokenFilePathFromURL(appURL, keyName)
if err != nil { if err != nil {
return "", errors.Wrap(err, "failed to generate app token file path") return "", errors.Wrap(err, "failed to generate app token file path")
} }
@ -178,36 +170,40 @@ func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, log *zerolog.
defer fileLockAppToken.Release() defer fileLockAppToken.Release()
// check to see if another process has gotten a token while we waited for the lock // check to see if another process has gotten a token while we waited for the lock
if token, err := GetAppTokenIfExists(appInfo); token != "" && err == nil { if token, err := GetAppTokenIfExists(appURL); token != "" && err == nil {
return token, nil return token, nil
} }
// If an app token couldnt be found on disk, check for an org token and attempt to exchange it for an app token. // If an app token couldnt be found on disk, check for an org token and attempt to exchange it for an app token.
var orgTokenPath string var orgTokenPath string
orgToken, err := GetOrgTokenIfExists(appInfo.AuthDomain) // Get auth domain to format into org token file path
if err != nil { if authDomain, err := getAuthDomain(appURL); err != nil {
orgTokenPath, err = generateOrgTokenFilePathFromURL(appInfo.AuthDomain) log.Error().Msgf("failed to get auth domain: %s", err)
} else {
orgToken, err := GetOrgTokenIfExists(authDomain)
if err != nil { if err != nil {
return "", errors.Wrap(err, "failed to generate org token file path") orgTokenPath, err = generateOrgTokenFilePathFromURL(authDomain)
} if err != nil {
return "", errors.Wrap(err, "failed to generate org token file path")
fileLockOrgToken := newLock(orgTokenPath) }
if err = fileLockOrgToken.Acquire(); err != nil {
return "", errors.Wrap(err, "failed to acquire org token lock") fileLockOrgToken := newLock(orgTokenPath)
} if err = fileLockOrgToken.Acquire(); err != nil {
defer fileLockOrgToken.Release() return "", errors.Wrap(err, "failed to acquire org token lock")
// check if an org token has been created since the lock was acquired }
orgToken, err = GetOrgTokenIfExists(appInfo.AuthDomain) defer fileLockOrgToken.Release()
} // check if an org token has been created since the lock was acquired
if err == nil { orgToken, err = GetOrgTokenIfExists(authDomain)
if appToken, err := exchangeOrgToken(appURL, orgToken); err != nil { }
log.Debug().Msgf("failed to exchange org token for app token: %s", err) if err == nil {
} else { if appToken, err := exchangeOrgToken(appURL, orgToken); err != nil {
// generate app path log.Debug().Msgf("failed to exchange org token for app token: %s", err)
if err := ioutil.WriteFile(appTokenPath, []byte(appToken), 0600); err != nil { } else {
return "", errors.Wrap(err, "failed to write app token to disk") if err := ioutil.WriteFile(appTokenPath, []byte(appToken), 0600); err != nil {
return "", errors.Wrap(err, "failed to write app token to disk")
}
return appToken, nil
} }
return appToken, nil
} }
} }
return getTokensFromEdge(appURL, appTokenPath, orgTokenPath, useHostOnly, log) return getTokensFromEdge(appURL, appTokenPath, orgTokenPath, useHostOnly, log)
@ -246,46 +242,31 @@ func getTokensFromEdge(appURL *url.URL, appTokenPath, orgTokenPath string, useHo
} }
// GetAppInfo makes a request to the appURL and stops at the first redirect. The 302 location header will contain the // getAuthDomain makes a request to the appURL and stops at the first redirect. The 302 location header will contain the
// auth domain // auth domain
func GetAppInfo(reqURL *url.URL) (*AppInfo, error) { func getAuthDomain(appURL *url.URL) (string, error) {
client := &http.Client{ client := &http.Client{
// do not follow redirects // do not follow redirects
CheckRedirect: func(req *http.Request, via []*http.Request) error { CheckRedirect: func(req *http.Request, via []*http.Request) error {
// stop after hitting login endpoint since it will contain app path return http.ErrUseLastResponse
if strings.Contains(via[len(via)-1].URL.Path, AccessLoginWorkerPath) {
return http.ErrUseLastResponse
}
return nil
}, },
Timeout: time.Second * 7, Timeout: time.Second * 7,
} }
appInfoReq, err := http.NewRequest("HEAD", reqURL.String(), nil) authDomainReq, err := http.NewRequest("HEAD", appURL.String(), nil)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to create app info request") return "", errors.Wrap(err, "failed to create auth domain request")
} }
resp, err := client.Do(appInfoReq) resp, err := client.Do(authDomainReq)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to get app info") return "", errors.Wrap(err, "failed to get auth domain")
} }
resp.Body.Close() resp.Body.Close()
location := resp.Request.URL location, err := resp.Location()
if !strings.Contains(location.Path, AccessLoginWorkerPath) { if err != nil {
return nil, fmt.Errorf("failed to get Access app info for %s", reqURL.String()) return "", fmt.Errorf("failed to get auth domain. Received status code %d from %s", resp.StatusCode, appURL.String())
} }
return location.Hostname(), nil
aud := resp.Request.URL.Query().Get("kid")
if aud == "" {
return nil, errors.New("Empty app aud")
}
domain := resp.Header.Get(appDomainHeader)
if domain == "" {
return nil, errors.New("Empty app domain")
}
return &AppInfo{location.Hostname(), aud, domain}, nil
} }
@ -295,8 +276,8 @@ func exchangeOrgToken(appURL *url.URL, orgToken string) (string, error) {
client := &http.Client{ client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error { CheckRedirect: func(req *http.Request, via []*http.Request) error {
// attach org token to login request // attach org token to login request
if strings.Contains(req.URL.Path, AccessLoginWorkerPath) { if strings.Contains(req.URL.Path, "cdn-cgi/access/login") {
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: orgToken}) req.AddCookie(&http.Cookie{Name: tokenHeader, Value: orgToken})
} }
// stop after hitting authorized endpoint since it will contain the app token // stop after hitting authorized endpoint since it will contain the app token
if strings.Contains(via[len(via)-1].URL.Path, "cdn-cgi/access/authorized") { if strings.Contains(via[len(via)-1].URL.Path, "cdn-cgi/access/authorized") {
@ -319,7 +300,7 @@ func exchangeOrgToken(appURL *url.URL, orgToken string) (string, error) {
var appToken string var appToken string
for _, c := range resp.Cookies() { for _, c := range resp.Cookies() {
//if Org token revoked on exchange, getTokensFromEdge instead //if Org token revoked on exchange, getTokensFromEdge instead
validAppToken := c.Name == tokenCookie && time.Now().Before(c.Expires) validAppToken := c.Name == tokenHeader && time.Now().Before(c.Expires)
if validAppToken { if validAppToken {
appToken = c.Value appToken = c.Value
break break
@ -354,8 +335,8 @@ func GetOrgTokenIfExists(authDomain string) (string, error) {
return token.Encode(), nil return token.Encode(), nil
} }
func GetAppTokenIfExists(appInfo *AppInfo) (string, error) { func GetAppTokenIfExists(url *url.URL) (string, error) {
path, err := GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName) path, err := GenerateAppTokenFilePathFromURL(url, keyName)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -392,8 +373,8 @@ func getTokenIfExists(path string) (*jose.JWT, error) {
} }
// RemoveTokenIfExists removes the a token from local storage if it exists // RemoveTokenIfExists removes the a token from local storage if it exists
func RemoveTokenIfExists(appInfo *AppInfo) error { func RemoveTokenIfExists(url *url.URL) error {
path, err := GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName) path, err := GenerateAppTokenFilePathFromURL(url, keyName)
if err != nil { if err != nil {
return err return err
} }