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-07-03 TUN-9540: Use numeric user id for Dockerfiles
|
||||
- 2025-07-01 TUN-9161: Remove P256Kyber768Draft00PQKex curve from nonFips curve preferences
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ type StartOptions struct {
|
|||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 != "" {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -21,9 +22,8 @@ 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/"
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -155,10 +155,12 @@ func (sc *subcommandContext) create(name string, credentialsFilePath string, sec
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tunnelCredentials := connection.Credentials{
|
||||
AccountTag: credential.AccountID(),
|
||||
TunnelSecret: tunnelSecret,
|
||||
TunnelID: tunnel.ID,
|
||||
Endpoint: credential.Endpoint(),
|
||||
}
|
||||
usedCertPath := false
|
||||
if credentialsFilePath == "" {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
func (e *StreamListenerError) Error() string {
|
||||
return "accept stream listener encountered a failure while serving"
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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)).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ func (c User) AccountID() string {
|
|||
return c.cert.AccountID
|
||||
}
|
||||
|
||||
func (c User) Endpoint() string {
|
||||
return c.cert.Endpoint
|
||||
}
|
||||
|
||||
func (c User) ZoneID() string {
|
||||
return c.cert.ZoneID
|
||||
}
|
||||
|
|
|
|||
2
go.mod
2
go.mod
|
|
@ -10,7 +10,7 @@ require (
|
|||
github.com/fortytw2/leaktest v1.3.0
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
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-jose/go-jose/v4 v4.1.0
|
||||
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.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
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.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||
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/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,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("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
|
||||
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...")
|
||||
|
|
|
|||
|
|
@ -1,9 +1,19 @@
|
|||
# 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)
|
||||
|
||||
- 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)
|
||||
|
|
@ -306,7 +316,7 @@ Cheers all, happy coding!
|
|||
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,
|
||||
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 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`,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
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]
|
||||
3. Add tests for your change.
|
||||
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]
|
||||
|
||||
[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://help.github.com/articles/fork-a-repo
|
||||
[branch]: http://learn.github.com/p/branching.html
|
||||
[git-help]: https://guides.github.com
|
||||
[pull-req]: https://help.github.com/articles/using-pull-requests
|
||||
[fork]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/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
|
||||
[git-help]: https://docs.github.com/en
|
||||
[pull-req]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!
|
|||
|
||||
## Install
|
||||
|
||||
`go get -u github.com/go-chi/chi/v5`
|
||||
```sh
|
||||
go get -u github.com/go-chi/chi/v5
|
||||
```
|
||||
|
||||
|
||||
## Features
|
||||
|
|
@ -65,7 +67,7 @@ func main() {
|
|||
|
||||
**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
|
||||
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.
|
||||
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
|
||||
|
||||
// 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 |
|
||||
| [SetHeader] | Short-hand middleware to set a response header key/value |
|
||||
| [StripSlashes] | Strip slashes on routing paths |
|
||||
| [Sunset] | Sunset set Deprecation/Sunset header to response |
|
||||
| [Throttle] | Puts a ceiling on the number of concurrent requests |
|
||||
| [Timeout] | Signals to the request context when the timeout deadline is reached |
|
||||
| [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
|
||||
[SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader
|
||||
[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
|
||||
[ThrottleBacklog]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleBacklog
|
||||
[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
|
||||
* 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
|
||||
* 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)
|
||||
|
||||
[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
|
||||
[Travis]: https://travis-ci.org/go-chi/chi
|
||||
[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
|
||||
// expression match, for example {number:\\d+}. The regular expression
|
||||
// syntax is Go's normal regexp RE2 syntax, except that regular expressions
|
||||
// including { or } are not supported, and / will never be
|
||||
// syntax is Go's normal regexp RE2 syntax, except that / will never be
|
||||
// matched. An anonymous regexp pattern is allowed, using an empty string
|
||||
// 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}/info" matches "/user/jsmith/info"
|
||||
// "/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"
|
||||
package chi
|
||||
|
||||
|
|
@ -127,6 +126,10 @@ type Routes interface {
|
|||
// the method/path - similar to routing a http request, but without
|
||||
// executing the handler thereafter.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ type Context struct {
|
|||
URLParams RouteParams
|
||||
|
||||
// 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
|
||||
|
||||
// The endpoint routing pattern that matched the request URI path
|
||||
|
|
@ -74,9 +74,8 @@ type Context struct {
|
|||
// patterns across a stack of sub-routers.
|
||||
RoutePatterns []string
|
||||
|
||||
// methodNotAllowed hint
|
||||
methodNotAllowed bool
|
||||
methodsAllowed []methodTyp // allowed methods in case of a 405
|
||||
methodNotAllowed bool
|
||||
}
|
||||
|
||||
// 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.Values = x.routeParams.Values[:0]
|
||||
x.methodNotAllowed = false
|
||||
x.methodsAllowed = x.methodsAllowed[:0]
|
||||
x.parentCtx = nil
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ func (x *Context) URLParam(key string) string {
|
|||
// RoutePattern builds the routing pattern string for the particular
|
||||
// request, at the particular point during routing. This means, the value
|
||||
// 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,
|
||||
//
|
||||
|
|
@ -121,10 +121,15 @@ func (x *Context) URLParam(key string) string {
|
|||
// })
|
||||
// }
|
||||
func (x *Context) RoutePattern() string {
|
||||
if x == nil {
|
||||
return ""
|
||||
}
|
||||
routePattern := strings.Join(x.RoutePatterns, "")
|
||||
routePattern = replaceWildcards(routePattern)
|
||||
if routePattern != "/" {
|
||||
routePattern = strings.TrimSuffix(routePattern, "//")
|
||||
routePattern = strings.TrimSuffix(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
|
||||
// execute the `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)
|
||||
}
|
||||
|
||||
// HandleFunc adds the route `pattern` that matches any http method to
|
||||
// execute the `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)
|
||||
}
|
||||
|
||||
|
|
@ -250,20 +260,19 @@ func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
|
|||
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
|
||||
// set of middlewares. See _examples/.
|
||||
func (mx *Mux) Group(fn func(r Router)) Router {
|
||||
im := mx.With().(*Mux)
|
||||
im := mx.With()
|
||||
if fn != nil {
|
||||
fn(im)
|
||||
}
|
||||
return im
|
||||
}
|
||||
|
||||
// Route creates a new Mux with a fresh middleware stack and mounts it
|
||||
// along the `pattern` as a subrouter. Effectively, this is a short-hand
|
||||
// call to Mount. See _examples/.
|
||||
// Route creates a new Mux and mounts it along the `pattern` as a subrouter.
|
||||
// Effectively, this is a short-hand call to Mount. See _examples/.
|
||||
func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
|
||||
if fn == nil {
|
||||
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
|
||||
// the state carefully or make a NewRouteContext().
|
||||
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]
|
||||
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)
|
||||
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
|
||||
|
|
@ -441,6 +471,10 @@ func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Find the route
|
||||
if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
|
||||
if supportsPathValue {
|
||||
setPathValue(rctx, r)
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
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]
|
||||
}
|
||||
|
||||
var rexpat string
|
||||
if idx := strings.Index(key, ":"); idx >= 0 {
|
||||
key, rexpat, isRegexp := strings.Cut(key, ":")
|
||||
if isRegexp {
|
||||
nt = ntRegexp
|
||||
rexpat = key[idx+1:]
|
||||
key = key[:idx]
|
||||
}
|
||||
|
||||
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/gin-gonic/gin v1.9.1
|
||||
## explicit; go 1.20
|
||||
# github.com/go-chi/chi/v5 v5.0.10
|
||||
## explicit; go 1.14
|
||||
# github.com/go-chi/chi/v5 v5.2.2
|
||||
## explicit; go 1.20
|
||||
github.com/go-chi/chi/v5
|
||||
# github.com/go-chi/cors v1.2.1
|
||||
## explicit; go 1.14
|
||||
|
|
|
|||
Loading…
Reference in New Issue