all: add pin protected deactivation

This commit is contained in:
Cuong Manh Le
2024-02-01 20:43:03 +07:00
committed by Cuong Manh Le
parent 0826671809
commit d822bf4257
5 changed files with 86 additions and 6 deletions

View File

@@ -364,6 +364,9 @@ func initCLI() {
return
}
initLogging()
if err := checkDeactivationPin(s); errors.Is(err, errInvalidDeactivationPin) {
os.Exit(deactivationPinInvalidExitCode)
}
if doTasks([]task{{s.Stop, true}}) {
p.router.Cleanup()
p.resetDNS()
@@ -372,6 +375,8 @@ func initCLI() {
},
}
stopCmd.Flags().StringVarP(&iface, "iface", "", "", `Reset DNS setting for iface, "auto" means the default interface gateway`)
stopCmd.Flags().Int64VarP(&deactivationPin, "pin", "", defaultDeactivationPin, `Pin code for stopping ctrld`)
_ = stopCmd.Flags().MarkHidden("pin")
restartCmd := &cobra.Command{
PreRun: func(cmd *cobra.Command, args []string) {
@@ -518,10 +523,15 @@ NOTE: Uninstalling will set DNS to values provided by DHCP.`,
if iface == "" {
iface = "auto"
}
if err := checkDeactivationPin(s); errors.Is(err, errInvalidDeactivationPin) {
os.Exit(deactivationPinInvalidExitCode)
}
uninstall(p, s)
},
}
uninstallCmd.Flags().StringVarP(&iface, "iface", "", "", `Reset DNS setting for iface, use "auto" for the default gateway interface`)
uninstallCmd.Flags().Int64VarP(&deactivationPin, "pin", "", defaultDeactivationPin, `Pin code for uninstalling ctrld`)
_ = uninstallCmd.Flags().MarkHidden("pin")
listIfacesCmd := &cobra.Command{
Use: "list",
@@ -1171,6 +1181,18 @@ func processNoConfigFlags(noConfigStart bool) {
v.Set("upstream", upstream)
}
// defaultDeactivationPin is the default value for cdDeactivationPin.
// If cdDeactivationPin equals to this default, it means the pin code is not set from Control D API.
const defaultDeactivationPin = -1
// cdDeactivationPin is used in cd mode to decide whether stop and uninstall commands can be run.
var cdDeactivationPin int64 = defaultDeactivationPin
// deactivationPinNotSet reports whether cdDeactivationPin was not set by processCDFlags.
func deactivationPinNotSet() bool {
return cdDeactivationPin == defaultDeactivationPin
}
func processCDFlags(cfg *ctrld.Config) error {
logger := mainLog.Load().With().Str("mode", "cd").Logger()
logger.Info().Msgf("fetching Controld D configuration from API: %s", cdUID)
@@ -1195,6 +1217,11 @@ func processCDFlags(cfg *ctrld.Config) error {
return err
}
if resolverConfig.DeactivationPin != nil {
logger.Debug().Msg("saving deactivation pin")
cdDeactivationPin = *resolverConfig.DeactivationPin
}
logger.Info().Msg("generating ctrld config from Control-D configuration")
*cfg = ctrld.Config{}
@@ -2049,3 +2076,29 @@ func noticeWritingControlDConfig() error {
}
return nil
}
// deactivationPinInvalidExitCode indicates exit code due to invalid pin code.
const deactivationPinInvalidExitCode = 126
// errInvalidDeactivationPin indicates that the deactivation pin is invalid.
var errInvalidDeactivationPin = errors.New("deactivation pin is invalid")
// checkDeactivationPin validates if the deactivation pin matches one in ControlD config.
func checkDeactivationPin(s service.Service) error {
dir, err := socketDir()
if err != nil {
mainLog.Load().Err(err).Msg("could not check deactivation pin")
return err
}
cc := newSocketControlClient(s, dir)
if cc == nil {
return nil // ctrld is not running.
}
data, _ := json.Marshal(&deactivationRequest{Pin: deactivationPin})
resp, _ := cc.post(deactivationPath, bytes.NewReader(data))
if resp != nil && resp.StatusCode == http.StatusOK {
return nil // valid pin
}
mainLog.Load().Error().Msg("deactivation pin is invalid")
return errInvalidDeactivationPin
}

View File

@@ -27,3 +27,8 @@ func newControlClient(addr string) *controlClient {
func (c *controlClient) post(path string, data io.Reader) (*http.Response, error) {
return c.c.Post("http://unix"+path, contentTypeJson, data)
}
// deactivationRequest represents request for validating deactivation pin.
type deactivationRequest struct {
Pin int64 `json:"pin"`
}

View File

@@ -16,10 +16,11 @@ import (
)
const (
contentTypeJson = "application/json"
listClientsPath = "/clients"
startedPath = "/started"
reloadPath = "/reload"
contentTypeJson = "application/json"
listClientsPath = "/clients"
startedPath = "/started"
reloadPath = "/reload"
deactivationPath = "/deactivation"
)
type controlServer struct {
@@ -146,6 +147,25 @@ func (p *prog) registerControlServerHandler() {
// Otherwise, reload is done.
w.WriteHeader(http.StatusOK)
}))
p.cs.register(deactivationPath, http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
// Non-cd mode or pin code not set, always allowing deactivation.
if cdUID == "" || deactivationPinNotSet() {
w.WriteHeader(http.StatusOK)
return
}
var req deactivationRequest
if err := json.NewDecoder(request.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusPreconditionFailed)
mainLog.Load().Err(err).Msg("invalid deactivation request")
return
}
code := http.StatusBadRequest
if req.Pin == cdDeactivationPin {
code = http.StatusOK
}
w.WriteHeader(code)
}))
}
func jsonResponse(next http.Handler) http.Handler {

View File

@@ -34,6 +34,7 @@ var (
ifaceStartStop string
nextdns string
cdUpstreamProto string
deactivationPin int64
mainLog atomic.Pointer[zerolog.Logger]
consoleWriter zerolog.ConsoleWriter

View File

@@ -35,8 +35,9 @@ type ResolverConfig struct {
Ctrld struct {
CustomConfig string `json:"custom_config"`
} `json:"ctrld"`
Exclude []string `json:"exclude"`
UID string `json:"uid"`
Exclude []string `json:"exclude"`
UID string `json:"uid"`
DeactivationPin *int64 `json:"deactivation_pin,omitempty"`
}
type utilityResponse struct {