2018-10-19 20:44:35 +00:00
|
|
|
package token
|
2018-10-08 19:20:28 +00:00
|
|
|
|
|
|
|
import (
|
2019-06-26 15:48:45 +00:00
|
|
|
"context"
|
2020-02-24 02:48:06 +00:00
|
|
|
"encoding/json"
|
2019-06-26 15:48:45 +00:00
|
|
|
"fmt"
|
2018-10-08 19:20:28 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"net/url"
|
2019-06-26 15:48:45 +00:00
|
|
|
"os"
|
2019-07-31 20:24:57 +00:00
|
|
|
"os/signal"
|
|
|
|
"syscall"
|
2020-02-24 02:48:06 +00:00
|
|
|
"time"
|
2018-10-08 19:20:28 +00:00
|
|
|
|
2019-06-26 15:48:45 +00:00
|
|
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
2019-01-23 21:42:10 +00:00
|
|
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/path"
|
2018-10-08 19:20:28 +00:00
|
|
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/transfer"
|
2020-04-29 20:51:32 +00:00
|
|
|
"github.com/cloudflare/cloudflared/logger"
|
2019-06-26 15:48:45 +00:00
|
|
|
"github.com/cloudflare/cloudflared/origin"
|
2019-08-26 21:45:49 +00:00
|
|
|
"github.com/coreos/go-oidc/jose"
|
2019-01-23 21:42:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
keyName = "token"
|
2018-10-08 19:20:28 +00:00
|
|
|
)
|
|
|
|
|
2019-06-26 15:48:45 +00:00
|
|
|
type lock struct {
|
|
|
|
lockFilePath string
|
|
|
|
backoff *origin.BackoffHandler
|
2019-11-12 18:50:41 +00:00
|
|
|
sigHandler *signalHandler
|
2019-07-31 20:24:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type signalHandler struct {
|
2019-11-12 18:50:41 +00:00
|
|
|
sigChannel chan os.Signal
|
|
|
|
signals []os.Signal
|
2019-07-31 20:24:57 +00:00
|
|
|
}
|
|
|
|
|
2020-02-24 02:48:06 +00:00
|
|
|
type jwtPayload struct {
|
|
|
|
Aud []string `json:"aud"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
Exp int `json:"exp"`
|
|
|
|
Iat int `json:"iat"`
|
|
|
|
Nbf int `json:"nbf"`
|
|
|
|
Iss string `json:"iss"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
Subt string `json:"sub"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p jwtPayload) isExpired() bool {
|
|
|
|
return int(time.Now().Unix()) > p.Exp
|
|
|
|
}
|
|
|
|
|
2019-11-12 18:50:41 +00:00
|
|
|
func (s *signalHandler) register(handler func()) {
|
2019-07-31 20:24:57 +00:00
|
|
|
s.sigChannel = make(chan os.Signal, 1)
|
|
|
|
signal.Notify(s.sigChannel, s.signals...)
|
|
|
|
go func(s *signalHandler) {
|
|
|
|
for range s.sigChannel {
|
|
|
|
handler()
|
|
|
|
}
|
|
|
|
}(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *signalHandler) deregister() {
|
|
|
|
signal.Stop(s.sigChannel)
|
|
|
|
close(s.sigChannel)
|
2019-06-26 15:48:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func errDeleteTokenFailed(lockFilePath string) error {
|
|
|
|
return fmt.Errorf("failed to acquire a new Access token. Please try to delete %s", lockFilePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
// newLock will get a new file lock
|
|
|
|
func newLock(path string) *lock {
|
|
|
|
lockPath := path + ".lock"
|
|
|
|
return &lock{
|
|
|
|
lockFilePath: lockPath,
|
|
|
|
backoff: &origin.BackoffHandler{MaxRetries: 7},
|
2019-11-12 18:50:41 +00:00
|
|
|
sigHandler: &signalHandler{
|
|
|
|
signals: []os.Signal{syscall.SIGINT, syscall.SIGTERM},
|
2019-07-31 20:24:57 +00:00
|
|
|
},
|
2019-06-26 15:48:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *lock) Acquire() error {
|
2019-07-31 20:24:57 +00:00
|
|
|
// Intercept SIGINT and SIGTERM to release lock before exiting
|
|
|
|
l.sigHandler.register(func() {
|
2019-11-12 18:50:41 +00:00
|
|
|
l.deleteLockFile()
|
|
|
|
os.Exit(0)
|
2019-07-31 20:24:57 +00:00
|
|
|
})
|
|
|
|
|
2019-06-26 15:48:45 +00:00
|
|
|
// Check for a path.lock file
|
|
|
|
// if the lock file exists; start polling
|
|
|
|
// if not, create the lock file and go through the normal flow.
|
|
|
|
// See AUTH-1736 for the reason why we do all this
|
|
|
|
for isTokenLocked(l.lockFilePath) {
|
|
|
|
if l.backoff.Backoff(context.Background()) {
|
|
|
|
continue
|
2019-07-31 20:24:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := l.deleteLockFile(); err != nil {
|
|
|
|
return err
|
2019-06-26 15:48:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a lock file so other processes won't also try to get the token at
|
|
|
|
// the same time
|
|
|
|
if err := ioutil.WriteFile(l.lockFilePath, []byte{}, 0600); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-31 20:24:57 +00:00
|
|
|
func (l *lock) deleteLockFile() error {
|
2019-06-26 15:48:45 +00:00
|
|
|
if err := os.Remove(l.lockFilePath); err != nil && !os.IsNotExist(err) {
|
|
|
|
return errDeleteTokenFailed(l.lockFilePath)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-07-31 20:24:57 +00:00
|
|
|
func (l *lock) Release() error {
|
|
|
|
defer l.sigHandler.deregister()
|
|
|
|
return l.deleteLockFile()
|
|
|
|
}
|
|
|
|
|
2019-06-26 15:48:45 +00:00
|
|
|
// isTokenLocked checks to see if there is another process attempting to get the token already
|
|
|
|
func isTokenLocked(lockFilePath string) bool {
|
|
|
|
exists, err := config.FileExists(lockFilePath)
|
|
|
|
return exists && err == nil
|
|
|
|
}
|
|
|
|
|
2020-06-11 17:02:34 +00:00
|
|
|
// FetchTokenWithRedirect will either load a stored token or generate a new one
|
2020-07-17 16:08:05 +00:00
|
|
|
// it appends the full url as the redirect URL to the access cli request if opening the browser
|
2020-06-11 17:02:34 +00:00
|
|
|
func FetchTokenWithRedirect(appURL *url.URL, logger logger.Service) (string, error) {
|
2020-07-17 16:08:05 +00:00
|
|
|
return getToken(appURL, false, logger)
|
2020-06-11 17:02:34 +00:00
|
|
|
}
|
|
|
|
|
2018-09-21 15:18:23 +00:00
|
|
|
// FetchToken will either load a stored token or generate a new one
|
2020-07-17 16:08:05 +00:00
|
|
|
// it appends the host of the appURL as the redirect URL to the access cli request if opening the browser
|
2020-04-29 20:51:32 +00:00
|
|
|
func FetchToken(appURL *url.URL, logger logger.Service) (string, error) {
|
2020-07-17 16:08:05 +00:00
|
|
|
return getToken(appURL, true, logger)
|
2020-06-11 17:02:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// getToken will either load a stored token or generate a new one
|
2020-07-17 16:08:05 +00:00
|
|
|
func getToken(appURL *url.URL, useHostOnly bool, logger logger.Service) (string, error) {
|
2018-10-19 20:44:35 +00:00
|
|
|
if token, err := GetTokenIfExists(appURL); token != "" && err == nil {
|
2018-10-08 19:20:28 +00:00
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
2019-01-23 21:42:10 +00:00
|
|
|
path, err := path.GenerateFilePathFromURL(appURL, keyName)
|
2018-10-08 19:20:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2019-07-31 20:24:57 +00:00
|
|
|
fileLock := newLock(path)
|
|
|
|
|
|
|
|
err = fileLock.Acquire()
|
2019-06-26 15:48:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2019-07-31 20:24:57 +00:00
|
|
|
defer fileLock.Release()
|
2019-06-26 15:48:45 +00:00
|
|
|
|
|
|
|
// check to see if another process has gotten a token while we waited for the lock
|
|
|
|
if token, err := GetTokenIfExists(appURL); token != "" && err == nil {
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
2018-10-08 19:20:28 +00:00
|
|
|
// this weird parameter is the resource name (token) and the key/value
|
|
|
|
// we want to send to the transfer service. the key is token and the value
|
|
|
|
// is blank (basically just the id generated in the transfer service)
|
2020-07-17 16:08:05 +00:00
|
|
|
token, err := transfer.Run(appURL, keyName, keyName, "", path, true, useHostOnly, logger)
|
2018-10-08 19:20:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(token), nil
|
|
|
|
}
|
|
|
|
|
2020-02-24 02:48:06 +00:00
|
|
|
// GetTokenIfExists will return the token from local storage if it exists and not expired
|
2018-10-19 20:44:35 +00:00
|
|
|
func GetTokenIfExists(url *url.URL) (string, error) {
|
2019-01-23 21:42:10 +00:00
|
|
|
path, err := path.GenerateFilePathFromURL(url, keyName)
|
2018-10-08 19:20:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
content, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
token, err := jose.ParseJWT(string(content))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2020-02-24 02:48:06 +00:00
|
|
|
var payload jwtPayload
|
|
|
|
err = json.Unmarshal(token.Payload, &payload)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if payload.isExpired() {
|
|
|
|
err := os.Remove(path)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2019-06-26 15:48:45 +00:00
|
|
|
return token.Encode(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveTokenIfExists removes the a token from local storage if it exists
|
|
|
|
func RemoveTokenIfExists(url *url.URL) error {
|
|
|
|
path, err := path.GenerateFilePathFromURL(url, keyName)
|
2018-10-08 19:20:28 +00:00
|
|
|
if err != nil {
|
2019-06-26 15:48:45 +00:00
|
|
|
return err
|
2018-10-08 19:20:28 +00:00
|
|
|
}
|
2019-06-26 15:48:45 +00:00
|
|
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
2018-10-08 19:20:28 +00:00
|
|
|
}
|
2019-06-26 15:48:45 +00:00
|
|
|
|
|
|
|
return nil
|
2018-10-08 19:20:28 +00:00
|
|
|
}
|