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

View File

@ -10,7 +10,6 @@ import (
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/token"
"github.com/cloudflare/cloudflared/validation"
"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
wsConn := carrier.NewWSConnection(log)

View File

@ -221,18 +221,12 @@ func login(c *cli.Context) error {
log.Error().Msg("Please provide the url of the Access application")
return err
}
appInfo, err := token.GetAppInfo(appURL)
if err != nil {
return err
}
if err := verifyTokenAtEdge(appURL, appInfo, c, log); err != nil {
if err := verifyTokenAtEdge(appURL, c, log); err != nil {
log.Err(err).Msg("Could not verify token")
return err
}
cfdToken, err := token.GetAppTokenIfExists(appInfo)
cfdToken, err := token.GetAppTokenIfExists(appURL)
if err != nil {
fmt.Fprintln(os.Stderr, "Unable to find token for provided application.")
return err
@ -274,17 +268,13 @@ func curl(c *cli.Context) error {
return err
}
appInfo, err := token.GetAppInfo(appURL)
if err != nil {
return err
}
tok, err := token.GetAppTokenIfExists(appInfo)
tok, err := token.GetAppTokenIfExists(appURL)
if err != nil || tok == "" {
if allowRequest {
log.Info().Msg("You don't have an Access token set. Please run access token <access application> to fetch one.")
return run("curl", cmdArgs...)
}
tok, err = token.FetchToken(appURL, appInfo, log)
tok, err = token.FetchToken(appURL, log)
if err != nil {
log.Err(err).Msg("Failed to refresh token")
return err
@ -328,12 +318,7 @@ func generateToken(c *cli.Context) error {
fmt.Fprintln(os.Stderr, "Please provide a url.")
return err
}
appInfo, err := token.GetAppInfo(appURL)
if err != nil {
return err
}
tok, err := token.GetAppTokenIfExists(appInfo)
tok, err := token.GetAppTokenIfExists(appURL)
if err != nil || tok == "" {
fmt.Fprintln(os.Stderr, "Unable to find token for provided application. Please run login command to generate token.")
return err
@ -384,24 +369,19 @@ func sshGen(c *cli.Context) error {
// this fetchToken function mutates the appURL param. We should refactor that
fetchTokenURL := &url.URL{}
*fetchTokenURL = *originURL
appInfo, err := token.GetAppInfo(fetchTokenURL)
if err != nil {
return err
}
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, appInfo, log)
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, log)
if err != nil {
return err
}
if err := sshgen.GenerateShortLivedCertificate(appInfo, cfdToken); err != nil {
if err := sshgen.GenerateShortLivedCertificate(originURL, cfdToken); err != nil {
return err
}
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) {
if len(cmdArgs) < 1 {
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.
// Then makes a request to to the origin with the token to ensure it 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))
if c.IsSet(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) {
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 {
return err
@ -492,7 +472,7 @@ func verifyTokenAtEdge(appUrl *url.URL, appInfo *token.AppInfo, c *cli.Context,
return nil
}
if err := token.RemoveTokenIfExists(appInfo); err != nil {
if err := token.RemoveTokenIfExists(appUrl); err != nil {
return err
}

View File

@ -12,6 +12,7 @@ import (
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
"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
// GenerateShortLivedCertificate generates and stores a keypair for short lived certs
func GenerateShortLivedCertificate(appInfo *cfpath.AppInfo, token string) error {
fullName, err := cfpath.GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName)
func GenerateShortLivedCertificate(appURL *url.URL, token string) error {
fullName, err := cfpath.GenerateAppTokenFilePathFromURL(appURL, keyName)
if err != nil {
return err
}

View File

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

View File

@ -2,6 +2,7 @@ package token
import (
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
@ -12,13 +13,12 @@ import (
)
// 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()
if err != nil {
return "", err
}
name := fmt.Sprintf("%s-%s-%s", appDomain, aud, suffix)
name = strings.Replace(strings.Replace(name, "/", "-", -1), "*", "-", -1)
name := strings.Replace(fmt.Sprintf("%s%s-%s", url.Hostname(), url.EscapedPath(), suffix), "/", "-", -1)
return filepath.Join(configPath, name), nil
}

View File

@ -23,17 +23,9 @@ import (
const (
keyName = "token"
tokenCookie = "CF_Authorization"
appDomainHeader = "CF-Access-Domain"
AccessLoginWorkerPath = "/cdn-cgi/access/login"
tokenHeader = "CF_Authorization"
)
type AppInfo struct {
AuthDomain string
AppAUD string
AppDomain string
}
type lock struct {
lockFilePath string
backoff *origin.BackoffHandler
@ -150,23 +142,23 @@ func isTokenLocked(lockFilePath string) bool {
// 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
func FetchTokenWithRedirect(appURL *url.URL, appInfo *AppInfo, log *zerolog.Logger) (string, error) {
return getToken(appURL, appInfo, false, log)
func FetchTokenWithRedirect(appURL *url.URL, log *zerolog.Logger) (string, error) {
return getToken(appURL, false, log)
}
// 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
func FetchToken(appURL *url.URL, appInfo *AppInfo, log *zerolog.Logger) (string, error) {
return getToken(appURL, appInfo, true, log)
func FetchToken(appURL *url.URL, log *zerolog.Logger) (string, error) {
return getToken(appURL, true, log)
}
// 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) {
if token, err := GetAppTokenIfExists(appInfo); token != "" && err == nil {
func getToken(appURL *url.URL, useHostOnly bool, log *zerolog.Logger) (string, error) {
if token, err := GetAppTokenIfExists(appURL); token != "" && err == nil {
return token, nil
}
appTokenPath, err := GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName)
appTokenPath, err := GenerateAppTokenFilePathFromURL(appURL, keyName)
if err != nil {
return "", errors.Wrap(err, "failed to generate app token file path")
}
@ -178,15 +170,19 @@ func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, log *zerolog.
defer fileLockAppToken.Release()
// 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
}
// 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
orgToken, err := GetOrgTokenIfExists(appInfo.AuthDomain)
// Get auth domain to format into org token file path
if authDomain, err := getAuthDomain(appURL); err != nil {
log.Error().Msgf("failed to get auth domain: %s", err)
} else {
orgToken, err := GetOrgTokenIfExists(authDomain)
if err != nil {
orgTokenPath, err = generateOrgTokenFilePathFromURL(appInfo.AuthDomain)
orgTokenPath, err = generateOrgTokenFilePathFromURL(authDomain)
if err != nil {
return "", errors.Wrap(err, "failed to generate org token file path")
}
@ -197,19 +193,19 @@ func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, log *zerolog.
}
defer fileLockOrgToken.Release()
// check if an org token has been created since the lock was acquired
orgToken, err = GetOrgTokenIfExists(appInfo.AuthDomain)
orgToken, err = GetOrgTokenIfExists(authDomain)
}
if err == nil {
if appToken, err := exchangeOrgToken(appURL, orgToken); err != nil {
log.Debug().Msgf("failed to exchange org token for app token: %s", err)
} else {
// generate app path
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 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
func GetAppInfo(reqURL *url.URL) (*AppInfo, error) {
func getAuthDomain(appURL *url.URL) (string, error) {
client := &http.Client{
// do not follow redirects
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// stop after hitting login endpoint since it will contain app path
if strings.Contains(via[len(via)-1].URL.Path, AccessLoginWorkerPath) {
return http.ErrUseLastResponse
}
return nil
},
Timeout: time.Second * 7,
}
appInfoReq, err := http.NewRequest("HEAD", reqURL.String(), nil)
authDomainReq, err := http.NewRequest("HEAD", appURL.String(), 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 {
return nil, errors.Wrap(err, "failed to get app info")
return "", errors.Wrap(err, "failed to get auth domain")
}
resp.Body.Close()
location := resp.Request.URL
if !strings.Contains(location.Path, AccessLoginWorkerPath) {
return nil, fmt.Errorf("failed to get Access app info for %s", reqURL.String())
location, err := resp.Location()
if err != nil {
return "", fmt.Errorf("failed to get auth domain. Received status code %d from %s", resp.StatusCode, appURL.String())
}
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
return location.Hostname(), nil
}
@ -295,8 +276,8 @@ func exchangeOrgToken(appURL *url.URL, orgToken string) (string, error) {
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// attach org token to login request
if strings.Contains(req.URL.Path, AccessLoginWorkerPath) {
req.AddCookie(&http.Cookie{Name: tokenCookie, Value: orgToken})
if strings.Contains(req.URL.Path, "cdn-cgi/access/login") {
req.AddCookie(&http.Cookie{Name: tokenHeader, Value: orgToken})
}
// 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") {
@ -319,7 +300,7 @@ func exchangeOrgToken(appURL *url.URL, orgToken string) (string, error) {
var appToken string
for _, c := range resp.Cookies() {
//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 {
appToken = c.Value
break
@ -354,8 +335,8 @@ func GetOrgTokenIfExists(authDomain string) (string, error) {
return token.Encode(), nil
}
func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
path, err := GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName)
func GetAppTokenIfExists(url *url.URL) (string, error) {
path, err := GenerateAppTokenFilePathFromURL(url, keyName)
if err != nil {
return "", err
}
@ -392,8 +373,8 @@ func getTokenIfExists(path string) (*jose.JWT, error) {
}
// RemoveTokenIfExists removes the a token from local storage if it exists
func RemoveTokenIfExists(appInfo *AppInfo) error {
path, err := GenerateAppTokenFilePathFromURL(appInfo.AppDomain, appInfo.AppAUD, keyName)
func RemoveTokenIfExists(url *url.URL) error {
path, err := GenerateAppTokenFilePathFromURL(url, keyName)
if err != nil {
return err
}