Merge branch 'cloudflare:master' into master
This commit is contained in:
commit
0de8a73541
|
|
@ -1,3 +1,13 @@
|
||||||
|
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
|
||||||
|
- 2025-07-07 TUN-9542: Remove unsupported Debian-based releases
|
||||||
|
|
||||||
2025.7.0
|
2025.7.0
|
||||||
- 2025-07-03 TUN-9540: Use numeric user id for Dockerfiles
|
- 2025-07-03 TUN-9540: Use numeric user id for Dockerfiles
|
||||||
- 2025-07-01 TUN-9161: Remove P256Kyber768Draft00PQKex curve from nonFips curve preferences
|
- 2025-07-01 TUN-9161: Remove P256Kyber768Draft00PQKex curve from nonFips curve preferences
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ type StartOptions struct {
|
||||||
Headers http.Header
|
Headers http.Header
|
||||||
Host string
|
Host string
|
||||||
TLSClientConfig *tls.Config
|
TLSClientConfig *tls.Config
|
||||||
|
AutoCloseInterstitial bool
|
||||||
|
IsFedramp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection wraps up all the needed functions to forward over the tunnel
|
// Connection wraps up all the needed functions to forward over the tunnel
|
||||||
|
|
@ -46,7 +48,6 @@ type StdinoutStream struct{}
|
||||||
// Read will read from Stdin
|
// Read will read from Stdin
|
||||||
func (c *StdinoutStream) Read(p []byte) (int, error) {
|
func (c *StdinoutStream) Read(p []byte) (int, error) {
|
||||||
return os.Stdin.Read(p)
|
return os.Stdin.Read(p)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write will write to Stdout
|
// Write will write to Stdout
|
||||||
|
|
@ -139,7 +140,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, options.AppInfo, options.AutoCloseInterstitial, options.IsFedramp, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *z
|
||||||
options := &carrier.StartOptions{
|
options := &carrier.StartOptions{
|
||||||
OriginURL: forwarder.URL,
|
OriginURL: forwarder.URL,
|
||||||
Headers: headers, //TODO: TUN-2688 support custom headers from config file
|
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
|
// 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(),
|
OriginURL: url.String(),
|
||||||
Headers: headers,
|
Headers: headers,
|
||||||
Host: url.Host,
|
Host: url.Host,
|
||||||
|
IsFedramp: c.Bool(fedrampFlag),
|
||||||
}
|
}
|
||||||
|
|
||||||
if connectTo := c.String(sshConnectTo); connectTo != "" {
|
if connectTo := c.String(sshConnectTo); connectTo != "" {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ Host {{.Hostname}}
|
||||||
ProxyCommand {{.Cloudflared}} access ssh --hostname %h
|
ProxyCommand {{.Cloudflared}} access ssh --hostname %h
|
||||||
{{end}}
|
{{end}}
|
||||||
`
|
`
|
||||||
|
fedrampFlag = "fedramp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
|
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
|
||||||
|
|
@ -79,6 +80,10 @@ func Commands() []*cli.Command {
|
||||||
Aliases: []string{"forward"},
|
Aliases: []string{"forward"},
|
||||||
Category: "Access",
|
Category: "Access",
|
||||||
Usage: "access <subcommand>",
|
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
|
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
|
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
|
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",
|
Name: "no-verbose",
|
||||||
Usage: "print only the jwt to stdout",
|
Usage: "print only the jwt to stdout",
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "auto-close",
|
||||||
|
Usage: "automatically close the auth interstitial after action",
|
||||||
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: appURLFlag,
|
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.")
|
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, appInfo, c.Bool(cfdflags.AutoCloseInterstitial), c.Bool(fedrampFlag), 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
|
||||||
|
|
@ -442,7 +451,7 @@ func sshGen(c *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -542,7 +551,7 @@ func verifyTokenAtEdge(appUrl *url.URL, appInfo *token.AppInfo, c *cli.Context,
|
||||||
if c.IsSet(sshTokenSecretFlag) {
|
if c.IsSet(sshTokenSecretFlag) {
|
||||||
headers.Add(cfAccessClientSecretHeader, c.String(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 {
|
if valid, err := isTokenValid(options, log); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -163,4 +163,7 @@ const (
|
||||||
|
|
||||||
// Management hostname to signify incoming management requests
|
// Management hostname to signify incoming management requests
|
||||||
ManagementHostname = "management-hostname"
|
ManagementHostname = "management-hostname"
|
||||||
|
|
||||||
|
// Automatically close the login interstitial browser window after the user makes a decision.
|
||||||
|
AutoCloseInterstitial = "auto-close"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||||
|
cfdflags "github.com/cloudflare/cloudflared/cmd/cloudflared/flags"
|
||||||
"github.com/cloudflare/cloudflared/config"
|
"github.com/cloudflare/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/credentials"
|
"github.com/cloudflare/cloudflared/credentials"
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
"github.com/cloudflare/cloudflared/logger"
|
||||||
|
|
@ -21,9 +22,8 @@ import (
|
||||||
const (
|
const (
|
||||||
baseLoginURL = "https://dash.cloudflare.com/argotunnel"
|
baseLoginURL = "https://dash.cloudflare.com/argotunnel"
|
||||||
callbackURL = "https://login.cloudflareaccess.org/"
|
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.fed.cloudflare.com/argotunnel"
|
||||||
fedBaseLoginURL = "https://dash.cloudflare.com/argotunnel"
|
fedCallbackStoreURL = "https://login.fed.cloudflareaccess.org/"
|
||||||
fedCallbackStoreURL = "https://login.cloudflareaccess.org/"
|
|
||||||
fedRAMPParamName = "fedramp"
|
fedRAMPParamName = "fedramp"
|
||||||
loginURLParamName = "loginURL"
|
loginURLParamName = "loginURL"
|
||||||
callbackURLParamName = "callbackURL"
|
callbackURLParamName = "callbackURL"
|
||||||
|
|
@ -97,6 +97,8 @@ func login(c *cli.Context) error {
|
||||||
callbackStoreURL,
|
callbackStoreURL,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
c.Bool(cfdflags.AutoCloseInterstitial),
|
||||||
|
isFEDRamp,
|
||||||
log,
|
log,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -155,10 +155,12 @@ func (sc *subcommandContext) create(name string, credentialsFilePath string, sec
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelCredentials := connection.Credentials{
|
tunnelCredentials := connection.Credentials{
|
||||||
AccountTag: credential.AccountID(),
|
AccountTag: credential.AccountID(),
|
||||||
TunnelSecret: tunnelSecret,
|
TunnelSecret: tunnelSecret,
|
||||||
TunnelID: tunnel.ID,
|
TunnelID: tunnel.ID,
|
||||||
|
Endpoint: credential.Endpoint(),
|
||||||
}
|
}
|
||||||
usedCertPath := false
|
usedCertPath := false
|
||||||
if credentialsFilePath == "" {
|
if credentialsFilePath == "" {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -16,6 +16,7 @@ type Forwarder struct {
|
||||||
TokenClientID string `json:"service_token_id" yaml:"serviceTokenID"`
|
TokenClientID string `json:"service_token_id" yaml:"serviceTokenID"`
|
||||||
TokenSecret string `json:"secret_token_id" yaml:"serviceTokenSecret"`
|
TokenSecret string `json:"secret_token_id" yaml:"serviceTokenSecret"`
|
||||||
Destination string `json:"destination"`
|
Destination string `json:"destination"`
|
||||||
|
IsFedramp bool `json:"is_fedramp" yaml:"isFedramp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tunnel represents a tunnel that should be started
|
// 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
|
// Hash returns the computed values to see if the forwarder values change
|
||||||
func (f *Forwarder) Hash() string {
|
func (f *Forwarder) Hash() string {
|
||||||
h := md5.New()
|
h := sha256.New()
|
||||||
io.WriteString(h, f.URL)
|
_, _ = io.WriteString(h, f.URL)
|
||||||
io.WriteString(h, f.Listener)
|
_, _ = io.WriteString(h, f.Listener)
|
||||||
io.WriteString(h, f.TokenClientID)
|
_, _ = io.WriteString(h, f.TokenClientID)
|
||||||
io.WriteString(h, f.TokenSecret)
|
_, _ = io.WriteString(h, f.TokenSecret)
|
||||||
io.WriteString(h, f.Destination)
|
_, _ = io.WriteString(h, f.Destination)
|
||||||
return fmt.Sprintf("%x", h.Sum(nil))
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the computed values to see if the forwarder values change
|
// Hash returns the computed values to see if the forwarder values change
|
||||||
func (r *DNSResolver) Hash() string {
|
func (r *DNSResolver) Hash() string {
|
||||||
h := md5.New()
|
h := sha256.New()
|
||||||
io.WriteString(h, r.Address)
|
_, _ = io.WriteString(h, r.Address)
|
||||||
io.WriteString(h, strings.Join(r.Bootstraps, ","))
|
_, _ = io.WriteString(h, strings.Join(r.Bootstraps, ","))
|
||||||
io.WriteString(h, strings.Join(r.Upstreams, ","))
|
_, _ = io.WriteString(h, strings.Join(r.Upstreams, ","))
|
||||||
io.WriteString(h, fmt.Sprintf("%d", r.Port))
|
_, _ = io.WriteString(h, fmt.Sprintf("%d", r.Port))
|
||||||
io.WriteString(h, fmt.Sprintf("%d", r.MaxUpstreamConnections))
|
_, _ = io.WriteString(h, fmt.Sprintf("%d", r.MaxUpstreamConnections))
|
||||||
io.WriteString(h, fmt.Sprintf("%v", r.Enabled))
|
_, _ = io.WriteString(h, fmt.Sprintf("%v", r.Enabled))
|
||||||
return fmt.Sprintf("%x", h.Sum(nil))
|
return fmt.Sprintf("%x", h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ func (c *controlStream) ServeControlStream(
|
||||||
tunnelConfigGetter TunnelConfigJSONGetter,
|
tunnelConfigGetter TunnelConfigJSONGetter,
|
||||||
) error {
|
) error {
|
||||||
registrationClient := c.registerClientFunc(ctx, rw, c.registerTimeout)
|
registrationClient := c.registerClientFunc(ctx, rw, c.registerTimeout)
|
||||||
|
c.observer.logConnecting(c.connIndex, c.edgeAddress, c.protocol)
|
||||||
registrationDetails, err := registrationClient.RegisterConnection(
|
registrationDetails, err := registrationClient.RegisterConnection(
|
||||||
ctx,
|
ctx,
|
||||||
c.tunnelProperties.Credentials.Auth(),
|
c.tunnelProperties.Credentials.Auth(),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package connection
|
package connection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
|
||||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
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 {
|
var _ error = &ControlStreamError{}
|
||||||
return "muxer shutdown"
|
|
||||||
|
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 {
|
var _ error = &StreamListenerError{}
|
||||||
log := observer.log.With().
|
|
||||||
Uint8(LogFieldConnIndex, connIndex).
|
|
||||||
Err(err).
|
|
||||||
Logger()
|
|
||||||
|
|
||||||
switch err.(type) {
|
func (e *StreamListenerError) Error() string {
|
||||||
case edgediscovery.DialError:
|
return "accept stream listener encountered a failure while serving"
|
||||||
log.Error().Msg("Connection unable to dial edge")
|
|
||||||
default:
|
|
||||||
log.Error().Msg("Connection failed")
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
type DatagramManagerError struct{}
|
||||||
|
|
||||||
|
var _ error = &DatagramManagerError{}
|
||||||
|
|
||||||
|
func (e *DatagramManagerError) Error() string {
|
||||||
|
return "datagram manager encountered a failure while serving"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,15 @@ func (o *Observer) RegisterSink(sink EventSink) {
|
||||||
o.addSinkChan <- sink
|
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) {
|
func (o *Observer) logConnected(connectionID uuid.UUID, connIndex uint8, location string, address net.IP, protocol Protocol) {
|
||||||
o.log.Info().
|
o.log.Info().
|
||||||
Int(management.EventTypeKey, int(management.Cloudflared)).
|
Int(management.EventTypeKey, int(management.Cloudflared)).
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package connection
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -12,7 +13,6 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/quic-go/quic-go"
|
"github.com/quic-go/quic-go"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
@ -65,7 +65,7 @@ func NewTunnelConnection(
|
||||||
streamWriteTimeout time.Duration,
|
streamWriteTimeout time.Duration,
|
||||||
gracePeriod time.Duration,
|
gracePeriod time.Duration,
|
||||||
logger *zerolog.Logger,
|
logger *zerolog.Logger,
|
||||||
) (TunnelConnection, error) {
|
) TunnelConnection {
|
||||||
return &quicConnection{
|
return &quicConnection{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|
@ -77,10 +77,11 @@ func NewTunnelConnection(
|
||||||
rpcTimeout: rpcTimeout,
|
rpcTimeout: rpcTimeout,
|
||||||
streamWriteTimeout: streamWriteTimeout,
|
streamWriteTimeout: streamWriteTimeout,
|
||||||
gracePeriod: gracePeriod,
|
gracePeriod: gracePeriod,
|
||||||
}, nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve starts a QUIC connection that begins accepting streams.
|
// 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 {
|
func (q *quicConnection) Serve(ctx context.Context) error {
|
||||||
// The edge assumes the first stream is used for the control plane
|
// The edge assumes the first stream is used for the control plane
|
||||||
controlStream, err := q.conn.OpenStream()
|
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)
|
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
|
// 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.
|
// other goroutines. We enforce returning a not-nil error for each function started in the errgroup by logging
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
// the error returned and returning a custom error type instead.
|
||||||
errGroup, ctx := errgroup.WithContext(ctx)
|
errGroup, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
// In the future, if cloudflared can autonomously push traffic to the edge, we have to make sure the control
|
// Close the quic connection if any of the following routines return from the errgroup (regardless of their error)
|
||||||
// stream is already fully registered before the other goroutines can proceed.
|
// because they are no longer processing requests for the connection.
|
||||||
|
defer q.Close()
|
||||||
|
|
||||||
|
// Start the control stream routine
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
// err is equal to nil if we exit due to unregistration. If that happens we want to wait the full
|
// 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
|
// 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()
|
if err != nil {
|
||||||
return err
|
q.logger.Error().Err(err).Msg("failed to serve the control stream")
|
||||||
|
}
|
||||||
|
return &ControlStreamError{}
|
||||||
})
|
})
|
||||||
|
// Start the accept stream loop routine
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
err := q.acceptStream(ctx)
|
||||||
return 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 {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
err := q.datagramHandler.Serve(ctx)
|
||||||
return q.datagramHandler.Serve(ctx)
|
if err != nil {
|
||||||
|
q.logger.Error().Err(err).Msg("failed to run the datagram handler")
|
||||||
|
}
|
||||||
|
return &DatagramManagerError{}
|
||||||
})
|
})
|
||||||
|
|
||||||
return errGroup.Wait()
|
return errGroup.Wait()
|
||||||
|
|
@ -140,7 +151,6 @@ func (q *quicConnection) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *quicConnection) acceptStream(ctx context.Context) error {
|
func (q *quicConnection) acceptStream(ctx context.Context) error {
|
||||||
defer q.Close()
|
|
||||||
for {
|
for {
|
||||||
quicStream, err := q.conn.AcceptStream(ctx)
|
quicStream, err := q.conn.AcceptStream(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -230,7 +240,7 @@ func (q *quicConnection) dispatchRequest(ctx context.Context, stream *rpcquic.Re
|
||||||
ConnIndex: q.connIndex,
|
ConnIndex: q.connIndex,
|
||||||
}), rwa.connectResponseSent
|
}), rwa.connectResponseSent
|
||||||
default:
|
default:
|
||||||
return errors.Errorf("unsupported error type: %s", request.Type), false
|
return fmt.Errorf("unsupported error type: %s", request.Type), false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -847,7 +847,7 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
|
||||||
&log,
|
&log,
|
||||||
}
|
}
|
||||||
|
|
||||||
tunnelConn, err := NewTunnelConnection(
|
tunnelConn := NewTunnelConnection(
|
||||||
ctx,
|
ctx,
|
||||||
conn,
|
conn,
|
||||||
index,
|
index,
|
||||||
|
|
@ -860,7 +860,6 @@ func testTunnelConnection(t *testing.T, serverAddr netip.AddrPort, index uint8)
|
||||||
0*time.Second,
|
0*time.Second,
|
||||||
&log,
|
&log,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
|
||||||
return tunnelConn, datagramConn
|
return tunnelConn, datagramConn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,24 +98,17 @@ func NewDatagramV2Connection(ctx context.Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *datagramV2Connection) Serve(ctx context.Context) error {
|
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
|
// If either goroutine from the errgroup returns at all (error or nil), we rely on its cancellation to make sure
|
||||||
// as fast as possible as well. Nil error means we want to exit for good (caller code won't retry serving this
|
// the other goroutines as well.
|
||||||
// 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)
|
|
||||||
errGroup, ctx := errgroup.WithContext(ctx)
|
errGroup, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
|
||||||
return d.sessionManager.Serve(ctx)
|
return d.sessionManager.Serve(ctx)
|
||||||
})
|
})
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
|
||||||
return d.datagramMuxer.ServeReceive(ctx)
|
return d.datagramMuxer.ServeReceive(ctx)
|
||||||
})
|
})
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
defer cancel()
|
|
||||||
return d.packetRouter.Serve(ctx)
|
return d.packetRouter.Serve(ctx)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ func (c User) AccountID() string {
|
||||||
return c.cert.AccountID
|
return c.cert.AccountID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c User) Endpoint() string {
|
||||||
|
return c.cert.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
func (c User) ZoneID() string {
|
func (c User) ZoneID() string {
|
||||||
return c.cert.ZoneID
|
return c.cert.ZoneID
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -10,7 +10,7 @@ require (
|
||||||
github.com/fortytw2/leaktest v1.3.0
|
github.com/fortytw2/leaktest v1.3.0
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/getsentry/sentry-go v0.16.0
|
github.com/getsentry/sentry-go v0.16.0
|
||||||
github.com/go-chi/chi/v5 v5.0.10
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-jose/go-jose/v4 v4.1.0
|
github.com/go-jose/go-jose/v4 v4.1.0
|
||||||
github.com/gobwas/ws v1.2.1
|
github.com/gobwas/ws v1.2.1
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -58,8 +58,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
|
||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ func (c *datagramConn) Serve(ctx context.Context) error {
|
||||||
// Monitor the context of cloudflared
|
// Monitor the context of cloudflared
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
// Monitor the context of the underlying connection
|
// Monitor the context of the underlying quic connection
|
||||||
case <-connCtx.Done():
|
case <-connCtx.Done():
|
||||||
return connCtx.Err()
|
return connCtx.Err()
|
||||||
// Monitor for any hard errors from reading the connection
|
// Monitor for any hard errors from reading the connection
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ func (s *Supervisor) Run(
|
||||||
if err == errEarlyShutdown {
|
if err == errEarlyShutdown {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
s.log.Logger().Error().Err(err).Msg("initial tunnel connection failed")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var tunnelsWaiting []int
|
var tunnelsWaiting []int
|
||||||
|
|
@ -154,6 +155,7 @@ func (s *Supervisor) Run(
|
||||||
// (note that this may also be caused by context cancellation)
|
// (note that this may also be caused by context cancellation)
|
||||||
case tunnelError := <-s.tunnelErrors:
|
case tunnelError := <-s.tunnelErrors:
|
||||||
tunnelsActive--
|
tunnelsActive--
|
||||||
|
s.log.ConnAwareLogger().Err(tunnelError.err).Int(connection.LogFieldConnIndex, tunnelError.index).Msg("Connection terminated")
|
||||||
if tunnelError.err != nil && !shuttingDown {
|
if tunnelError.err != nil && !shuttingDown {
|
||||||
switch tunnelError.err.(type) {
|
switch tunnelError.err.(type) {
|
||||||
case ReconnectSignal:
|
case ReconnectSignal:
|
||||||
|
|
@ -166,7 +168,6 @@ func (s *Supervisor) Run(
|
||||||
if _, retry := s.tunnelsProtocolFallback[tunnelError.index].GetMaxBackoffDuration(ctx); !retry {
|
if _, retry := s.tunnelsProtocolFallback[tunnelError.index].GetMaxBackoffDuration(ctx); !retry {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s.log.ConnAwareLogger().Err(tunnelError.err).Int(connection.LogFieldConnIndex, tunnelError.index).Msg("Connection terminated")
|
|
||||||
tunnelsWaiting = append(tunnelsWaiting, tunnelError.index)
|
tunnelsWaiting = append(tunnelsWaiting, tunnelError.index)
|
||||||
s.waitForNextTunnel(tunnelError.index)
|
s.waitForNextTunnel(tunnelError.index)
|
||||||
|
|
||||||
|
|
@ -285,7 +286,10 @@ func (s *Supervisor) startFirstTunnel(
|
||||||
*quic.IdleTimeoutError,
|
*quic.IdleTimeoutError,
|
||||||
*quic.ApplicationError,
|
*quic.ApplicationError,
|
||||||
edgediscovery.DialError,
|
edgediscovery.DialError,
|
||||||
*connection.EdgeQuicDialError:
|
*connection.EdgeQuicDialError,
|
||||||
|
*connection.ControlStreamError,
|
||||||
|
*connection.StreamListenerError,
|
||||||
|
*connection.DatagramManagerError:
|
||||||
// Try again for these types of errors
|
// Try again for these types of errors
|
||||||
default:
|
default:
|
||||||
// Uncaught errors should bail startup
|
// Uncaught errors should bail startup
|
||||||
|
|
@ -301,13 +305,9 @@ func (s *Supervisor) startTunnel(
|
||||||
index int,
|
index int,
|
||||||
connectedSignal *signal.Signal,
|
connectedSignal *signal.Signal,
|
||||||
) {
|
) {
|
||||||
var err error
|
|
||||||
defer func() {
|
|
||||||
s.tunnelErrors <- tunnelError{index: index, err: err}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// nolint: gosec
|
// 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 {
|
func (s *Supervisor) newConnectedTunnelSignal(index int) *signal.Signal {
|
||||||
|
|
|
||||||
|
|
@ -556,6 +556,7 @@ func (e *EdgeTunnelServer) serveQUIC(
|
||||||
pqMode := connOptions.FeatureSnapshot.PostQuantum
|
pqMode := connOptions.FeatureSnapshot.PostQuantum
|
||||||
curvePref, err := curvePreference(pqMode, fips.IsFipsEnabled(), tlsConfig.CurvePreferences)
|
curvePref, err := curvePreference(pqMode, fips.IsFipsEnabled(), tlsConfig.CurvePreferences)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
connLogger.ConnAwareLogger().Err(err).Msgf("failed to get curve preferences")
|
||||||
return err, true
|
return err, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -627,7 +628,7 @@ func (e *EdgeTunnelServer) serveQUIC(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap the [quic.Connection] as a TunnelConnection
|
// Wrap the [quic.Connection] as a TunnelConnection
|
||||||
tunnelConn, err := connection.NewTunnelConnection(
|
tunnelConn := connection.NewTunnelConnection(
|
||||||
ctx,
|
ctx,
|
||||||
conn,
|
conn,
|
||||||
connIndex,
|
connIndex,
|
||||||
|
|
@ -640,17 +641,13 @@ func (e *EdgeTunnelServer) serveQUIC(
|
||||||
e.config.GracePeriod,
|
e.config.GracePeriod,
|
||||||
connLogger.Logger(),
|
connLogger.Logger(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
connLogger.ConnAwareLogger().Err(err).Msgf("Failed to create new tunnel connection")
|
|
||||||
return err, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve the TunnelConnection
|
// Serve the TunnelConnection
|
||||||
errGroup, serveCtx := errgroup.WithContext(ctx)
|
errGroup, serveCtx := errgroup.WithContext(ctx)
|
||||||
errGroup.Go(func() error {
|
errGroup.Go(func() error {
|
||||||
err := tunnelConn.Serve(serveCtx)
|
err := tunnelConn.Serve(serveCtx)
|
||||||
if err != nil {
|
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
|
return err
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -185,18 +185,18 @@ func Init(version string) {
|
||||||
|
|
||||||
// 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, appInfo *AppInfo, autoClose bool, isFedramp bool, log *zerolog.Logger) (string, error) {
|
||||||
return getToken(appURL, appInfo, false, log)
|
return getToken(appURL, appInfo, false, autoClose, isFedramp, 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, appInfo *AppInfo, autoClose bool, isFedramp bool, log *zerolog.Logger) (string, error) {
|
||||||
return getToken(appURL, appInfo, true, log)
|
return getToken(appURL, appInfo, true, autoClose, isFedramp, 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, appInfo *AppInfo, useHostOnly bool, autoClose bool, isFedramp bool, log *zerolog.Logger) (string, error) {
|
||||||
if token, err := GetAppTokenIfExists(appInfo); token != "" && err == nil {
|
if token, err := GetAppTokenIfExists(appInfo); token != "" && err == nil {
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
@ -249,18 +249,19 @@ func getToken(appURL *url.URL, appInfo *AppInfo, useHostOnly bool, log *zerolog.
|
||||||
return appToken, nil
|
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,
|
// getTokensFromEdge will attempt to use the transfer service to retrieve an app and org token, save them to disk,
|
||||||
// and return the app token.
|
// 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.
|
// 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
|
// 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
|
// 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)
|
// 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 {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to run transfer service")
|
return "", errors.Wrap(err, "failed to run transfer service")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
baseStoreURL = "https://login.cloudflareaccess.org/"
|
baseStoreURL = "https://login.cloudflareaccess.org/"
|
||||||
|
fedStoreURL = "https://login.fed.cloudflareaccess.org/"
|
||||||
clientTimeout = time.Second * 60
|
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 "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
|
// the user to complete an action, while it long polls in the background waiting for an
|
||||||
// action to be completed to download the resource.
|
// 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")
|
encrypterClient, err := NewEncrypter("cloudflared_priv.pem", "cloudflared_pub.pem")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -45,8 +46,14 @@ func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string,
|
||||||
|
|
||||||
var resourceData []byte
|
var resourceData []byte
|
||||||
|
|
||||||
|
storeURL := baseStoreURL
|
||||||
|
|
||||||
|
if fedramp {
|
||||||
|
storeURL = fedStoreURL
|
||||||
|
}
|
||||||
|
|
||||||
if shouldEncrypt {
|
if shouldEncrypt {
|
||||||
buf, key, err := transferRequest(baseStoreURL+"transfer/"+encrypterClient.PublicKey(), log)
|
buf, key, err := transferRequest(storeURL+"transfer/"+encrypterClient.PublicKey(), log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +69,7 @@ func RunTransfer(transferURL *url.URL, appAUD, resourceName, key, value string,
|
||||||
|
|
||||||
resourceData = decrypted
|
resourceData = decrypted
|
||||||
} else {
|
} else {
|
||||||
buf, _, err := transferRequest(baseStoreURL+encrypterClient.PublicKey(), log)
|
buf, _, err := transferRequest(storeURL+encrypterClient.PublicKey(), log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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.
|
// 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.
|
// 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 := baseURL.Query()
|
||||||
q.Set(key, value)
|
q.Set(key, value)
|
||||||
q.Set("aud", appAUD)
|
q.Set("aud", appAUD)
|
||||||
|
|
@ -90,6 +97,10 @@ 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("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("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
|
q.Set("edge_token_transfer", "true") // use new LoginHelper service built on workers
|
||||||
|
if autoClose {
|
||||||
|
q.Set("close_interstitial", "true") // Automatically close the success window.
|
||||||
|
}
|
||||||
|
|
||||||
baseURL.RawQuery = q.Encode() // and this actual baseURL.
|
baseURL.RawQuery = q.Encode() // and this actual baseURL.
|
||||||
baseURL.Path = "cdn-cgi/access/cli"
|
baseURL.Path = "cdn-cgi/access/cli"
|
||||||
return baseURL.String(), nil
|
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
|
// ignore everything other than server errors as the resource
|
||||||
// may not exist until the user does the interaction
|
// may not exist until the user does the interaction
|
||||||
if resp.StatusCode >= 500 {
|
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 {
|
if resp.StatusCode != 200 {
|
||||||
log.Info().Msg("Waiting for login...")
|
log.Info().Msg("Waiting for login...")
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,19 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v5.0.12 (2024-02-16)
|
||||||
|
|
||||||
|
- History of changes: see https://github.com/go-chi/chi/compare/v5.0.11...v5.0.12
|
||||||
|
|
||||||
|
|
||||||
|
## v5.0.11 (2023-12-19)
|
||||||
|
|
||||||
|
- History of changes: see https://github.com/go-chi/chi/compare/v5.0.10...v5.0.11
|
||||||
|
|
||||||
|
|
||||||
## v5.0.10 (2023-07-13)
|
## v5.0.10 (2023-07-13)
|
||||||
|
|
||||||
- Fixed small edge case in tests of v5.0.9 for older Go versions
|
- Fixed small edge case in tests of v5.0.9 for older Go versions
|
||||||
- History of changes: see https://github.com/go-chi/chi/compare/v5.0.8...v5.0.10
|
- History of changes: see https://github.com/go-chi/chi/compare/v5.0.9...v5.0.10
|
||||||
|
|
||||||
|
|
||||||
## v5.0.9 (2023-07-13)
|
## v5.0.9 (2023-07-13)
|
||||||
|
|
@ -306,7 +316,7 @@ Cheers all, happy coding!
|
||||||
request-scoped values. We're very excited about the new context addition and are proud to
|
request-scoped values. We're very excited about the new context addition and are proud to
|
||||||
introduce chi v2, a minimal and powerful routing package for building large HTTP services,
|
introduce chi v2, a minimal and powerful routing package for building large HTTP services,
|
||||||
with zero external dependencies. Chi focuses on idiomatic design and encourages the use of
|
with zero external dependencies. Chi focuses on idiomatic design and encourages the use of
|
||||||
stdlib HTTP handlers and middlwares.
|
stdlib HTTP handlers and middlewares.
|
||||||
- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc`
|
- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc`
|
||||||
- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()`
|
- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()`
|
||||||
- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`,
|
- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
A typical workflow is:
|
A typical workflow is:
|
||||||
|
|
||||||
1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip]
|
1. [Fork the repository.][fork]
|
||||||
2. [Create a topic branch.][branch]
|
2. [Create a topic branch.][branch]
|
||||||
3. Add tests for your change.
|
3. Add tests for your change.
|
||||||
4. Run `go test`. If your tests pass, return to the step 3.
|
4. Run `go test`. If your tests pass, return to the step 3.
|
||||||
|
|
@ -24,8 +24,8 @@ A typical workflow is:
|
||||||
8. [Submit a pull request.][pull-req]
|
8. [Submit a pull request.][pull-req]
|
||||||
|
|
||||||
[go-install]: https://golang.org/doc/install
|
[go-install]: https://golang.org/doc/install
|
||||||
[go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html
|
[fork]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo
|
||||||
[fork]: https://help.github.com/articles/fork-a-repo
|
[branch]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches
|
||||||
[branch]: http://learn.github.com/p/branching.html
|
[git-help]: https://docs.github.com/en
|
||||||
[git-help]: https://guides.github.com
|
[pull-req]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests
|
||||||
[pull-req]: https://help.github.com/articles/using-pull-requests
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
`go get -u github.com/go-chi/chi/v5`
|
```sh
|
||||||
|
go get -u github.com/go-chi/chi/v5
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
@ -65,7 +67,7 @@ func main() {
|
||||||
|
|
||||||
**REST Preview:**
|
**REST Preview:**
|
||||||
|
|
||||||
Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs
|
Here is a little preview of what routing looks like with chi. Also take a look at the generated routing docs
|
||||||
in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in
|
in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in
|
||||||
Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)).
|
Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)).
|
||||||
|
|
||||||
|
|
@ -194,7 +196,7 @@ type Router interface {
|
||||||
// path, with a fresh middleware stack for the inline-Router.
|
// path, with a fresh middleware stack for the inline-Router.
|
||||||
Group(fn func(r Router)) Router
|
Group(fn func(r Router)) Router
|
||||||
|
|
||||||
// Route mounts a sub-Router along a `pattern`` string.
|
// Route mounts a sub-Router along a `pattern` string.
|
||||||
Route(pattern string, fn func(r Router)) Router
|
Route(pattern string, fn func(r Router)) Router
|
||||||
|
|
||||||
// Mount attaches another http.Handler along ./pattern/*
|
// Mount attaches another http.Handler along ./pattern/*
|
||||||
|
|
@ -354,6 +356,7 @@ with `net/http` can be used with chi's mux.
|
||||||
| [RouteHeaders] | Route handling for request headers |
|
| [RouteHeaders] | Route handling for request headers |
|
||||||
| [SetHeader] | Short-hand middleware to set a response header key/value |
|
| [SetHeader] | Short-hand middleware to set a response header key/value |
|
||||||
| [StripSlashes] | Strip slashes on routing paths |
|
| [StripSlashes] | Strip slashes on routing paths |
|
||||||
|
| [Sunset] | Sunset set Deprecation/Sunset header to response |
|
||||||
| [Throttle] | Puts a ceiling on the number of concurrent requests |
|
| [Throttle] | Puts a ceiling on the number of concurrent requests |
|
||||||
| [Timeout] | Signals to the request context when the timeout deadline is reached |
|
| [Timeout] | Signals to the request context when the timeout deadline is reached |
|
||||||
| [URLFormat] | Parse extension from url and put it on request context |
|
| [URLFormat] | Parse extension from url and put it on request context |
|
||||||
|
|
@ -380,6 +383,7 @@ with `net/http` can be used with chi's mux.
|
||||||
[RouteHeaders]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RouteHeaders
|
[RouteHeaders]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RouteHeaders
|
||||||
[SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader
|
[SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader
|
||||||
[StripSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#StripSlashes
|
[StripSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#StripSlashes
|
||||||
|
[Sunset]: https://pkg.go.dev/github.com/go-chi/chi/v5/middleware#Sunset
|
||||||
[Throttle]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Throttle
|
[Throttle]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Throttle
|
||||||
[ThrottleBacklog]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleBacklog
|
[ThrottleBacklog]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleBacklog
|
||||||
[ThrottleWithOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleWithOpts
|
[ThrottleWithOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleWithOpts
|
||||||
|
|
@ -467,7 +471,8 @@ how setting context on a request in Go works.
|
||||||
|
|
||||||
* Carl Jackson for https://github.com/zenazn/goji
|
* Carl Jackson for https://github.com/zenazn/goji
|
||||||
* Parts of chi's thinking comes from goji, and chi's middleware package
|
* Parts of chi's thinking comes from goji, and chi's middleware package
|
||||||
sources from goji.
|
sources from [goji](https://github.com/zenazn/goji/tree/master/web/middleware).
|
||||||
|
* Please see goji's [LICENSE](https://github.com/zenazn/goji/blob/master/LICENSE) (MIT)
|
||||||
* Armon Dadgar for https://github.com/armon/go-radix
|
* Armon Dadgar for https://github.com/armon/go-radix
|
||||||
* Contributions: [@VojtechVitek](https://github.com/VojtechVitek)
|
* Contributions: [@VojtechVitek](https://github.com/VojtechVitek)
|
||||||
|
|
||||||
|
|
@ -494,7 +499,7 @@ Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka)
|
||||||
|
|
||||||
Licensed under [MIT License](./LICENSE)
|
Licensed under [MIT License](./LICENSE)
|
||||||
|
|
||||||
[GoDoc]: https://pkg.go.dev/github.com/go-chi/chi?tab=versions
|
[GoDoc]: https://pkg.go.dev/github.com/go-chi/chi/v5
|
||||||
[GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg
|
[GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg
|
||||||
[Travis]: https://travis-ci.org/go-chi/chi
|
[Travis]: https://travis-ci.org/go-chi/chi
|
||||||
[Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master
|
[Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Reporting Security Issues
|
||||||
|
|
||||||
|
We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
|
||||||
|
|
||||||
|
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/go-chi/chi/security/advisories/new) tab.
|
||||||
|
|
@ -37,8 +37,7 @@
|
||||||
//
|
//
|
||||||
// A placeholder with a name followed by a colon allows a regular
|
// A placeholder with a name followed by a colon allows a regular
|
||||||
// expression match, for example {number:\\d+}. The regular expression
|
// expression match, for example {number:\\d+}. The regular expression
|
||||||
// syntax is Go's normal regexp RE2 syntax, except that regular expressions
|
// syntax is Go's normal regexp RE2 syntax, except that / will never be
|
||||||
// including { or } are not supported, and / will never be
|
|
||||||
// matched. An anonymous regexp pattern is allowed, using an empty string
|
// matched. An anonymous regexp pattern is allowed, using an empty string
|
||||||
// before the colon in the placeholder, such as {:\\d+}
|
// before the colon in the placeholder, such as {:\\d+}
|
||||||
//
|
//
|
||||||
|
|
@ -51,7 +50,7 @@
|
||||||
// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/"
|
// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/"
|
||||||
// "/user/{name}/info" matches "/user/jsmith/info"
|
// "/user/{name}/info" matches "/user/jsmith/info"
|
||||||
// "/page/*" matches "/page/intro/latest"
|
// "/page/*" matches "/page/intro/latest"
|
||||||
// "/page/{other}/index" also matches "/page/intro/latest"
|
// "/page/{other}/latest" also matches "/page/intro/latest"
|
||||||
// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01"
|
// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01"
|
||||||
package chi
|
package chi
|
||||||
|
|
||||||
|
|
@ -127,6 +126,10 @@ type Routes interface {
|
||||||
// the method/path - similar to routing a http request, but without
|
// the method/path - similar to routing a http request, but without
|
||||||
// executing the handler thereafter.
|
// executing the handler thereafter.
|
||||||
Match(rctx *Context, method, path string) bool
|
Match(rctx *Context, method, path string) bool
|
||||||
|
|
||||||
|
// Find searches the routing tree for the pattern that matches
|
||||||
|
// the method/path.
|
||||||
|
Find(rctx *Context, method, path string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Middlewares type is a slice of standard middleware handlers with methods
|
// Middlewares type is a slice of standard middleware handlers with methods
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ type Context struct {
|
||||||
URLParams RouteParams
|
URLParams RouteParams
|
||||||
|
|
||||||
// Route parameters matched for the current sub-router. It is
|
// Route parameters matched for the current sub-router. It is
|
||||||
// intentionally unexported so it cant be tampered.
|
// intentionally unexported so it can't be tampered.
|
||||||
routeParams RouteParams
|
routeParams RouteParams
|
||||||
|
|
||||||
// The endpoint routing pattern that matched the request URI path
|
// The endpoint routing pattern that matched the request URI path
|
||||||
|
|
@ -74,9 +74,8 @@ type Context struct {
|
||||||
// patterns across a stack of sub-routers.
|
// patterns across a stack of sub-routers.
|
||||||
RoutePatterns []string
|
RoutePatterns []string
|
||||||
|
|
||||||
// methodNotAllowed hint
|
|
||||||
methodNotAllowed bool
|
|
||||||
methodsAllowed []methodTyp // allowed methods in case of a 405
|
methodsAllowed []methodTyp // allowed methods in case of a 405
|
||||||
|
methodNotAllowed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset a routing context to its initial state.
|
// Reset a routing context to its initial state.
|
||||||
|
|
@ -92,6 +91,7 @@ func (x *Context) Reset() {
|
||||||
x.routeParams.Keys = x.routeParams.Keys[:0]
|
x.routeParams.Keys = x.routeParams.Keys[:0]
|
||||||
x.routeParams.Values = x.routeParams.Values[:0]
|
x.routeParams.Values = x.routeParams.Values[:0]
|
||||||
x.methodNotAllowed = false
|
x.methodNotAllowed = false
|
||||||
|
x.methodsAllowed = x.methodsAllowed[:0]
|
||||||
x.parentCtx = nil
|
x.parentCtx = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,7 +109,7 @@ func (x *Context) URLParam(key string) string {
|
||||||
// RoutePattern builds the routing pattern string for the particular
|
// RoutePattern builds the routing pattern string for the particular
|
||||||
// request, at the particular point during routing. This means, the value
|
// request, at the particular point during routing. This means, the value
|
||||||
// will change throughout the execution of a request in a router. That is
|
// will change throughout the execution of a request in a router. That is
|
||||||
// why its advised to only use this value after calling the next handler.
|
// why it's advised to only use this value after calling the next handler.
|
||||||
//
|
//
|
||||||
// For example,
|
// For example,
|
||||||
//
|
//
|
||||||
|
|
@ -121,10 +121,15 @@ func (x *Context) URLParam(key string) string {
|
||||||
// })
|
// })
|
||||||
// }
|
// }
|
||||||
func (x *Context) RoutePattern() string {
|
func (x *Context) RoutePattern() string {
|
||||||
|
if x == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
routePattern := strings.Join(x.RoutePatterns, "")
|
routePattern := strings.Join(x.RoutePatterns, "")
|
||||||
routePattern = replaceWildcards(routePattern)
|
routePattern = replaceWildcards(routePattern)
|
||||||
|
if routePattern != "/" {
|
||||||
routePattern = strings.TrimSuffix(routePattern, "//")
|
routePattern = strings.TrimSuffix(routePattern, "//")
|
||||||
routePattern = strings.TrimSuffix(routePattern, "/")
|
routePattern = strings.TrimSuffix(routePattern, "/")
|
||||||
|
}
|
||||||
return routePattern
|
return routePattern
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,12 +107,22 @@ func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) {
|
||||||
// Handle adds the route `pattern` that matches any http method to
|
// Handle adds the route `pattern` that matches any http method to
|
||||||
// execute the `handler` http.Handler.
|
// execute the `handler` http.Handler.
|
||||||
func (mx *Mux) Handle(pattern string, handler http.Handler) {
|
func (mx *Mux) Handle(pattern string, handler http.Handler) {
|
||||||
|
if method, rest, found := strings.Cut(pattern, " "); found {
|
||||||
|
mx.Method(method, rest, handler)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
mx.handle(mALL, pattern, handler)
|
mx.handle(mALL, pattern, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleFunc adds the route `pattern` that matches any http method to
|
// HandleFunc adds the route `pattern` that matches any http method to
|
||||||
// execute the `handlerFn` http.HandlerFunc.
|
// execute the `handlerFn` http.HandlerFunc.
|
||||||
func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) {
|
func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) {
|
||||||
|
if method, rest, found := strings.Cut(pattern, " "); found {
|
||||||
|
mx.Method(method, rest, handlerFn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
mx.handle(mALL, pattern, handlerFn)
|
mx.handle(mALL, pattern, handlerFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -250,20 +260,19 @@ func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
|
||||||
return im
|
return im
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group creates a new inline-Mux with a fresh middleware stack. It's useful
|
// Group creates a new inline-Mux with a copy of middleware stack. It's useful
|
||||||
// for a group of handlers along the same routing path that use an additional
|
// for a group of handlers along the same routing path that use an additional
|
||||||
// set of middlewares. See _examples/.
|
// set of middlewares. See _examples/.
|
||||||
func (mx *Mux) Group(fn func(r Router)) Router {
|
func (mx *Mux) Group(fn func(r Router)) Router {
|
||||||
im := mx.With().(*Mux)
|
im := mx.With()
|
||||||
if fn != nil {
|
if fn != nil {
|
||||||
fn(im)
|
fn(im)
|
||||||
}
|
}
|
||||||
return im
|
return im
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route creates a new Mux with a fresh middleware stack and mounts it
|
// Route creates a new Mux and mounts it along the `pattern` as a subrouter.
|
||||||
// along the `pattern` as a subrouter. Effectively, this is a short-hand
|
// Effectively, this is a short-hand call to Mount. See _examples/.
|
||||||
// call to Mount. See _examples/.
|
|
||||||
func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
|
func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
|
||||||
if fn == nil {
|
if fn == nil {
|
||||||
panic(fmt.Sprintf("chi: attempting to Route() a nil subrouter on '%s'", pattern))
|
panic(fmt.Sprintf("chi: attempting to Route() a nil subrouter on '%s'", pattern))
|
||||||
|
|
@ -352,19 +361,40 @@ func (mx *Mux) Middlewares() Middlewares {
|
||||||
// Note: the *Context state is updated during execution, so manage
|
// Note: the *Context state is updated during execution, so manage
|
||||||
// the state carefully or make a NewRouteContext().
|
// the state carefully or make a NewRouteContext().
|
||||||
func (mx *Mux) Match(rctx *Context, method, path string) bool {
|
func (mx *Mux) Match(rctx *Context, method, path string) bool {
|
||||||
|
return mx.Find(rctx, method, path) != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find searches the routing tree for the pattern that matches
|
||||||
|
// the method/path.
|
||||||
|
//
|
||||||
|
// Note: the *Context state is updated during execution, so manage
|
||||||
|
// the state carefully or make a NewRouteContext().
|
||||||
|
func (mx *Mux) Find(rctx *Context, method, path string) string {
|
||||||
m, ok := methodMap[method]
|
m, ok := methodMap[method]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
node, _, h := mx.tree.FindRoute(rctx, m, path)
|
node, _, _ := mx.tree.FindRoute(rctx, m, path)
|
||||||
|
pattern := rctx.routePattern
|
||||||
|
|
||||||
|
if node != nil {
|
||||||
|
if node.subroutes == nil {
|
||||||
|
e := node.endpoints[m]
|
||||||
|
return e.pattern
|
||||||
|
}
|
||||||
|
|
||||||
if node != nil && node.subroutes != nil {
|
|
||||||
rctx.RoutePath = mx.nextRoutePath(rctx)
|
rctx.RoutePath = mx.nextRoutePath(rctx)
|
||||||
return node.subroutes.Match(rctx, method, rctx.RoutePath)
|
subPattern := node.subroutes.Find(rctx, method, rctx.RoutePath)
|
||||||
|
if subPattern == "" {
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return h != nil
|
pattern = strings.TrimSuffix(pattern, "/*")
|
||||||
|
pattern += subPattern
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotFoundHandler returns the default Mux 404 responder whenever a route
|
// NotFoundHandler returns the default Mux 404 responder whenever a route
|
||||||
|
|
@ -441,6 +471,10 @@ func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Find the route
|
// Find the route
|
||||||
if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
|
if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
|
||||||
|
if supportsPathValue {
|
||||||
|
setPathValue(rctx, r)
|
||||||
|
}
|
||||||
|
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
//go:build go1.22 && !tinygo
|
||||||
|
// +build go1.22,!tinygo
|
||||||
|
|
||||||
|
|
||||||
|
package chi
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// supportsPathValue is true if the Go version is 1.22 and above.
|
||||||
|
//
|
||||||
|
// If this is true, `net/http.Request` has methods `SetPathValue` and `PathValue`.
|
||||||
|
const supportsPathValue = true
|
||||||
|
|
||||||
|
// setPathValue sets the path values in the Request value
|
||||||
|
// based on the provided request context.
|
||||||
|
func setPathValue(rctx *Context, r *http.Request) {
|
||||||
|
for i, key := range rctx.URLParams.Keys {
|
||||||
|
value := rctx.URLParams.Values[i]
|
||||||
|
r.SetPathValue(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
//go:build !go1.22 || tinygo
|
||||||
|
// +build !go1.22 tinygo
|
||||||
|
|
||||||
|
package chi
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// supportsPathValue is true if the Go version is 1.22 and above.
|
||||||
|
//
|
||||||
|
// If this is true, `net/http.Request` has methods `SetPathValue` and `PathValue`.
|
||||||
|
const supportsPathValue = false
|
||||||
|
|
||||||
|
// setPathValue sets the path values in the Request value
|
||||||
|
// based on the provided request context.
|
||||||
|
//
|
||||||
|
// setPathValue is only supported in Go 1.22 and above so
|
||||||
|
// this is just a blank function so that it compiles.
|
||||||
|
func setPathValue(rctx *Context, r *http.Request) {
|
||||||
|
}
|
||||||
|
|
@ -730,11 +730,9 @@ func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) {
|
||||||
tail = pattern[pe]
|
tail = pattern[pe]
|
||||||
}
|
}
|
||||||
|
|
||||||
var rexpat string
|
key, rexpat, isRegexp := strings.Cut(key, ":")
|
||||||
if idx := strings.Index(key, ":"); idx >= 0 {
|
if isRegexp {
|
||||||
nt = ntRegexp
|
nt = ntRegexp
|
||||||
rexpat = key[idx+1:]
|
|
||||||
key = key[:idx]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(rexpat) > 0 {
|
if len(rexpat) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,8 @@ github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage
|
||||||
github.com/getsentry/sentry-go/internal/ratelimit
|
github.com/getsentry/sentry-go/internal/ratelimit
|
||||||
# github.com/gin-gonic/gin v1.9.1
|
# github.com/gin-gonic/gin v1.9.1
|
||||||
## explicit; go 1.20
|
## explicit; go 1.20
|
||||||
# github.com/go-chi/chi/v5 v5.0.10
|
# github.com/go-chi/chi/v5 v5.2.2
|
||||||
## explicit; go 1.14
|
## explicit; go 1.20
|
||||||
github.com/go-chi/chi/v5
|
github.com/go-chi/chi/v5
|
||||||
# github.com/go-chi/cors v1.2.1
|
# github.com/go-chi/cors v1.2.1
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue