mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-08-09 17:59:41 +00:00
AUTH-910, AUTH-1049, AUTH-1068, AUTH-1056: Generate and store Access tokens with E2EE option, curl/cmd wrapper
This commit is contained in:

committed by
Areg Harutyunyan

parent
671483a95c
commit
4f04f35bd1
162
cmd/cloudflared/access/cmd.go
Normal file
162
cmd/cloudflared/access/cmd.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
|
||||
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
raven "github.com/getsentry/raven-go"
|
||||
cli "gopkg.in/urfave/cli.v2"
|
||||
)
|
||||
|
||||
const sentryDSN = "https://5a81ca98270b4aee89d4d9913b259fec:583d2c118b384712aa8b91afbdabde81@sentry.cfops.it/170" // we probably need a public accessable url.
|
||||
|
||||
// Flags return the global flags for Access related commands (hopefully none)
|
||||
func Flags() []cli.Flag {
|
||||
return []cli.Flag{} // no flags yet.
|
||||
}
|
||||
|
||||
// Commands returns all the Access related subcommands
|
||||
func Commands() []*cli.Command {
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "access",
|
||||
Category: "Access",
|
||||
Usage: "access <subcommand>",
|
||||
Description: `Cloudflare Access protects internal resources by securing, authenticating and monitoring access
|
||||
per-user and by application. With Cloudflare Access, only authenticated users with the required permissions are
|
||||
able to reach sensitive resources. The commands provided here allow you to interact with Access protected
|
||||
applications from the command line.`,
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "login",
|
||||
Action: login,
|
||||
Usage: "login <url of access application>",
|
||||
Description: `The login subcommand initiates an authentication flow with your identity provider.
|
||||
The subcommand will launch a browser. For headless systems, a URL is provided.
|
||||
Once authenticated with your identity provider, the login command will generate a JSON Web Token (JWT)
|
||||
scoped to your identity, the application you intend to reach, and valid for a session duration set by your
|
||||
administrator. cloudflared stores the token in local storage.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "url",
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "curl",
|
||||
Action: curl,
|
||||
Usage: "curl <args>",
|
||||
Description: `The curl subcommand wraps curl and automatically injects the JWT into a cf-jwt-access-assertion
|
||||
header when using curl to reach an application behind Access.`,
|
||||
ArgsUsage: "nojwt will allow the curl request to continue even if the jwt is not present.",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "allow-request",
|
||||
Aliases: []string{"ar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "token",
|
||||
Action: token,
|
||||
Usage: "token -app=<url of access application>",
|
||||
ArgsUsage: "url of Access application",
|
||||
Description: `The token subcommand produces a JWT which can be used to authenticate requests.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "app",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// login pops up the browser window to do the actual login and JWT generation
|
||||
func login(c *cli.Context) error {
|
||||
raven.SetDSN(sentryDSN)
|
||||
logger := log.CreateLogger()
|
||||
args := c.Args()
|
||||
appURL, err := url.Parse(args.First())
|
||||
if args.Len() < 1 || err != nil {
|
||||
logger.Errorf("Please provide the URL of the Access application\n")
|
||||
return err
|
||||
}
|
||||
if err := fetchToken(c, appURL); err != nil {
|
||||
logger.Errorf("Failed to fetch token: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// curl provides a wrapper around curl, passing Access JWT along in request
|
||||
func curl(c *cli.Context) error {
|
||||
raven.SetDSN(sentryDSN)
|
||||
logger := log.CreateLogger()
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
logger.Error("Please provide the access app and command you wish to run.")
|
||||
return errors.New("incorrect args")
|
||||
}
|
||||
|
||||
var appURL *url.URL
|
||||
cmdArgs := args.Slice()
|
||||
for _, arg := range cmdArgs {
|
||||
u, err := url.ParseRequestURI(arg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
appURL = u
|
||||
break
|
||||
}
|
||||
|
||||
token, err := getTokenIfExists(appURL)
|
||||
if err != nil || token == "" {
|
||||
if !c.Bool("nojwt") {
|
||||
if err := fetchToken(c, appURL); err != nil {
|
||||
logger.Errorf("Failed to refresh token: %s\n", err)
|
||||
return err
|
||||
}
|
||||
token, err = getTokenIfExists(appURL)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed pull existing token: %s\n", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logger.Warn("You don't have an Access token set. Please run access token <access application> to fetch one.")
|
||||
}
|
||||
}
|
||||
|
||||
cmdArgs = append(cmdArgs, "-H")
|
||||
cmdArgs = append(cmdArgs, fmt.Sprintf("cf-jwt-access-assertion=%s", token))
|
||||
return shell.Run("curl", cmdArgs...)
|
||||
}
|
||||
|
||||
// token dumps provided token to stdout
|
||||
func token(c *cli.Context) error {
|
||||
raven.SetDSN(sentryDSN)
|
||||
appURL, err := url.Parse(c.String("app"))
|
||||
if err != nil || c.NumFlags() < 1 {
|
||||
fmt.Fprintln(os.Stderr, "Please provide access application.")
|
||||
return err
|
||||
}
|
||||
token, err := getTokenIfExists(appURL)
|
||||
if err != nil || token == "" {
|
||||
fmt.Fprintln(os.Stderr, "Unable to find token for provided application. Please run token command to generate token.")
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(os.Stdout, token); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Failed to write token to stdout.")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
86
cmd/cloudflared/access/token.go
Normal file
86
cmd/cloudflared/access/token.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/transfer"
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
"github.com/coreos/go-oidc/jose"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
cli "gopkg.in/urfave/cli.v2"
|
||||
)
|
||||
|
||||
var logger = log.CreateLogger()
|
||||
|
||||
// fetchToken will either load a stored token or generate a new one
|
||||
func fetchToken(c *cli.Context, appURL *url.URL) error {
|
||||
if token, err := getTokenIfExists(appURL); token != "" && err == nil {
|
||||
fmt.Fprintf(os.Stdout, "You have an existing token:\n\n%s\n\n", token)
|
||||
return nil
|
||||
}
|
||||
|
||||
path, err := generateFilePathForTokenURL(appURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := transfer.Run(c, appURL, "token", path, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stdout, "Successfully fetched your token:\n\n%s\n\n", string(token))
|
||||
return nil
|
||||
}
|
||||
|
||||
// getTokenIfExists will return the token from local storage if it exists
|
||||
func getTokenIfExists(url *url.URL) (string, error) {
|
||||
path, err := generateFilePathForTokenURL(url)
|
||||
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
|
||||
}
|
||||
|
||||
claims, err := token.Claims()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ident, err := oidc.IdentityFromClaims(claims)
|
||||
if err == nil && ident.ExpiresAt.After(time.Now()) {
|
||||
return token.Encode(), nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// generateFilePathForTokenURL will return a filepath for given access application url
|
||||
func generateFilePathForTokenURL(url *url.URL) (string, error) {
|
||||
configPath, err := homedir.Expand(config.DefaultConfigDirs[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ok, err := config.FileExists(configPath)
|
||||
if !ok && err == nil {
|
||||
// create config directory if doesn't already exist
|
||||
err = os.Mkdir(configPath, 0700)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := strings.Replace(fmt.Sprintf("%s%s-token", url.Hostname(), url.EscapedPath()), "/", "-", -1)
|
||||
return filepath.Join(configPath, name), nil
|
||||
}
|
Reference in New Issue
Block a user