Skip to content

Commit

Permalink
cmd: Support admin endpoint on unix socket (#3320)
Browse files Browse the repository at this point in the history
  • Loading branch information
mholt authored May 29, 2020
1 parent 6c051cd commit 996af09
Showing 1 changed file with 61 additions and 33 deletions.
94 changes: 61 additions & 33 deletions cmd/commandfuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package caddycmd

import (
"bytes"
"context"
"crypto/rand"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -276,24 +277,9 @@ func cmdRun(fl Flags) (int, error) {
func cmdStop(fl Flags) (int, error) {
stopCmdAddrFlag := fl.String("address")

adminAddr := caddy.DefaultAdminListen
if stopCmdAddrFlag != "" {
adminAddr = stopCmdAddrFlag
}
stopEndpoint := fmt.Sprintf("http://%s/stop", adminAddr)

req, err := http.NewRequest(http.MethodPost, stopEndpoint, nil)
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("making request: %v", err)
}
req.Header.Set("Origin", adminAddr)

err = apiRequest(req)
err := apiRequest(stopCmdAddrFlag, http.MethodPost, "/stop", nil)
if err != nil {
caddy.Log().Warn("failed using API to stop instance",
zap.String("endpoint", stopEndpoint),
zap.Error(err),
)
caddy.Log().Warn("failed using API to stop instance", zap.Error(err))
return caddy.ExitCodeFailedStartup, err
}

Expand All @@ -314,7 +300,7 @@ func cmdReload(fl Flags) (int, error) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load")
}

// get the address of the admin listener and craft endpoint URL
// get the address of the admin listener; use flag if specified
adminAddr := reloadCmdAddrFlag
if adminAddr == "" && len(config) > 0 {
var tmpStruct struct {
Expand All @@ -327,20 +313,8 @@ func cmdReload(fl Flags) (int, error) {
}
adminAddr = tmpStruct.Admin.Listen
}
if adminAddr == "" {
adminAddr = caddy.DefaultAdminListen
}
loadEndpoint := fmt.Sprintf("http://%s/load", adminAddr)

// prepare the request to update the configuration
req, err := http.NewRequest(http.MethodPost, loadEndpoint, bytes.NewReader(config))
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("making request: %v", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Origin", adminAddr)

err = apiRequest(req)
err = apiRequest(adminAddr, http.MethodPost, "/load", bytes.NewReader(config))
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("sending configuration to instance: %v", err)
}
Expand Down Expand Up @@ -645,8 +619,62 @@ commands:
return caddy.ExitCodeSuccess, nil
}

func apiRequest(req *http.Request) error {
resp, err := http.DefaultClient.Do(req)
// apiRequest makes an API request to the endpoint adminAddr with the
// given HTTP method and request URI. If body is non-nil, it will be
// assumed to be Content-Type application/json.
func apiRequest(adminAddr, method, uri string, body io.Reader) error {
// parse the admin address
if adminAddr == "" {
adminAddr = caddy.DefaultAdminListen
}
parsedAddr, err := caddy.ParseNetworkAddress(adminAddr)
if err != nil || parsedAddr.PortRangeSize() > 1 {
return fmt.Errorf("invalid admin address %s: %v", adminAddr, err)
}
origin := parsedAddr.JoinHostPort(0)
if parsedAddr.IsUnixNetwork() {
origin = "unixsocket" // hack so that http.NewRequest() is happy
}

// form the request
req, err := http.NewRequest(method, "http://"+origin+uri, body)
if err != nil {
return fmt.Errorf("making request: %v", err)
}
if parsedAddr.IsUnixNetwork() {
// When listening on a unix socket, the admin endpoint doesn't
// accept any Host header because there is no host:port for
// a unix socket's address. The server's host check is fairly
// strict for security reasons, so we don't allow just any
// Host header. For unix sockets, the Host header must be
// empty. Unfortunately, Go makes it impossible to make HTTP
// requests with an empty Host header... except with this one
// weird trick. (Hopefully they don't fix it. It's already
// hard enough to use HTTP over unix sockets.)
//
// An equivalent curl command would be something like:
// $ curl --unix-socket caddy.sock http:/:$REQUEST_URI

This comment has been minimized.

Copy link
@francislavoie

francislavoie May 29, 2020

Member

Comment is strange:

// $ curl --unix-socket caddy.sock http:/:$REQUEST_URI

Should be

// $ curl --unix-socket caddy.sock http://$REQUEST_URI

I think.

This comment has been minimized.

Copy link
@mholt

mholt May 29, 2020

Author Member

Nope, one slash. The comment is correct.

This comment has been minimized.

Copy link
@francislavoie

francislavoie May 29, 2020

Member

Wat

req.URL.Host = " "
req.Host = ""
} else {
req.Header.Set("Origin", origin)
}
if body != nil {
req.Header.Set("Content-Type", "application/json")
}

// make an HTTP client that dials our network type, since admin
// endpoints aren't always TCP, which is what the default transport
// expects; reuse is not of particular concern here
client := http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial(parsedAddr.Network, parsedAddr.JoinHostPort(0))
},
},
}

resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("performing request: %v", err)
}
Expand Down

0 comments on commit 996af09

Please sign in to comment.