install: add web ui cert verification

* connect to the ups web ui after install and verify the proper certificate is being served
* rename `apchost` flag to `hostname`
* separate ports to additional flags (`sshport` `sslport`) with sane defaults
This commit is contained in:
Greg T. Wallace 2024-09-17 18:44:34 -04:00
parent c22447b0c2
commit 1cd9916a17
3 changed files with 63 additions and 7 deletions

View file

@ -145,7 +145,7 @@ disk. It instead installs the files directly on the NMC. Logic
automatically deduces if the device is an NMC2 or NMC3 and performs automatically deduces if the device is an NMC2 or NMC3 and performs
the appropriate installation steps. the appropriate installation steps.
e.g. `./apc-p15-tool install --keyfile ./apckey.pem --certfile ./apccert.pem --apchost myapc.example.com:22 --username apc --password someSecret --fingerprint 123abc` e.g. `./apc-p15-tool install --keyfile ./apckey.pem --certfile ./apccert.pem --hostname myapc.example.com --username apc --password someSecret --fingerprint 123abc`
## Note About Install Automation ## Note About Install Automation

View file

@ -2,9 +2,14 @@ package app
import ( import (
"apc-p15-tool/pkg/apcssh" "apc-p15-tool/pkg/apcssh"
"bytes"
"context" "context"
"crypto/tls"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"strconv"
"time"
) )
// cmdInstall is the app's command to create apc p15 file content from key and cert // cmdInstall is the app's command to create apc p15 file content from key and cert
@ -36,7 +41,9 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error {
} }
// host to install on must be specified // host to install on must be specified
if app.config.install.hostAndPort == nil || *app.config.install.hostAndPort == "" { if app.config.install.hostname == nil || *app.config.install.hostname == "" ||
app.config.install.sshport == nil || *app.config.install.sshport == 0 {
return errors.New("install: failed, apc host not specified") return errors.New("install: failed, apc host not specified")
} }
@ -55,7 +62,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error {
// make APC SSH client // make APC SSH client
cfg := &apcssh.Config{ cfg := &apcssh.Config{
Hostname: *app.config.install.hostAndPort, Hostname: *app.config.install.hostname + ":" + strconv.Itoa(*app.config.install.sshport),
Username: *app.config.install.username, Username: *app.config.install.username,
Password: *app.config.install.password, Password: *app.config.install.password,
ServerFingerprint: *app.config.install.fingerprint, ServerFingerprint: *app.config.install.fingerprint,
@ -75,7 +82,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error {
} }
// installed // installed
app.stdLogger.Printf("install: apc p15 file installed on %s", *app.config.install.hostAndPort) app.stdLogger.Printf("install: apc p15 file installed on %s", *app.config.install.hostname)
// restart UPS webUI // restart UPS webUI
if app.config.install.restartWebUI != nil && *app.config.install.restartWebUI { if app.config.install.restartWebUI != nil && *app.config.install.restartWebUI {
@ -89,5 +96,48 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error {
app.stdLogger.Println("install: sent webui restart command") app.stdLogger.Println("install: sent webui restart command")
} }
// check the new certificate is installed
if app.config.install.skipVerify != nil && !*app.config.install.skipVerify &&
app.config.install.webUISSLPort != nil && *app.config.install.webUISSLPort != 0 {
app.stdLogger.Println("install: attempting to verify certificate install...")
// sleep for UPS to finish anything it might be doing
time.Sleep(5 * time.Second)
// if UPS web UI was restarted, sleep longer
if app.config.install.restartWebUI != nil && *app.config.install.restartWebUI {
app.stdLogger.Println("install: waiting for ups webui restart...")
time.Sleep(25 * time.Second)
}
// connect to the web UI to get the current certificate
conf := &tls.Config{
InsecureSkipVerify: true,
}
conn, err := tls.Dial("tcp", *app.config.install.hostname+":"+strconv.Itoa(*app.config.install.webUISSLPort), conf)
if err != nil {
return fmt.Errorf("install: failed to dial webui for verification (%s)", err)
}
defer conn.Close()
// get top cert
leafCert := conn.ConnectionState().PeerCertificates[0]
if leafCert == nil {
return fmt.Errorf("install: failed to get web ui leaf cert for verification (%s)", err)
}
// convert pem to DER for comparison
pemBlock, _ := pem.Decode(certPem)
// verify cert is the correct one
certVerified := bytes.Equal(leafCert.Raw, pemBlock.Bytes)
if !certVerified {
return errors.New("install: web ui leaf cert does not match new cert")
}
app.stdLogger.Println("install: ups web ui cert verified")
}
return nil return nil
} }

View file

@ -33,11 +33,14 @@ type config struct {
} }
install struct { install struct {
keyCertPemCfg keyCertPemCfg
hostAndPort *string hostname *string
sshport *int
fingerprint *string fingerprint *string
username *string username *string
password *string password *string
restartWebUI *bool restartWebUI *bool
webUISSLPort *int
skipVerify *bool
insecureCipher *bool insecureCipher *bool
} }
} }
@ -92,16 +95,19 @@ func (app *app) getConfig(args []string) error {
cfg.install.certPemFilePath = installFlags.StringLong("certfile", "", "path and filename of the certificate in pem format") cfg.install.certPemFilePath = installFlags.StringLong("certfile", "", "path and filename of the certificate in pem format")
cfg.install.keyPem = installFlags.StringLong("keypem", "", "string of the key in pem format") cfg.install.keyPem = installFlags.StringLong("keypem", "", "string of the key in pem format")
cfg.install.certPem = installFlags.StringLong("certpem", "", "string of the certificate in pem format") cfg.install.certPem = installFlags.StringLong("certpem", "", "string of the certificate in pem format")
cfg.install.hostAndPort = installFlags.StringLong("apchost", "", "hostname:port of the apc ups to install the certificate on") cfg.install.hostname = installFlags.StringLong("hostname", "", "hostname of the apc ups to install the certificate on")
cfg.install.sshport = installFlags.IntLong("sshport", 22, "apc ups ssh port number")
cfg.install.fingerprint = installFlags.StringLong("fingerprint", "", "the SHA256 fingerprint value of the ups' ssh server") cfg.install.fingerprint = installFlags.StringLong("fingerprint", "", "the SHA256 fingerprint value of the ups' ssh server")
cfg.install.username = installFlags.StringLong("username", "", "username to login to the apc ups") cfg.install.username = installFlags.StringLong("username", "", "username to login to the apc ups")
cfg.install.password = installFlags.StringLong("password", "", "password to login to the apc ups") cfg.install.password = installFlags.StringLong("password", "", "password to login to the apc ups")
cfg.install.restartWebUI = installFlags.BoolLong("restartwebui", "some devices may need a webui restart to begin using the new cert, enabling this option sends the restart command after the p15 is installed") cfg.install.restartWebUI = installFlags.BoolLong("restartwebui", "some devices may need a webui restart to begin using the new cert, enabling this option sends the restart command after the p15 is installed")
cfg.install.webUISSLPort = installFlags.IntLong("sslport", 443, "apc ups ssl webui port number")
cfg.install.skipVerify = installFlags.BoolLong("skipverify", "the tool will try to connect to the UPS web UI to verify install success; this flag disables that check")
cfg.install.insecureCipher = installFlags.BoolLong("insecurecipher", "allows the use of insecure ssh ciphers (NOT recommended)") cfg.install.insecureCipher = installFlags.BoolLong("insecurecipher", "allows the use of insecure ssh ciphers (NOT recommended)")
installCmd := &ff.Command{ installCmd := &ff.Command{
Name: "install", Name: "install",
Usage: "apc-p15-tool install --keyfile key.pem --certfile cert.pem --apchost example.com:22 --fingerprint 123abc --username apc --password test", Usage: "apc-p15-tool install --keyfile key.pem --certfile cert.pem --hostname example.com --fingerprint 123abc --username apc --password test",
ShortHelp: "install the specified key and cert pem files on an apc ups (they will be converted to a comaptible p15 file)", ShortHelp: "install the specified key and cert pem files on an apc ups (they will be converted to a comaptible p15 file)",
Flags: installFlags, Flags: installFlags,
Exec: app.cmdInstall, Exec: app.cmdInstall,