mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-31 10:37:24 -04:00 
			
		
		
		
	* use gofmput to format code * use gci to format imports * reconfigure gci * linter autofixes * rearrange imports a little * export GOOS=windows golangci-lint run ./... --fix
		
			
				
	
	
		
			236 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2015 Matthew Holt and The Caddy Authors
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| //     http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package caddypki
 | |
| 
 | |
| import (
 | |
| 	"crypto/x509"
 | |
| 	"encoding/json"
 | |
| 	"encoding/pem"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"os"
 | |
| 	"path"
 | |
| 
 | |
| 	"github.com/smallstep/truststore"
 | |
| 	"github.com/spf13/cobra"
 | |
| 
 | |
| 	caddycmd "github.com/caddyserver/caddy/v2/cmd"
 | |
| 
 | |
| 	"github.com/caddyserver/caddy/v2"
 | |
| )
 | |
| 
 | |
| func init() {
 | |
| 	caddycmd.RegisterCommand(caddycmd.Command{
 | |
| 		Name:  "trust",
 | |
| 		Usage: "[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]",
 | |
| 		Short: "Installs a CA certificate into local trust stores",
 | |
| 		Long: `
 | |
| Adds a root certificate into the local trust stores.
 | |
| 
 | |
| Caddy will attempt to install its root certificates into the local
 | |
| trust stores automatically when they are first generated, but it
 | |
| might fail if Caddy doesn't have the appropriate permissions to
 | |
| write to the trust store. This command is necessary to pre-install
 | |
| the certificates before using them, if the server process runs as an
 | |
| unprivileged user (such as via systemd).
 | |
| 
 | |
| By default, this command installs the root certificate for Caddy's
 | |
| default CA (i.e. 'local'). You may specify the ID of another CA
 | |
| with the --ca flag.
 | |
| 
 | |
| This command will attempt to connect to Caddy's admin API running at
 | |
| '` + caddy.DefaultAdminListen + `' to fetch the root certificate. You may
 | |
| explicitly specify the --address, or use the --config flag to load
 | |
| the admin address from your config, if not using the default.`,
 | |
| 		CobraFunc: func(cmd *cobra.Command) {
 | |
| 			cmd.Flags().StringP("ca", "", "", "The ID of the CA to trust (defaults to 'local')")
 | |
| 			cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)")
 | |
| 			cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)")
 | |
| 			cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)")
 | |
| 			cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdTrust)
 | |
| 		},
 | |
| 	})
 | |
| 
 | |
| 	caddycmd.RegisterCommand(caddycmd.Command{
 | |
| 		Name:  "untrust",
 | |
| 		Usage: "[--cert <path>] | [[--ca <id>] [--address <listen>] [--config <path> [--adapter <name>]]]",
 | |
| 		Short: "Untrusts a locally-trusted CA certificate",
 | |
| 		Long: `
 | |
| Untrusts a root certificate from the local trust store(s).
 | |
| 
 | |
| This command uninstalls trust; it does not necessarily delete the
 | |
| root certificate from trust stores entirely. Thus, repeatedly
 | |
| trusting and untrusting new certificates can fill up trust databases.
 | |
| 
 | |
| This command does not delete or modify certificate files from Caddy's
 | |
| configured storage.
 | |
| 
 | |
| This command can be used in one of two ways. Either by specifying
 | |
| which certificate to untrust by a direct path to the certificate
 | |
| file with the --cert flag, or by fetching the root certificate for
 | |
| the CA from the admin API (default behaviour).
 | |
| 
 | |
| If the admin API is used, then the CA defaults to 'local'. You may
 | |
| specify the ID of another CA with the --ca flag. By default, this
 | |
| will attempt to connect to the Caddy's admin API running at
 | |
| '` + caddy.DefaultAdminListen + `' to fetch the root certificate.
 | |
| You may explicitly specify the --address, or use the --config flag
 | |
| to load the admin address from your config, if not using the default.`,
 | |
| 		CobraFunc: func(cmd *cobra.Command) {
 | |
| 			cmd.Flags().StringP("cert", "p", "", "The path to the CA certificate to untrust")
 | |
| 			cmd.Flags().StringP("ca", "", "", "The ID of the CA to untrust (defaults to 'local')")
 | |
| 			cmd.Flags().StringP("address", "", "", "Address of the administration API listener (if --config is not used)")
 | |
| 			cmd.Flags().StringP("config", "c", "", "Configuration file (if --address is not used)")
 | |
| 			cmd.Flags().StringP("adapter", "a", "", "Name of config adapter to apply (if --config is used)")
 | |
| 			cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdUntrust)
 | |
| 		},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func cmdTrust(fl caddycmd.Flags) (int, error) {
 | |
| 	caID := fl.String("ca")
 | |
| 	addrFlag := fl.String("address")
 | |
| 	configFlag := fl.String("config")
 | |
| 	configAdapterFlag := fl.String("adapter")
 | |
| 
 | |
| 	// Prepare the URI to the admin endpoint
 | |
| 	if caID == "" {
 | |
| 		caID = DefaultCAID
 | |
| 	}
 | |
| 
 | |
| 	// Determine where we're sending the request to get the CA info
 | |
| 	adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag)
 | |
| 	if err != nil {
 | |
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// Fetch the root cert from the admin API
 | |
| 	rootCert, err := rootCertFromAdmin(adminAddr, caID)
 | |
| 	if err != nil {
 | |
| 		return caddy.ExitCodeFailedStartup, err
 | |
| 	}
 | |
| 
 | |
| 	// Set up the CA struct; we only need to fill in the root
 | |
| 	// because we're only using it to make use of the installRoot()
 | |
| 	// function. Also needs a logger for warnings, and a "cert path"
 | |
| 	// for the root cert; since we're loading from the API and we
 | |
| 	// don't know the actual storage path via this flow, we'll just
 | |
| 	// pass through the admin API address instead.
 | |
| 	ca := CA{
 | |
| 		log:          caddy.Log(),
 | |
| 		root:         rootCert,
 | |
| 		rootCertPath: adminAddr + path.Join(adminPKIEndpointBase, "ca", caID),
 | |
| 	}
 | |
| 
 | |
| 	// Install the cert!
 | |
| 	err = ca.installRoot()
 | |
| 	if err != nil {
 | |
| 		return caddy.ExitCodeFailedStartup, err
 | |
| 	}
 | |
| 
 | |
| 	return caddy.ExitCodeSuccess, nil
 | |
| }
 | |
| 
 | |
| func cmdUntrust(fl caddycmd.Flags) (int, error) {
 | |
| 	certFile := fl.String("cert")
 | |
| 	caID := fl.String("ca")
 | |
| 	addrFlag := fl.String("address")
 | |
| 	configFlag := fl.String("config")
 | |
| 	configAdapterFlag := fl.String("adapter")
 | |
| 
 | |
| 	if certFile != "" && (caID != "" || addrFlag != "" || configFlag != "") {
 | |
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("conflicting command line arguments, cannot use --cert with other flags")
 | |
| 	}
 | |
| 
 | |
| 	// If a file was specified, try to uninstall the cert matching that file
 | |
| 	if certFile != "" {
 | |
| 		// Sanity check, make sure cert file exists first
 | |
| 		_, err := os.Stat(certFile)
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedStartup, fmt.Errorf("accessing certificate file: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		// Uninstall the file!
 | |
| 		err = truststore.UninstallFile(certFile,
 | |
| 			truststore.WithDebug(),
 | |
| 			truststore.WithFirefox(),
 | |
| 			truststore.WithJava())
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		return caddy.ExitCodeSuccess, nil
 | |
| 	}
 | |
| 
 | |
| 	// Prepare the URI to the admin endpoint
 | |
| 	if caID == "" {
 | |
| 		caID = DefaultCAID
 | |
| 	}
 | |
| 
 | |
| 	// Determine where we're sending the request to get the CA info
 | |
| 	adminAddr, err := caddycmd.DetermineAdminAPIAddress(addrFlag, nil, configFlag, configAdapterFlag)
 | |
| 	if err != nil {
 | |
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("couldn't determine admin API address: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// Fetch the root cert from the admin API
 | |
| 	rootCert, err := rootCertFromAdmin(adminAddr, caID)
 | |
| 	if err != nil {
 | |
| 		return caddy.ExitCodeFailedStartup, err
 | |
| 	}
 | |
| 
 | |
| 	// Uninstall the cert!
 | |
| 	err = truststore.Uninstall(rootCert,
 | |
| 		truststore.WithDebug(),
 | |
| 		truststore.WithFirefox(),
 | |
| 		truststore.WithJava())
 | |
| 	if err != nil {
 | |
| 		return caddy.ExitCodeFailedStartup, fmt.Errorf("failed to uninstall certificate file: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	return caddy.ExitCodeSuccess, nil
 | |
| }
 | |
| 
 | |
| // rootCertFromAdmin makes the API request to fetch the root certificate for the named CA via admin API.
 | |
| func rootCertFromAdmin(adminAddr string, caID string) (*x509.Certificate, error) {
 | |
| 	uri := path.Join(adminPKIEndpointBase, "ca", caID)
 | |
| 
 | |
| 	// Make the request to fetch the CA info
 | |
| 	resp, err := caddycmd.AdminAPIRequest(adminAddr, http.MethodGet, uri, make(http.Header), nil)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("requesting CA info: %v", err)
 | |
| 	}
 | |
| 	defer resp.Body.Close()
 | |
| 
 | |
| 	// Decode the response
 | |
| 	caInfo := new(caInfo)
 | |
| 	err = json.NewDecoder(resp.Body).Decode(caInfo)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to decode JSON response: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	// Decode the root cert
 | |
| 	rootBlock, _ := pem.Decode([]byte(caInfo.RootCert))
 | |
| 	if rootBlock == nil {
 | |
| 		return nil, fmt.Errorf("failed to decode root certificate: %v", err)
 | |
| 	}
 | |
| 	rootCert, err := x509.ParseCertificate(rootBlock.Bytes)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to parse root certificate: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	return rootCert, nil
 | |
| }
 |