mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-07-27 01:09:57 +00:00
AUTH-1557: Short Lived Certs
This commit is contained in:
@@ -12,10 +12,17 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/token"
|
||||
"github.com/cloudflare/cloudflared/sshgen"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type StartOptions struct {
|
||||
OriginURL string
|
||||
Headers http.Header
|
||||
ShouldGenCert bool
|
||||
}
|
||||
|
||||
// StdinoutStream is empty struct for wrapping stdin/stdout
|
||||
// into a single ReadWriter
|
||||
type StdinoutStream struct {
|
||||
@@ -34,27 +41,27 @@ func (c *StdinoutStream) Write(p []byte) (int, error) {
|
||||
|
||||
// StartClient will copy the data from stdin/stdout over a WebSocket connection
|
||||
// to the edge (originURL)
|
||||
func StartClient(logger *logrus.Logger, originURL string, stream io.ReadWriter, headers http.Header) error {
|
||||
return serveStream(logger, originURL, stream, headers)
|
||||
func StartClient(logger *logrus.Logger, stream io.ReadWriter, options *StartOptions) error {
|
||||
return serveStream(logger, stream, options)
|
||||
}
|
||||
|
||||
// StartServer will setup a listener on a specified address/port and then
|
||||
// forward connections to the origin by calling `Serve()`.
|
||||
func StartServer(logger *logrus.Logger, address, originURL string, shutdownC <-chan struct{}, headers http.Header) error {
|
||||
func StartServer(logger *logrus.Logger, address string, shutdownC <-chan struct{}, options *StartOptions) error {
|
||||
listener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("failed to start forwarding server")
|
||||
return err
|
||||
}
|
||||
logger.Info("Started listening on ", address)
|
||||
return Serve(logger, listener, originURL, shutdownC, headers)
|
||||
return Serve(logger, listener, shutdownC, options)
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections on the specified net.Listener.
|
||||
// Each connection is handled in a new goroutine: its data is copied over a
|
||||
// WebSocket connection to the edge (originURL).
|
||||
// `Serve` always closes `listener`.
|
||||
func Serve(logger *logrus.Logger, listener net.Listener, originURL string, shutdownC <-chan struct{}, headers http.Header) error {
|
||||
func Serve(logger *logrus.Logger, listener net.Listener, shutdownC <-chan struct{}, options *StartOptions) error {
|
||||
defer listener.Close()
|
||||
for {
|
||||
select {
|
||||
@@ -65,22 +72,22 @@ func Serve(logger *logrus.Logger, listener net.Listener, originURL string, shutd
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go serveConnection(logger, conn, originURL, headers)
|
||||
go serveConnection(logger, conn, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// serveConnection handles connections for the Serve() call
|
||||
func serveConnection(logger *logrus.Logger, c net.Conn, originURL string, headers http.Header) {
|
||||
func serveConnection(logger *logrus.Logger, c net.Conn, options *StartOptions) {
|
||||
defer c.Close()
|
||||
serveStream(logger, originURL, c, headers)
|
||||
serveStream(logger, c, options)
|
||||
}
|
||||
|
||||
// serveStream will serve the data over the WebSocket stream
|
||||
func serveStream(logger *logrus.Logger, originURL string, conn io.ReadWriter, headers http.Header) error {
|
||||
wsConn, err := createWebsocketStream(originURL, headers)
|
||||
func serveStream(logger *logrus.Logger, conn io.ReadWriter, options *StartOptions) error {
|
||||
wsConn, err := createWebsocketStream(options)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("failed to connect to %s\n", originURL)
|
||||
logger.WithError(err).Errorf("failed to connect to %s\n", options.OriginURL)
|
||||
return err
|
||||
}
|
||||
defer wsConn.Close()
|
||||
@@ -93,12 +100,12 @@ func serveStream(logger *logrus.Logger, originURL string, conn io.ReadWriter, he
|
||||
// createWebsocketStream will create a WebSocket connection to stream data over
|
||||
// It also handles redirects from Access and will present that flow if
|
||||
// the token is not present on the request
|
||||
func createWebsocketStream(originURL string, headers http.Header) (*websocket.Conn, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, originURL, nil)
|
||||
func createWebsocketStream(options *StartOptions) (*websocket.Conn, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = headers
|
||||
req.Header = options.Headers
|
||||
|
||||
wsConn, resp, err := websocket.ClientConnect(req, nil)
|
||||
if err != nil && resp != nil && resp.StatusCode > 300 {
|
||||
@@ -109,11 +116,17 @@ func createWebsocketStream(originURL string, headers http.Header) (*websocket.Co
|
||||
if !strings.Contains(location.String(), "cdn-cgi/access/login") {
|
||||
return nil, errors.New("not an Access redirect")
|
||||
}
|
||||
req, err := buildAccessRequest(originURL)
|
||||
req, token, err := buildAccessRequest(options.OriginURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.ShouldGenCert {
|
||||
if err := sshgen.GenerateShortLivedCertificate(req.URL, token); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
wsConn, _, err = websocket.ClientConnect(req, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -126,24 +139,24 @@ func createWebsocketStream(originURL string, headers http.Header) (*websocket.Co
|
||||
}
|
||||
|
||||
// buildAccessRequest builds an HTTP request with the Access token set
|
||||
func buildAccessRequest(originURL string) (*http.Request, error) {
|
||||
func buildAccessRequest(originURL string) (*http.Request, string, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, originURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
token, err := token.FetchToken(req.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// We need to create a new request as FetchToken will modify req (boo mutable)
|
||||
// as it has to follow redirect on the API and such, so here we init a new one
|
||||
originRequest, err := http.NewRequest(http.MethodGet, originURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
originRequest.Header.Set("cf-access-token", token)
|
||||
|
||||
return originRequest, nil
|
||||
return originRequest, token, nil
|
||||
}
|
||||
|
@@ -48,7 +48,12 @@ func TestStartClient(t *testing.T) {
|
||||
defer ts.Close()
|
||||
|
||||
buf := newTestStream()
|
||||
err := StartClient(logger, "http://"+ts.Listener.Addr().String(), buf, nil)
|
||||
options := &StartOptions{
|
||||
OriginURL: "http://" + ts.Listener.Addr().String(),
|
||||
Headers: nil,
|
||||
ShouldGenCert: false,
|
||||
}
|
||||
err := StartClient(logger, buf, options)
|
||||
assert.NoError(t, err)
|
||||
buf.Write([]byte(message))
|
||||
|
||||
@@ -67,9 +72,14 @@ func TestStartServer(t *testing.T) {
|
||||
shutdownC := make(chan struct{})
|
||||
ts := newTestWebSocketServer()
|
||||
defer ts.Close()
|
||||
options := &StartOptions{
|
||||
OriginURL: "http://" + ts.Listener.Addr().String(),
|
||||
Headers: nil,
|
||||
ShouldGenCert: false,
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := Serve(logger, listener, "http://"+ts.Listener.Addr().String(), shutdownC, nil)
|
||||
err := Serve(logger, listener, shutdownC, options)
|
||||
if err != nil {
|
||||
t.Fatalf("Error running server: %v", err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user