mirror of
https://github.com/gregtwallace/apc-p15-tool.git
synced 2025-01-22 08:14:08 +00:00
add optional webui restart
This commit is contained in:
parent
29cd44077b
commit
2b46f33af8
6 changed files with 103 additions and 28 deletions
|
@ -11,6 +11,9 @@ const createDefaultOutFilePath = "apctool.p15"
|
|||
// cmdCreate is the app's command to create an apc p15 file from key and cert
|
||||
// pem files
|
||||
func (app *app) cmdCreate(_ context.Context, args []string) error {
|
||||
// done
|
||||
defer app.stdLogger.Println("create: done")
|
||||
|
||||
// extra args == error
|
||||
if len(args) != 0 {
|
||||
return fmt.Errorf("create: failed, %w (%d)", ErrExtraArgs, len(args))
|
||||
|
|
|
@ -16,6 +16,9 @@ import (
|
|||
// 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 {
|
||||
// done
|
||||
defer app.stdLogger.Println("install: done")
|
||||
|
||||
// extra args == error
|
||||
if len(args) != 0 {
|
||||
return fmt.Errorf("install: failed, %w (%d)", ErrExtraArgs, len(args))
|
||||
|
@ -129,7 +132,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error {
|
|||
HostKeyCallback: hk,
|
||||
|
||||
// reasonable timeout for file copy
|
||||
Timeout: scpTimeout,
|
||||
Timeout: sshScpTimeout,
|
||||
}
|
||||
|
||||
// connect to ups over SSH
|
||||
|
@ -137,15 +140,37 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("install: failed to connect to host (%w)", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// send file to UPS
|
||||
err = scpSendFileToUPS(client, apcFile)
|
||||
err = sshScpSendFileToUPS(client, apcFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install: failed to send p15 file to ups over scp (%w)", err)
|
||||
}
|
||||
|
||||
// done
|
||||
// installed
|
||||
app.stdLogger.Printf("install: apc p15 file installed on %s", *app.config.install.hostAndPort)
|
||||
|
||||
// restart UPS webUI
|
||||
if app.config.install.restartWebUI != nil && *app.config.install.restartWebUI {
|
||||
app.stdLogger.Println("install: sending restart command")
|
||||
|
||||
// connect to ups over SSH
|
||||
// opening a second session doesn't seem to work with my NMC2 for some reason, so make
|
||||
// a new connection instead
|
||||
client, err = ssh.Dial("tcp", *app.config.install.hostAndPort, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install: failed to reconnect to host to send webui restart command (%w)", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
err = sshResetUPSWebUI(client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install: failed to send webui restart command (%w)", err)
|
||||
}
|
||||
|
||||
app.stdLogger.Println("install: sent webui restart command")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ type config struct {
|
|||
fingerprint *string
|
||||
username *string
|
||||
password *string
|
||||
restartWebUI *bool
|
||||
insecureCipher *bool
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +94,7 @@ func (app *app) getConfig(args []string) error {
|
|||
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.insecureCipher = installFlags.BoolLong("insecurecipher", "allows the use of insecure ssh ciphers (NOT recommended)")
|
||||
|
||||
installCmd := &ff.Command{
|
||||
|
|
45
pkg/app/ssh_resetwebui.go
Normal file
45
pkg/app/ssh_resetwebui.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// sshResetUPSWebUI sends a command to the UPS to restart the WebUI. This
|
||||
// command is supposed to be required to load the new cert, but that
|
||||
// doesn't seem to be true (at least it isn't on my UPS). Adding the
|
||||
// option though, in case other UPS might need it.
|
||||
func sshResetUPSWebUI(client *ssh.Client) error {
|
||||
// make session to use for restart command
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssh: restart: failed to create session (%w)", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// start shell
|
||||
err = session.Shell()
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssh: restart: failed to start shell (%w)", err)
|
||||
}
|
||||
|
||||
// execure reboot via SendRequest
|
||||
payload := []byte("reboot -Y")
|
||||
payloadLen := uint8(len(payload))
|
||||
payload = append([]byte{0, 0, 0, payloadLen}, payload...)
|
||||
|
||||
ok, err := session.SendRequest("exec", true, payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ssh: scp: failed to execute scp cmd (%w)", err)
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("ssh: scp: execute scp cmd not ok")
|
||||
}
|
||||
|
||||
// don't read remote output, as nothing interesting actually outputs
|
||||
|
||||
// done
|
||||
return nil
|
||||
}
|
|
@ -6,13 +6,13 @@ import (
|
|||
"io"
|
||||
)
|
||||
|
||||
// scpCheckResponse reads the output from the remote and returns an error
|
||||
// sshCheckResponse reads the output from the remote and returns an error
|
||||
// if the remote output was not 0
|
||||
func scpCheckResponse(remoteOutPipe io.Reader) error {
|
||||
func sshCheckResponse(remoteOutPipe io.Reader) error {
|
||||
buffer := make([]uint8, 1)
|
||||
_, err := remoteOutPipe.Read(buffer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to make read output buffer (%w)", err)
|
||||
return fmt.Errorf("ssh: failed to make read output buffer (%w)", err)
|
||||
}
|
||||
|
||||
responseType := buffer[0]
|
||||
|
@ -21,13 +21,13 @@ func scpCheckResponse(remoteOutPipe io.Reader) error {
|
|||
bufferedReader := bufio.NewReader(remoteOutPipe)
|
||||
message, err = bufferedReader.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to read output buffer (%w)", err)
|
||||
return fmt.Errorf("ssh: failed to read output buffer (%w)", err)
|
||||
}
|
||||
}
|
||||
|
||||
// if not 0 (aka OK)
|
||||
if responseType != 0 {
|
||||
return fmt.Errorf("scp: remote returned error (%d: %s)", responseType, message)
|
||||
return fmt.Errorf("ssh: remote returned error (%d: %s)", responseType, message)
|
||||
}
|
||||
|
||||
return nil
|
|
@ -16,20 +16,20 @@ import (
|
|||
// command instead of relying on something like github.com/bramvdbogaerde/go-scp
|
||||
|
||||
const (
|
||||
scpP15Destination = "/ssl/defaultcert.p15"
|
||||
scpP15PermissionsStr = "0600"
|
||||
sshScpP15Destination = "/ssl/defaultcert.p15"
|
||||
sshScpP15PermissionsStr = "0600"
|
||||
|
||||
scpTimeout = 90 * time.Second
|
||||
sshScpTimeout = 90 * time.Second
|
||||
)
|
||||
|
||||
// scpSendFileToUPS sends the p15File to the APC UPS via the SCP protocol. it is
|
||||
// sshScpSendFileToUPS sends the p15File to the APC UPS via the SCP protocol. it is
|
||||
// automatically placed in the correct directory and will overwrite any existing
|
||||
// file
|
||||
func scpSendFileToUPS(client *ssh.Client, p15File []byte) error {
|
||||
func sshScpSendFileToUPS(client *ssh.Client, p15File []byte) error {
|
||||
// make session to use for SCP
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to create session (%w)", err)
|
||||
return fmt.Errorf("ssh: scp: failed to create session (%w)", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
|
@ -49,55 +49,55 @@ func scpSendFileToUPS(client *ssh.Client, p15File []byte) error {
|
|||
// Go implementation sends additional 0x22 bytes when using Run() (as
|
||||
// compared to putty's scp tool). these additional bytes seem to cause the
|
||||
// apc ups to fail execution of the command
|
||||
payload := []byte(fmt.Sprintf("scp -q -t %s", scpP15Destination))
|
||||
payload := []byte(fmt.Sprintf("scp -q -t %s", sshScpP15Destination))
|
||||
payloadLen := uint8(len(payload))
|
||||
payload = append([]byte{0, 0, 0, payloadLen}, payload...)
|
||||
|
||||
ok, err := session.SendRequest("exec", true, payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to execute scp cmd (%w)", err)
|
||||
return fmt.Errorf("ssh: scp: failed to execute scp cmd (%w)", err)
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("scp: execute scp cmd not ok")
|
||||
return errors.New("ssh: scp: execute scp cmd not ok")
|
||||
}
|
||||
|
||||
// check remote response
|
||||
// Note: File upload may not work if the client doesn't actually read from
|
||||
// the remote output.
|
||||
err = scpCheckResponse(out)
|
||||
err = sshCheckResponse(out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to send scp cmd (bad remote response) (%w)", err)
|
||||
return fmt.Errorf("ssh: scp: failed to send scp cmd (bad remote response) (%w)", err)
|
||||
}
|
||||
|
||||
// just file name (without path)
|
||||
filename := path.Base(scpP15Destination)
|
||||
filename := path.Base(sshScpP15Destination)
|
||||
|
||||
// send file header
|
||||
_, err = fmt.Fprintln(w, "C"+scpP15PermissionsStr, len(p15File), filename)
|
||||
_, err = fmt.Fprintln(w, "C"+sshScpP15PermissionsStr, len(p15File), filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to send file info (%w)", err)
|
||||
return fmt.Errorf("ssh: scp: failed to send file info (%w)", err)
|
||||
}
|
||||
|
||||
err = scpCheckResponse(out)
|
||||
err = sshCheckResponse(out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to send file info (bad remote response) (%w)", err)
|
||||
return fmt.Errorf("ssh: scp: failed to send file info (bad remote response) (%w)", err)
|
||||
}
|
||||
|
||||
// send actual file
|
||||
_, err = io.Copy(w, bytes.NewReader(p15File))
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to send file(%w)", err)
|
||||
return fmt.Errorf("ssh: scp: failed to send file(%w)", err)
|
||||
}
|
||||
|
||||
// send file end
|
||||
_, err = fmt.Fprint(w, "\x00")
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to send final 00 byte (%w)", err)
|
||||
return fmt.Errorf("ssh: scp: failed to send final 00 byte (%w)", err)
|
||||
}
|
||||
|
||||
err = scpCheckResponse(out)
|
||||
err = sshCheckResponse(out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scp: failed to send file (bad remote response) (%w)", err)
|
||||
return fmt.Errorf("ssh: scp: failed to send file (bad remote response) (%w)", err)
|
||||
}
|
||||
|
||||
// done
|
Loading…
Reference in a new issue