@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 235 lines 8.3 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddypki
16
17 import (
18 "crypto/x509"
19 "encoding/json"
20 "encoding/pem"
21 "fmt"
22 "net/http"
23 "os"
24 "path"
25
26 "github.com/smallstep/truststore"
27 "github.com/spf13/cobra"
28
29 caddycmd "github.com/caddyserver/caddy/v2/cmd"
30
31 "github.com/caddyserver/caddy/v2"
32 )
33
34 func init() {
35 caddycmd.RegisterCommand(caddycmd.Command{
36 Name: "trust",
37 Usage: "[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]",
38 Short: "Installs a CA certificate into local trust stores",
39 Long: `
40 Adds a root certificate into the local trust stores.
41
42 Caddy will attempt to install its root certificates into the local
43 trust stores automatically when they are first generated, but it
44 might fail if Caddy doesn't have the appropriate permissions to
45 write to the trust store. This command is necessary to pre-install
46 the certificates before using them, if the server process runs as an
47 unprivileged user (such as via systemd).
48
49 By default, this command installs the root certificate for Caddy's
50 default CA (i.e. 'local'). You may specify the ID of another CA
51 with the --ca flag.
52
53 This command will attempt to connect to Caddy's admin API running at
54 '` + caddy.DefaultAdminListen + `' to fetch the root certificate. You may
55 explicitly specify the --address, or use the --config flag to load
56 the admin address from your config, if not using the default.`,
57 CobraFunc: func(cmd *cobra.Command) {
58 cmd.Flags().StringP("ca", "", "", "The ID of the CA to trust (defaults to 'local')")
59 cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)")
60 cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)")
61 cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)")
62 cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdTrust)
63 },
64 })
65
66 caddycmd.RegisterCommand(caddycmd.Command{
67 Name: "untrust",
68 Usage: "[--cert <path>] | [[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]]",
69 Short: "Untrusts a locally-trusted CA certificate",
70 Long: `
71 Untrusts a root certificate from the local trust store(s).
72
73 This command uninstalls trust; it does not necessarily delete the
74 root certificate from trust stores entirely. Thus, repeatedly
75 trusting and untrusting new certificates can fill up trust databases.
76
77 This command does not delete or modify certificate files from Caddy's
78 configured storage.
79
80 This command can be used in one of two ways. Either by specifying
81 which certificate to untrust by a direct path to the certificate
82 file with the --cert flag, or by fetching the root certificate for
83 the CA from the admin API (default behaviour).
84
85 If the admin API is used, then the CA defaults to 'local'. You may
86 specify the ID of another CA with the --ca flag. By default, this
87 will attempt to connect to the Caddy's admin API running at
88 '` + caddy.DefaultAdminListen + `' to fetch the root certificate.
89 You may explicitly specify the --address, or use the --config flag
90 to load the admin address from your config, if not using the default.`,
91 CobraFunc: func(cmd *cobra.Command) {
92 cmd.Flags().StringP("cert", "p", "", "The path to the CA certificate to untrust")
93 cmd.Flags().StringP("ca", "", "", "The ID of the CA to untrust (defaults to 'local')")
94 cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)")
95 cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)")
96 cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)")
97 cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdUntrust)
98 },
99 })
100 }
101
102 func cmdTrust(fl caddycmd.Flags) (int, error) {
103 caID := fl.String("ca")
104 addrFlag := fl.String("address")
105 configFlag := fl.String("config")
106 configAdapterFlag := fl.String("adapter")
107
108 // Prepare the URI to the admin endpoint
109 if caID == "" {
110 caID = DefaultCAID
111 }
112
113 // Determine where we're sending the request to get the CA info
114 adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag)
115 if err != nil {
116 return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
117 }
118
119 // Fetch the root cert from the admin API
120 rootCert, err := rootCertFromAdmin(adminAddr, caID)
121 if err != nil {
122 return caddy.ExitCodeFailedStartup, err
123 }
124
125 // Set up the CA struct; we only need to fill in the root
126 // because we're only using it to make use of the installRoot()
127 // function. Also needs a logger for warnings, and a "cert path"
128 // for the root cert; since we're loading from the API and we
129 // don't know the actual storage path via this flow, we'll just
130 // pass through the admin API address instead.
131 ca := CA{
132 log: caddy.Log(),
133 root: rootCert,
134 rootCertPath: adminAddr + path.Join(adminPKIEndpointBase, "ca", caID),
135 }
136
137 // Install the cert!
138 err = ca.installRoot()
139 if err != nil {
140 return caddy.ExitCodeFailedStartup, err
141 }
142
143 return caddy.ExitCodeSuccess, nil
144 }
145
146 func cmdUntrust(fl caddycmd.Flags) (int, error) {
147 certFile := fl.String("cert")
148 caID := fl.String("ca")
149 addrFlag := fl.String("address")
150 configFlag := fl.String("config")
151 configAdapterFlag := fl.String("adapter")
152
153 if certFile != "" && (caID != "" || addrFlag != "" || configFlag != "") {
154 return caddy.ExitCodeFailedStartup, fmt.Errorf("conflicting command line arguments, cannot use --cert with other flags")
155 }
156
157 // If a file was specified, try to uninstall the cert matching that file
158 if certFile != "" {
159 // Sanity check, make sure cert file exists first
160 _, err := os.Stat(certFile)
161 if err != nil {
162 return caddy.ExitCodeFailedStartup, fmt.Errorf("accessing certificate file: %v", err)
163 }
164
165 // Uninstall the file!
166 err = truststore.UninstallFile(certFile,
167 truststore.WithDebug(),
168 truststore.WithFirefox(),
169 truststore.WithJava())
170 if err != nil {
171 return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err)
172 }
173
174 return caddy.ExitCodeSuccess, nil
175 }
176
177 // Prepare the URI to the admin endpoint
178 if caID == "" {
179 caID = DefaultCAID
180 }
181
182 // Determine where we're sending the request to get the CA info
183 adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag)
184 if err != nil {
185 return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
186 }
187
188 // Fetch the root cert from the admin API
189 rootCert, err := rootCertFromAdmin(adminAddr, caID)
190 if err != nil {
191 return caddy.ExitCodeFailedStartup, err
192 }
193
194 // Uninstall the cert!
195 err = truststore.Uninstall(rootCert,
196 truststore.WithDebug(),
197 truststore.WithFirefox(),
198 truststore.WithJava())
199 if err != nil {
200 return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err)
201 }
202
203 return caddy.ExitCodeSuccess, nil
204 }
205
206 // rootCertFromAdmin makes the API request to fetch the root certificate for the named CA via admin API.
207 func rootCertFromAdmin(adminAddr string, caID string) (*x509.Certificate, error) {
208 uri := path.Join(adminPKIEndpointBase, "ca", caID)
209
210 // Make the request to fetch the CA info
211 resp, err := caddycmd.AdminAPIRequest(adminAddr, http.MethodGet, uri, make(http.Header), nil)
212 if err != nil {
213 return nil, fmt.Errorf("requesting CA info: %v", err)
214 }
215 defer resp.Body.Close()
216
217 // Decode the response
218 caInfo := new(caInfo)
219 err = json.NewDecoder(resp.Body).Decode(caInfo)
220 if err != nil {
221 return nil, fmt.Errorf("failed to decode JSON response: %v", err)
222 }
223
224 // Decode the root cert
225 rootBlock, _ := pem.Decode([]byte(caInfo.RootCert))
226 if rootBlock == nil {
227 return nil, fmt.Errorf("failed to decode root certificate: %v", err)
228 }
229 rootCert, err := x509.ParseCertificate(rootBlock.Bytes)
230 if err != nil {
231 return nil, fmt.Errorf("failed to parse root certificate: %v", err)
232 }
233
234 return rootCert, nil
235 }