Compare commits

...

4 Commits

Author SHA1 Message Date
João "Pisco" Fernandes f9c2bd51ae Release 2025.8.1 2025-08-21 10:53:31 +01:00
Devin Carr 41dffd7f3c CUSTESC-53681: Correct QUIC connection management for datagram handlers
Corrects the pattern of using errgroup's and context cancellation to simplify the logic for canceling extra routines for the QUIC connection. This is because the extra context cancellation is redundant with the fact that the errgroup also cancels it's own provided context when a routine returns (error or not).

For the datagram handler specifically, since it can respond faster to a context cancellation from the QUIC connection, we wrap the error before surfacing it outside of the QUIC connection scope to the supervisor. Additionally, the supervisor will look for this error type to check if it should retry the QUIC connection. These two operations are required because the supervisor does not look for a context canceled error when deciding to retry a connection. If a context canceled from the datagram handler were to be returned up to the supervisor on the initial connection, the cloudflared application would exit. We want to ensure that cloudflared maintains connection attempts even if any of the services on-top of a QUIC connection fail (datagram handler in this case).

Additional logging is also introduced along these paths to help with understanding the error conditions from the specific handlers on-top of a QUIC connection.

Related CUSTESC-53681

Closes TUN-9610
2025-08-19 16:10:00 -07:00
Kyle Hiller 8825ceecb5 AUTH-7480 update fed callback url for login helper
* AUTH-7480 update fed callback url for login helper
2025-08-19 18:54:31 +00:00
Kevin Marshall 50104548cf AUTH-7260: Add support for login interstitial auto closure
Adds a switch `--auto-close` which automatically closes Access login interstitial windows/tabs immediately after the user chooses Approve or Deny.
2025-08-12 20:41:12 +00:00
18 changed files with 154 additions and 107 deletions
+5
View File
@@ -1,3 +1,8 @@
2025.8.1
- 2025-08-19 AUTH-7480 update fed callback url for login helper
- 2025-08-19 CUSTESC-53681: Correct QUIC connection management for datagram handlers
- 2025-08-12 AUTH-7260: Add support for login interstitial auto closure
2025.8.0
- 2025-08-07 vuln: Fix GO-2025-3770 vulnerability
- 2025-07-23 TUN-9583: set proper url and hostname for cloudflared tail command
+8 -7
View File
@@ -26,11 +26,13 @@ const (
)
type StartOptions struct {
AppInfo *token.AppInfo
OriginURL string
Headers http.Header
Host string
TLSClientConfig *tls.Config
AppInfo *token.AppInfo
OriginURL string
Headers http.Header
Host string
TLSClientConfig *tls.Config
AutoCloseInterstitial bool
IsFedramp bool
}
// Connection wraps up all the needed functions to forward over the tunnel
@@ -46,7 +48,6 @@ type StdinoutStream struct{}
// Read will read from Stdin
func (c *StdinoutStream) Read(p []byte) (int, error) {
return os.Stdin.Read(p)
}
// Write will write to Stdout
@@ -139,7 +140,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, options.AppInfo, options.AutoCloseInterstitial, options.IsFedramp, log)
if err != nil {
return nil, err
}
+2
View File
@@ -47,6 +47,7 @@ func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *z
options := &carrier.StartOptions{
OriginURL: forwarder.URL,
Headers: headers, //TODO: TUN-2688 support custom headers from config file
IsFedramp: forwarder.IsFedramp,
}
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
@@ -92,6 +93,7 @@ func ssh(c *cli.Context) error {
OriginURL: url.String(),
Headers: headers,
Host: url.Host,
IsFedramp: c.Bool(fedrampFlag),
}
if connectTo := c.String(sshConnectTo); connectTo != "" {
+12 -3
View File
@@ -51,6 +51,7 @@ Host {{.Hostname}}
ProxyCommand {{.Cloudflared}} access ssh --hostname %h
{{end}}
`
fedrampFlag = "fedramp"
)
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
@@ -79,6 +80,10 @@ func Commands() []*cli.Command {
Aliases: []string{"forward"},
Category: "Access",
Usage: "access <subcommand>",
Flags: []cli.Flag{&cli.BoolFlag{
Name: fedrampFlag,
Usage: "use when performing operations in fedramp account",
}},
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
@@ -104,6 +109,10 @@ func Commands() []*cli.Command {
Name: "no-verbose",
Usage: "print only the jwt to stdout",
},
&cli.BoolFlag{
Name: "auto-close",
Usage: "automatically close the auth interstitial after action",
},
&cli.StringFlag{
Name: appURLFlag,
},
@@ -322,7 +331,7 @@ func curl(c *cli.Context) error {
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, appInfo, c.Bool(cfdflags.AutoCloseInterstitial), c.Bool(fedrampFlag), log)
if err != nil {
log.Err(err).Msg("Failed to refresh token")
return err
@@ -442,7 +451,7 @@ func sshGen(c *cli.Context) error {
if err != nil {
return err
}
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, appInfo, log)
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, appInfo, c.Bool(cfdflags.AutoCloseInterstitial), c.Bool(fedrampFlag), log)
if err != nil {
return err
}
@@ -542,7 +551,7 @@ func verifyTokenAtEdge(appUrl *url.URL, appInfo *token.AppInfo, c *cli.Context,
if c.IsSet(sshTokenSecretFlag) {
headers.Add(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
}
options := &carrier.StartOptions{AppInfo: appInfo, OriginURL: appUrl.String(), Headers: headers}
options := &carrier.StartOptions{AppInfo: appInfo, OriginURL: appUrl.String(), Headers: headers, AutoCloseInterstitial: c.Bool(cfdflags.AutoCloseInterstitial), IsFedramp: c.Bool(fedrampFlag)}
if valid, err := isTokenValid(options, log); err != nil {
return err
+3
View File
@@ -163,4 +163,7 @@ const (
// Management hostname to signify incoming management requests
ManagementHostname = "management-hostname"
// Automatically close the login interstitial browser window after the user makes a decision.
AutoCloseInterstitial = "auto-close"
)
+7 -5
View File
@@ -12,6 +12,7 @@ import (
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
cfdflags "github.com/cloudflare/cloudflared/cmd/cloudflared/flags"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/credentials"
"github.com/cloudflare/cloudflared/logger"
@@ -19,11 +20,10 @@ import (
)
const (
baseLoginURL = "https://dash.cloudflare.com/argotunnel"
callbackURL = "https://login.cloudflareaccess.org/"
// For now these are the same but will change in the future once we know which URLs to use (TUN-8872)
fedBaseLoginURL = "https://dash.cloudflare.com/argotunnel"
fedCallbackStoreURL = "https://login.cloudflareaccess.org/"
baseLoginURL = "https://dash.cloudflare.com/argotunnel"
callbackURL = "https://login.cloudflareaccess.org/"
fedBaseLoginURL = "https://dash.fed.cloudflare.com/argotunnel"
fedCallbackStoreURL = "https://login.fed.cloudflareaccess.org/"
fedRAMPParamName = "fedramp"
loginURLParamName = "loginURL"
callbackURLParamName = "callbackURL"
@@ -97,6 +97,8 @@ func login(c *cli.Context) error {
callbackStoreURL,
false,
false,
c.Bool(cfdflags.AutoCloseInterstitial),
isFEDRamp,
log,
)
if err != nil {
+15 -14
View File
@@ -1,7 +1,7 @@
package config
import (
"crypto/md5"
"crypto/sha256"
"fmt"
"io"
"strings"
@@ -16,6 +16,7 @@ type Forwarder struct {
TokenClientID string `json:"service_token_id" yaml:"serviceTokenID"`
TokenSecret string `json:"secret_token_id" yaml:"serviceTokenSecret"`
Destination string `json:"destination"`
IsFedramp bool `json:"is_fedramp" yaml:"isFedramp"`
}
// Tunnel represents a tunnel that should be started
@@ -46,24 +47,24 @@ type Root struct {
// Hash returns the computed values to see if the forwarder values change
func (f *Forwarder) Hash() string {
h := md5.New()
io.WriteString(h, f.URL)
io.WriteString(h, f.Listener)
io.WriteString(h, f.TokenClientID)
io.WriteString(h, f.TokenSecret)
io.WriteString(h, f.Destination)
h := sha256.New()
_, _ = io.WriteString(h, f.URL)
_, _ = io.WriteString(h, f.Listener)
_, _ = io.WriteString(h, f.TokenClientID)
_, _ = io.WriteString(h, f.TokenSecret)
_, _ = io.WriteString(h, f.Destination)
return fmt.Sprintf("%x", h.Sum(nil))
}
// Hash returns the computed values to see if the forwarder values change
func (r *DNSResolver) Hash() string {
h := md5.New()
io.WriteString(h, r.Address)
io.WriteString(h, strings.Join(r.Bootstraps, ","))
io.WriteString(h, strings.Join(r.Upstreams, ","))
io.WriteString(h, fmt.Sprintf("%d", r.Port))
io.WriteString(h, fmt.Sprintf("%d", r.MaxUpstreamConnections))
io.WriteString(h, fmt.Sprintf("%v", r.Enabled))
h := sha256.New()
_, _ = io.WriteString(h, r.Address)
_, _ = io.WriteString(h, strings.Join(r.Bootstraps, ","))
_, _ = io.WriteString(h, strings.Join(r.Upstreams, ","))
_, _ = io.WriteString(h, fmt.Sprintf("%d", r.Port))
_, _ = io.WriteString(h, fmt.Sprintf("%d", r.MaxUpstreamConnections))
_, _ = io.WriteString(h, fmt.Sprintf("%v", r.Enabled))
return fmt.Sprintf("%x", h.Sum(nil))
}
+1 -1
View File
@@ -82,7 +82,7 @@ func (c *controlStream) ServeControlStream(
tunnelConfigGetter TunnelConfigJSONGetter,
) error {
registrationClient := c.registerClientFunc(ctx, rw, c.registerTimeout)
c.observer.logConnecting(c.connIndex, c.edgeAddress, c.protocol)
registrationDetails, err := registrationClient.RegisterConnection(
ctx,
c.tunnelProperties.Credentials.Auth(),
+17 -18
View File
@@ -1,7 +1,6 @@
package connection
import (
"github.com/cloudflare/cloudflared/edgediscovery"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
@@ -53,26 +52,26 @@ func serverRegistrationErrorFromRPC(err error) ServerRegisterTunnelError {
}
}
type muxerShutdownError struct{}
type ControlStreamError struct{}
func (e muxerShutdownError) Error() string {
return "muxer shutdown"
var _ error = &ControlStreamError{}
func (e *ControlStreamError) Error() string {
return "control stream encountered a failure while serving"
}
var errMuxerStopped = muxerShutdownError{}
type StreamListenerError struct{}
func isHandshakeErrRecoverable(err error, connIndex uint8, observer *Observer) bool {
log := observer.log.With().
Uint8(LogFieldConnIndex, connIndex).
Err(err).
Logger()
var _ error = &StreamListenerError{}
switch err.(type) {
case edgediscovery.DialError:
log.Error().Msg("Connection unable to dial edge")
default:
log.Error().Msg("Connection failed")
return false
}
return true
func (e *StreamListenerError) Error() string {
return "accept stream listener encountered a failure while serving"
}
type DatagramManagerError struct{}
var _ error = &DatagramManagerError{}
func (e *DatagramManagerError) Error() string {
return "datagram manager encountered a failure while serving"
}
+9
View File
@@ -46,6 +46,15 @@ func (o *Observer) RegisterSink(sink EventSink) {
o.addSinkChan <- sink
}
func (o *Observer) logConnecting(connIndex uint8, address net.IP, protocol Protocol) {
o.log.Debug().
Int(management.EventTypeKey, int(management.Cloudflared)).
Uint8(LogFieldConnIndex, connIndex).
IPAddr(LogFieldIPAddress, address).
Str(LogFieldProtocol, protocol.String()).
Msg("Registering tunnel connection")
}
func (o *Observer) logConnected(connectionID uuid.UUID, connIndex uint8, location string, address net.IP, protocol Protocol) {
o.log.Info().
Int(management.EventTypeKey, int(management.Cloudflared)).
+28 -18
View File
@@ -3,6 +3,7 @@ package connection
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"net"
@@ -12,7 +13,6 @@ import (
"sync/atomic"
"time"
"github.com/pkg/errors"
"github.com/quic-go/quic-go"
"github.com/rs/zerolog"
"golang.org/x/sync/errgroup"
@@ -65,7 +65,7 @@ func NewTunnelConnection(
streamWriteTimeout time.Duration,
gracePeriod time.Duration,
logger *zerolog.Logger,
) (TunnelConnection, error) {
) TunnelConnection {
return &quicConnection{
conn: conn,
logger: logger,
@@ -77,10 +77,11 @@ func NewTunnelConnection(
rpcTimeout: rpcTimeout,
streamWriteTimeout: streamWriteTimeout,
gracePeriod: gracePeriod,
}, nil
}
}
// Serve starts a QUIC connection that begins accepting streams.
// Returning a nil error means cloudflared will exit for good and will not attempt to reconnect.
func (q *quicConnection) Serve(ctx context.Context) error {
// The edge assumes the first stream is used for the control plane
controlStream, err := q.conn.OpenStream()
@@ -88,16 +89,16 @@ func (q *quicConnection) Serve(ctx context.Context) error {
return fmt.Errorf("failed to open a registration control stream: %w", err)
}
// If either goroutine returns nil error, we rely on this cancellation to make sure the other goroutine exits
// as fast as possible as well. Nil error means we want to exit for good (caller code won't retry serving this
// connection).
// If either goroutine returns a non nil error, then the error group cancels the context, thus also canceling the
// other goroutine as fast as possible.
ctx, cancel := context.WithCancel(ctx)
// other goroutines. We enforce returning a not-nil error for each function started in the errgroup by logging
// the error returned and returning a custom error type instead.
errGroup, ctx := errgroup.WithContext(ctx)
// In the future, if cloudflared can autonomously push traffic to the edge, we have to make sure the control
// stream is already fully registered before the other goroutines can proceed.
// Close the quic connection if any of the following routines return from the errgroup (regardless of their error)
// because they are no longer processing requests for the connection.
defer q.Close()
// Start the control stream routine
errGroup.Go(func() error {
// err is equal to nil if we exit due to unregistration. If that happens we want to wait the full
// amount of the grace period, allowing requests to finish before we cancel the context, which will
@@ -114,16 +115,26 @@ func (q *quicConnection) Serve(ctx context.Context) error {
}
}
}
cancel()
return err
if err != nil {
q.logger.Error().Err(err).Msg("failed to serve the control stream")
}
return &ControlStreamError{}
})
// Start the accept stream loop routine
errGroup.Go(func() error {
defer cancel()
return q.acceptStream(ctx)
err := q.acceptStream(ctx)
if err != nil {
q.logger.Error().Err(err).Msg("failed to accept incoming stream requests")
}
return &StreamListenerError{}
})
// Start the datagram handler routine
errGroup.Go(func() error {
defer cancel()
return q.datagramHandler.Serve(ctx)
err := q.datagramHandler.Serve(ctx)
if err != nil {
q.logger.Error().Err(err).Msg("failed to run the datagram handler")
}
return &DatagramManagerError{}
})
return errGroup.Wait()
@@ -140,7 +151,6 @@ func (q *quicConnection) Close() {
}
func (q *quicConnection) acceptStream(ctx context.Context) error {
defer q.Close()
for {
quicStream, err := q.conn.AcceptStream(ctx)
if err != nil {
@@ -230,7 +240,7 @@ func (q *quicConnection) dispatchRequest(ctx context.Context, stream *rpcquic.Re
ConnIndex: q.connIndex,
}), rwa.connectResponseSent
default:
return errors.Errorf("unsupported error type: %s", request.Type), false
return fmt.Errorf("unsupported error type: %s", request.Type), false
}
}
+1 -2
View File
@@ -847,7 +847,7 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
&log,
}
tunnelConn, err := NewTunnelConnection(
tunnelConn := NewTunnelConnection(
ctx,
conn,
index,
@@ -860,7 +860,6 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
0*time.Second,
&log,
)
require.NoError(t, err)
return tunnelConn, datagramConn
}
+2 -9
View File
@@ -98,24 +98,17 @@ func NewDatagramV2Connection(ctx context.Context,
}
func (d *datagramV2Connection) Serve(ctx context.Context) error {
// If either goroutine returns nil error, we rely on this cancellation to make sure the other goroutine exits
// as fast as possible as well. Nil error means we want to exit for good (caller code won't retry serving this
// connection).
// If either goroutine returns a non nil error, then the error group cancels the context, thus also canceling the
// other goroutine as fast as possible.
ctx, cancel := context.WithCancel(ctx)
// If either goroutine from the errgroup returns at all (error or nil), we rely on its cancellation to make sure
// the other goroutines as well.
errGroup, ctx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
defer cancel()
return d.sessionManager.Serve(ctx)
})
errGroup.Go(func() error {
defer cancel()
return d.datagramMuxer.ServeReceive(ctx)
})
errGroup.Go(func() error {
defer cancel()
return d.packetRouter.Serve(ctx)
})
+1 -1
View File
@@ -175,7 +175,7 @@ func (c *datagramConn) Serve(ctx context.Context) error {
// Monitor the context of cloudflared
case <-ctx.Done():
return ctx.Err()
// Monitor the context of the underlying connection
// Monitor the context of the underlying quic connection
case <-connCtx.Done():
return connCtx.Err()
// Monitor for any hard errors from reading the connection
+8 -8
View File
@@ -132,6 +132,7 @@ func (s *Supervisor) Run(
if err == errEarlyShutdown {
return nil
}
s.log.Logger().Error().Err(err).Msg("initial tunnel connection failed")
return err
}
var tunnelsWaiting []int
@@ -154,6 +155,7 @@ func (s *Supervisor) Run(
// (note that this may also be caused by context cancellation)
case tunnelError := <-s.tunnelErrors:
tunnelsActive--
s.log.ConnAwareLogger().Err(tunnelError.err).Int(connection.LogFieldConnIndex, tunnelError.index).Msg("Connection terminated")
if tunnelError.err != nil && !shuttingDown {
switch tunnelError.err.(type) {
case ReconnectSignal:
@@ -166,7 +168,6 @@ func (s *Supervisor) Run(
if _, retry := s.tunnelsProtocolFallback[tunnelError.index].GetMaxBackoffDuration(ctx); !retry {
continue
}
s.log.ConnAwareLogger().Err(tunnelError.err).Int(connection.LogFieldConnIndex, tunnelError.index).Msg("Connection terminated")
tunnelsWaiting = append(tunnelsWaiting, tunnelError.index)
s.waitForNextTunnel(tunnelError.index)
@@ -285,7 +286,10 @@ func (s *Supervisor) startFirstTunnel(
*quic.IdleTimeoutError,
*quic.ApplicationError,
edgediscovery.DialError,
*connection.EdgeQuicDialError:
*connection.EdgeQuicDialError,
*connection.ControlStreamError,
*connection.StreamListenerError,
*connection.DatagramManagerError:
// Try again for these types of errors
default:
// Uncaught errors should bail startup
@@ -301,13 +305,9 @@ func (s *Supervisor) startTunnel(
index int,
connectedSignal *signal.Signal,
) {
var err error
defer func() {
s.tunnelErrors <- tunnelError{index: index, err: err}
}()
// nolint: gosec
err = s.edgeTunnelServer.Serve(ctx, uint8(index), s.tunnelsProtocolFallback[index], connectedSignal)
err := s.edgeTunnelServer.Serve(ctx, uint8(index), s.tunnelsProtocolFallback[index], connectedSignal)
s.tunnelErrors <- tunnelError{index: index, err: err}
}
func (s *Supervisor) newConnectedTunnelSignal(index int) *signal.Signal {
+3 -6
View File
@@ -556,6 +556,7 @@ func (e *EdgeTunnelServer) serveQUIC(
pqMode := connOptions.FeatureSnapshot.PostQuantum
curvePref, err := curvePreference(pqMode, fips.IsFipsEnabled(), tlsConfig.CurvePreferences)
if err != nil {
connLogger.ConnAwareLogger().Err(err).Msgf("failed to get curve preferences")
return err, true
}
@@ -627,7 +628,7 @@ func (e *EdgeTunnelServer) serveQUIC(
}
// Wrap the [quic.Connection] as a TunnelConnection
tunnelConn, err := connection.NewTunnelConnection(
tunnelConn := connection.NewTunnelConnection(
ctx,
conn,
connIndex,
@@ -640,17 +641,13 @@ func (e *EdgeTunnelServer) serveQUIC(
e.config.GracePeriod,
connLogger.Logger(),
)
if err != nil {
connLogger.ConnAwareLogger().Err(err).Msgf("Failed to create new tunnel connection")
return err, true
}
// Serve the TunnelConnection
errGroup, serveCtx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
err := tunnelConn.Serve(serveCtx)
if err != nil {
connLogger.ConnAwareLogger().Err(err).Msg("Failed to serve tunnel connection")
connLogger.ConnAwareLogger().Err(err).Msg("failed to serve tunnel connection")
}
return err
})
+9 -8
View File
@@ -185,18 +185,18 @@ func Init(version string) {
// 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, appInfo *AppInfo, autoClose bool, isFedramp bool, log *zerolog.Logger) (string, error) {
return getToken(appURL, appInfo, false, autoClose, isFedramp, 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, appInfo *AppInfo, autoClose bool, isFedramp bool, log *zerolog.Logger) (string, error) {
return getToken(appURL, appInfo, true, autoClose, isFedramp, 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) {
func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, autoClose bool, isFedramp bool, log *zerolog.Logger) (string, error) {
if token, err := GetAppTokenIfExists(appInfo); token != "" && err == nil {
return token, nil
}
@@ -249,18 +249,19 @@ func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, log *zerolog.
return appToken, nil
}
}
return getTokensFromEdge(appURL, appInfo.AppAUD, appTokenPath, orgTokenPath, useHostOnly, log)
return getTokensFromEdge(appURL, appInfo.AppAUD, appTokenPath, orgTokenPath, useHostOnly, autoClose, isFedramp, log)
}
// getTokensFromEdge will attempt to use the transfer service to retrieve an app and org token, save them to disk,
// and return the app token.
func getTokensFromEdge(appURL *url.URL, appAUD, appTokenPath, orgTokenPath string, useHostOnly bool, log *zerolog.Logger) (string, error) {
func getTokensFromEdge(appURL *url.URL, appAUD, appTokenPath, orgTokenPath string, useHostOnly bool, autoClose bool, isFedramp bool, log *zerolog.Logger) (string, error) {
fmt.Println("Get tokens from edge ", autoClose)
// If no org token exists or if it couldn't be exchanged for an app token, then run the transfer service flow.
// 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)
resourceData, err := RunTransfer(appURL, appAUD, keyName, keyName, "", true, useHostOnly, log)
resourceData, err := RunTransfer(appURL, appAUD, keyName, keyName, "", true, useHostOnly, autoClose, isFedramp, log)
if err != nil {
return "", errors.Wrap(err, "failed to run transfer service")
}
+23 -7
View File
@@ -16,6 +16,7 @@ import (
const (
baseStoreURL = "https://login.cloudflareaccess.org/"
fedStoreURL = "https://login.fed.cloudflareaccess.org/"
clientTimeout = time.Second * 60
)
@@ -25,12 +26,12 @@ const (
// The "dance" we refer to is building a HTTP request, opening that in a browser waiting for
// the user to complete an action, while it long polls in the background waiting for an
// action to be completed to download the resource.
func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, log *zerolog.Logger) ([]byte, error) {
func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string, shouldEncrypt bool, useHostOnly bool, autoClose bool, fedramp bool, log *zerolog.Logger) ([]byte, error) {
encrypterClient, err := NewEncrypter("cloudflared_priv.pem", "cloudflared_pub.pem")
if err != nil {
return nil, err
}
requestURL, err := buildRequestURL(transferURL, appAUD, key, value+encrypterClient.PublicKey(), shouldEncrypt, useHostOnly)
requestURL, err := buildRequestURL(transferURL, appAUD, key, value+encrypterClient.PublicKey(), shouldEncrypt, useHostOnly, autoClose)
if err != nil {
return nil, err
}
@@ -45,8 +46,14 @@ func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string,
var resourceData []byte
storeURL := baseStoreURL
if fedramp {
storeURL = fedStoreURL
}
if shouldEncrypt {
buf, key, err := transferRequest(baseStoreURL+"transfer/"+encrypterClient.PublicKey(), log)
buf, key, err := transferRequest(storeURL+"transfer/"+encrypterClient.PublicKey(), log)
if err != nil {
return nil, err
}
@@ -62,7 +69,7 @@ func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string,
resourceData = decrypted
} else {
buf, _, err := transferRequest(baseStoreURL+encrypterClient.PublicKey(), log)
buf, _, err := transferRequest(storeURL+encrypterClient.PublicKey(), log)
if err != nil {
return nil, err
}
@@ -75,7 +82,7 @@ func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string,
// BuildRequestURL creates a request suitable for a resource transfer.
// it will return a constructed url based off the base url and query key/value provided.
// cli will build a url for cli transfer request.
func buildRequestURL(baseURL *url.URL, appAUD string, key, value string, cli, useHostOnly bool) (string, error) {
func buildRequestURL(baseURL *url.URL, appAUD string, key, value string, cli, useHostOnly bool, autoClose bool) (string, error) {
q := baseURL.Query()
q.Set(key, value)
q.Set("aud", appAUD)
@@ -90,7 +97,11 @@ func buildRequestURL(baseURL *url.URL, appAUD string, key, value string, cli, us
q.Set("redirect_url", baseURL.String()) // we add the token as a query param on both the redirect_url and the main url
q.Set("send_org_token", "true") // indicates that the cli endpoint should return both the org and app token
q.Set("edge_token_transfer", "true") // use new LoginHelper service built on workers
baseURL.RawQuery = q.Encode() // and this actual baseURL.
if autoClose {
q.Set("close_interstitial", "true") // Automatically close the success window.
}
baseURL.RawQuery = q.Encode() // and this actual baseURL.
baseURL.Path = "cdn-cgi/access/cli"
return baseURL.String(), nil
}
@@ -127,7 +138,12 @@ func poll(client *http.Client, requestURL string, log *zerolog.Logger) ([]byte,
// ignore everything other than server errors as the resource
// may not exist until the user does the interaction
if resp.StatusCode >= 500 {
return nil, "", fmt.Errorf("error on request %d", resp.StatusCode)
buf := new(bytes.Buffer)
if _, err := io.Copy(buf, resp.Body); err != nil {
return nil, "", err
}
return nil, "", fmt.Errorf("error on request %d: %s", resp.StatusCode, buf.String())
}
if resp.StatusCode != 200 {
log.Info().Msg("Waiting for login...")