diff --git a/README.md b/README.md index fa53ea0..d339961 100644 --- a/README.md +++ b/README.md @@ -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 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 diff --git a/pkg/app/cmd_install.go b/pkg/app/cmd_install.go index d3270a4..a431eb8 100644 --- a/pkg/app/cmd_install.go +++ b/pkg/app/cmd_install.go @@ -2,9 +2,14 @@ package app import ( "apc-p15-tool/pkg/apcssh" + "bytes" "context" + "crypto/tls" + "encoding/pem" "errors" "fmt" + "strconv" + "time" ) // 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 - 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") } @@ -55,7 +62,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { // make APC SSH client cfg := &apcssh.Config{ - Hostname: *app.config.install.hostAndPort, + Hostname: *app.config.install.hostname + ":" + strconv.Itoa(*app.config.install.sshport), Username: *app.config.install.username, Password: *app.config.install.password, ServerFingerprint: *app.config.install.fingerprint, @@ -75,7 +82,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { } // 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 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") } + // 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 } diff --git a/pkg/app/config.go b/pkg/app/config.go index 6ef840b..7edf216 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -33,11 +33,14 @@ type config struct { } install struct { keyCertPemCfg - hostAndPort *string + hostname *string + sshport *int fingerprint *string username *string password *string restartWebUI *bool + webUISSLPort *int + skipVerify *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.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.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.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.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)") installCmd := &ff.Command{ 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)", Flags: installFlags, Exec: app.cmdInstall,