apc-p15-tool/pkg/app/cmd_install.go
Greg T. Wallace 47b964d6ee install: add time check and warning
Clock skew can cause problems with SSL and certificates. Check the UPS clock and log a warning for the user if the UPS clock is more than 1 hour different than the clock of the system this tool is running on

see: https://github.com/gregtwallace/apc-p15-tool/issues/11#issuecomment-2609010943
2025-01-27 19:11:06 -05:00

155 lines
5.1 KiB
Go

package app
import (
"apc-p15-tool/pkg/apcssh"
"bytes"
"context"
"crypto/tls"
"encoding/pem"
"errors"
"fmt"
"strconv"
"time"
)
const timeLoggingFormat = time.RFC1123Z
// cmdInstall is the app's command to create apc p15 file content from key and cert
// pem files and upload the p15 to the specified APC UPS
func (app *app) cmdInstall(cmdCtx context.Context, args []string) error {
// extra args == error
if len(args) != 0 {
return fmt.Errorf("install: failed, %w (%d)", ErrExtraArgs, len(args))
}
// must have username
if app.config.install.username == nil || *app.config.install.username == "" {
return errors.New("install: failed, username not specified")
}
// must have password
if app.config.install.password == nil || *app.config.install.password == "" {
return errors.New("install: failed, password not specified")
}
// must have fingerprint
if app.config.install.fingerprint == nil || *app.config.install.fingerprint == "" {
return errors.New("install: failed, fingerprint not specified")
}
keyPem, certPem, err := app.config.install.keyCertPemCfg.GetPemBytes("install")
if err != nil {
return err
}
// host to install on must be specified
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")
}
// validation done
// make p15 file
keyP15, keyCertP15, err := app.pemToAPCP15(keyPem, certPem, "install")
if err != nil {
return err
}
// log warning if insecure cipher
if app.config.install.insecureCipher != nil && *app.config.install.insecureCipher {
app.stdLogger.Println("WARNING: install: insecure ciphers are enabled (--insecurecipher). SSH with an insecure cipher is NOT secure and should NOT be used.")
}
// make APC SSH client
cfg := &apcssh.Config{
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,
InsecureCipher: *app.config.install.insecureCipher,
}
client, err := apcssh.New(cfg)
if err != nil {
return fmt.Errorf("install: failed to connect to host (%w)", err)
}
app.stdLogger.Println("install: connected to ups ssh, installing ssl key and cert...")
// check time - don't fail it time is no good, just do logging here
upsT, err := client.GetTime()
if err != nil {
app.errLogger.Printf("warn: install: failed to fetch UPS time (%s), you should manually verify the time is correct on the UPS", err)
} else if upsT.After(time.Now().Add(1*time.Hour)) || upsT.Before(time.Now().Add(-1*time.Hour)) {
app.errLogger.Printf("warn: install: UPS clock skew detected (this system's time is %s vs. UPS time %s", time.Now().Format(timeLoggingFormat), upsT.Format(timeLoggingFormat))
} else {
app.stdLogger.Printf("install: UPS clock appears correct (%s)", upsT.Format(timeLoggingFormat))
}
// install SSL Cert
err = client.InstallSSLCert(keyP15, certPem, keyCertP15)
if err != nil {
return fmt.Errorf("install: %w", err)
}
// installed
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 {
app.stdLogger.Println("install: sending restart command")
err = client.RestartWebUI()
if err != nil {
return fmt.Errorf("install: failed to send webui restart command (%w)", err)
}
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
}