summaryrefslogtreecommitdiff
path: root/cmd/commandfuncs.go
diff options
context:
space:
mode:
authorFrancis Lavoie <lavofr@gmail.com>2022-03-02 13:08:36 -0500
committerGitHub <noreply@github.com>2022-03-02 11:08:36 -0700
commitbbad6931e30a2e74b3f53fff797d1115cc9dd491 (patch)
tree4db78522ec9b352b2f27f2f3a11dfa1a314ee902 /cmd/commandfuncs.go
parent5bd96a6ac22849cd9fbbaae5285f0161e272b8e4 (diff)
pki: Implement API endpoints for certs and `caddy trust` (#4443)
* admin: Implement /pki/certificates/<id> API * pki: Lower "skip_install_trust" log level to INFO See https://github.com/caddyserver/caddy/issues/4058#issuecomment-976132935 It's not necessary to warn about this, because this was an option explicitly configured by the user. Still useful to log, but we don't need to be so loud about it. * cmd: Export functions needed for PKI app, return API response to caller * pki: Rewrite `caddy trust` command to use new admin endpoint instead * pki: Rewrite `caddy untrust` command to support using admin endpoint * Refactor cmd and pki packages for determining admin API endpoint
Diffstat (limited to 'cmd/commandfuncs.go')
-rw-r--r--cmd/commandfuncs.go112
1 files changed, 74 insertions, 38 deletions
diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go
index ec56ab9..d308aeb 100644
--- a/cmd/commandfuncs.go
+++ b/cmd/commandfuncs.go
@@ -202,7 +202,7 @@ func cmdRun(fl Flags) (int, error) {
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
var configFile string
if !runCmdResumeFlag {
- config, configFile, err = loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
+ config, configFile, err = LoadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -275,25 +275,33 @@ func cmdRun(fl Flags) (int, error) {
}
func cmdStop(fl Flags) (int, error) {
- stopCmdAddrFlag := fl.String("address")
+ addrFlag := fl.String("address")
+ configFlag := fl.String("config")
+ configAdapterFlag := fl.String("adapter")
- err := apiRequest(stopCmdAddrFlag, http.MethodPost, "/stop", nil, nil)
+ adminAddr, err := DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
+ }
+
+ resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/stop", nil, nil)
if err != nil {
caddy.Log().Warn("failed using API to stop instance", zap.Error(err))
return caddy.ExitCodeFailedStartup, err
}
+ defer resp.Body.Close()
return caddy.ExitCodeSuccess, nil
}
func cmdReload(fl Flags) (int, error) {
- reloadCmdConfigFlag := fl.String("config")
- reloadCmdConfigAdapterFlag := fl.String("adapter")
- reloadCmdAddrFlag := fl.String("address")
- reloadCmdForceFlag := fl.Bool("force")
+ configFlag := fl.String("config")
+ configAdapterFlag := fl.String("adapter")
+ addrFlag := fl.String("address")
+ forceFlag := fl.Bool("force")
// get the config in caddy's native format
- config, configFile, err := loadConfig(reloadCmdConfigFlag, reloadCmdConfigAdapterFlag)
+ config, configFile, err := LoadConfig(configFlag, configAdapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -301,30 +309,22 @@ func cmdReload(fl Flags) (int, error) {
return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load")
}
- // get the address of the admin listener; use flag if specified
- adminAddr := reloadCmdAddrFlag
- if adminAddr == "" && len(config) > 0 {
- var tmpStruct struct {
- Admin caddy.AdminConfig `json:"admin"`
- }
- err = json.Unmarshal(config, &tmpStruct)
- if err != nil {
- return caddy.ExitCodeFailedStartup,
- fmt.Errorf("unmarshaling admin listener address from config: %v", err)
- }
- adminAddr = tmpStruct.Admin.Listen
+ adminAddr, err := DetermineAdminAPIAddress(addrFlag, configFlag, configAdapterFlag)
+ if err != nil {
+ return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
}
// optionally force a config reload
headers := make(http.Header)
- if reloadCmdForceFlag {
+ if forceFlag {
headers.Set("Cache-Control", "must-revalidate")
}
- err = apiRequest(adminAddr, http.MethodPost, "/load", headers, bytes.NewReader(config))
+ resp, err := AdminAPIRequest(adminAddr, http.MethodPost, "/load", headers, bytes.NewReader(config))
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("sending configuration to instance: %v", err)
}
+ defer resp.Body.Close()
return caddy.ExitCodeSuccess, nil
}
@@ -518,7 +518,7 @@ func cmdValidateConfig(fl Flags) (int, error) {
validateCmdConfigFlag := fl.String("config")
validateCmdAdapterFlag := fl.String("adapter")
- input, _, err := loadConfig(validateCmdConfigFlag, validateCmdAdapterFlag)
+ input, _, err := LoadConfig(validateCmdConfigFlag, validateCmdAdapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
@@ -640,17 +640,15 @@ commands:
return caddy.ExitCodeSuccess, nil
}
-// 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, headers http.Header, body io.Reader) error {
- // parse the admin address
- if adminAddr == "" {
- adminAddr = caddy.DefaultAdminListen
- }
+// AdminAPIRequest makes an API request according to the CLI flags given,
+// with the given HTTP method and request URI. If body is non-nil, it will
+// be assumed to be Content-Type application/json. The caller should close
+// the response body. Should only be used by Caddy CLI commands which
+// need to interact with a running instance of Caddy via the admin API.
+func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io.Reader) (*http.Response, error) {
parsedAddr, err := caddy.ParseNetworkAddress(adminAddr)
if err != nil || parsedAddr.PortRangeSize() > 1 {
- return fmt.Errorf("invalid admin address %s: %v", adminAddr, err)
+ return nil, fmt.Errorf("invalid admin address %s: %v", adminAddr, err)
}
origin := parsedAddr.JoinHostPort(0)
if parsedAddr.IsUnixNetwork() {
@@ -660,7 +658,7 @@ func apiRequest(adminAddr, method, uri string, headers http.Header, body io.Read
// form the request
req, err := http.NewRequest(method, "http://"+origin+uri, body)
if err != nil {
- return fmt.Errorf("making request: %v", err)
+ return nil, fmt.Errorf("making request: %v", err)
}
if parsedAddr.IsUnixNetwork() {
// When listening on a unix socket, the admin endpoint doesn't
@@ -700,20 +698,58 @@ func apiRequest(adminAddr, method, uri string, headers http.Header, body io.Read
resp, err := client.Do(req)
if err != nil {
- return fmt.Errorf("performing request: %v", err)
+ return nil, fmt.Errorf("performing request: %v", err)
}
- defer resp.Body.Close()
// if it didn't work, let the user know
if resp.StatusCode >= 400 {
respBody, err := io.ReadAll(io.LimitReader(resp.Body, 1024*10))
if err != nil {
- return fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
+ return nil, fmt.Errorf("HTTP %d: reading error message: %v", resp.StatusCode, err)
+ }
+ return nil, fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
+ }
+
+ return resp, nil
+}
+
+// DetermineAdminAPIAddress determines which admin API endpoint address should
+// be used based on the inputs. By priority: if `address` is specified, then
+// it is returned; if `configFile` (and `configAdapter`) are specified, then that
+// config will be loaded to find the admin address; otherwise, the default
+// admin listen address will be returned.
+func DetermineAdminAPIAddress(address, configFile, configAdapter string) (string, error) {
+ // Prefer the address if specified and non-empty
+ if address != "" {
+ return address, nil
+ }
+
+ // Try to load the config from file if specified, with the given adapter name
+ if configFile != "" {
+ // get the config in caddy's native format
+ config, loadedConfigFile, err := LoadConfig(configFile, configAdapter)
+ if err != nil {
+ return "", err
+ }
+ if loadedConfigFile == "" {
+ return "", fmt.Errorf("no config file to load")
+ }
+
+ // get the address of the admin listener
+ if len(config) > 0 {
+ var tmpStruct struct {
+ Admin caddy.AdminConfig `json:"admin"`
+ }
+ err = json.Unmarshal(config, &tmpStruct)
+ if err != nil {
+ return "", fmt.Errorf("unmarshaling admin listener address from config: %v", err)
+ }
+ return tmpStruct.Admin.Listen, nil
}
- return fmt.Errorf("caddy responded with error: HTTP %d: %s", resp.StatusCode, respBody)
}
- return nil
+ // Fallback to the default listen address otherwise
+ return caddy.DefaultAdminListen, nil
}
type moduleInfo struct {