mirror of
https://github.com/gregtwallace/apc-p15-tool.git
synced 2025-01-22 08:14:08 +00:00
add install function
install pem files directly to an apc ups
This commit is contained in:
parent
4c154b2f27
commit
a089d12c87
9 changed files with 392 additions and 51 deletions
5
go.mod
5
go.mod
|
@ -9,7 +9,10 @@ require (
|
||||||
golang.org/x/crypto v0.18.0
|
golang.org/x/crypto v0.18.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require go.uber.org/multierr v1.11.0 // indirect
|
require (
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
replace apc-p15-tool/cmd => /cmd
|
replace apc-p15-tool/cmd => /cmd
|
||||||
|
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -18,6 +18,10 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||||
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
|
||||||
|
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
|
@ -31,27 +31,21 @@ func Start() {
|
||||||
logger: makeZapLogger(&initLogLevel),
|
logger: makeZapLogger(&initLogLevel),
|
||||||
}
|
}
|
||||||
|
|
||||||
// get config
|
// log start
|
||||||
app.getConfig()
|
app.logger.Infof("apc-p15-tool v%s", appVersion)
|
||||||
|
|
||||||
|
// get & parse config
|
||||||
|
err := app.getConfig()
|
||||||
|
|
||||||
// re-init logger with configured log level
|
// re-init logger with configured log level
|
||||||
app.logger = makeZapLogger(app.config.logLevel)
|
app.logger = makeZapLogger(app.config.logLevel)
|
||||||
|
|
||||||
// log start
|
// deal with config err (after logger re-init)
|
||||||
app.logger.Infof("apc-p15-tool v%s", appVersion)
|
|
||||||
|
|
||||||
// get config
|
|
||||||
app.getConfig()
|
|
||||||
|
|
||||||
// run it
|
|
||||||
exitCode := 0
|
|
||||||
err := app.cmd.ParseAndRun(context.Background(), os.Args[1:], ff.WithEnvVarPrefix(environmentVarPrefix))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exitCode = 1
|
exitCode := 0
|
||||||
|
|
||||||
if errors.Is(err, ff.ErrHelp) {
|
if errors.Is(err, ff.ErrHelp) {
|
||||||
// help explicitly requested
|
// help explicitly requested
|
||||||
exitCode = 0
|
|
||||||
app.logger.Info("\n\n", ffhelp.Command(app.cmd))
|
app.logger.Info("\n\n", ffhelp.Command(app.cmd))
|
||||||
|
|
||||||
} else if errors.Is(err, ff.ErrDuplicateFlag) ||
|
} else if errors.Is(err, ff.ErrDuplicateFlag) ||
|
||||||
|
@ -59,13 +53,28 @@ func Start() {
|
||||||
errors.Is(err, ff.ErrNoExec) ||
|
errors.Is(err, ff.ErrNoExec) ||
|
||||||
errors.Is(err, ErrExtraArgs) {
|
errors.Is(err, ErrExtraArgs) {
|
||||||
// other error that suggests user needs to see help
|
// other error that suggests user needs to see help
|
||||||
|
exitCode = 1
|
||||||
app.logger.Error(err)
|
app.logger.Error(err)
|
||||||
app.logger.Info("\n\n", ffhelp.Command(app.cmd))
|
app.logger.Info("\n\n", ffhelp.Command(app.cmd))
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// any other error
|
// any other error
|
||||||
|
exitCode = 1
|
||||||
app.logger.Error(err)
|
app.logger.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get config
|
||||||
|
app.getConfig()
|
||||||
|
|
||||||
|
// run it
|
||||||
|
exitCode := 0
|
||||||
|
err = app.cmd.Run(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
exitCode = 1
|
||||||
|
app.logger.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.logger.Info("apc-p15-tool done")
|
app.logger.Info("apc-p15-tool done")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"apc-p15-tool/pkg/pkcs15"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -29,42 +28,13 @@ func (app *app) cmdCreate(_ context.Context, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// validation done
|
// validation done
|
||||||
app.logger.Infof("create: making apc p15 file from pem files")
|
|
||||||
|
|
||||||
// Read in PEM files
|
// make p15 file
|
||||||
keyPem, err := os.ReadFile(*app.config.create.keyPemFilePath)
|
apcFile, err := app.pemToAPCP15(*app.config.create.keyPemFilePath, *app.config.create.certPemFilePath, "create")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create: failed to read key file (%s)", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
certPem, err := os.ReadFile(*app.config.create.certPemFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create: failed to read cert file (%s)", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make p15 struct
|
|
||||||
p15, err := pkcs15.ParsePEMToPKCS15(keyPem, certPem)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create: failed to parse pem files (%s)", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.logger.Infof("create: successfully loaded pem files")
|
|
||||||
|
|
||||||
// make file bytes
|
|
||||||
p15File, err := p15.ToP15File()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create: failed to make p15 file (%s)", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// make header for file bytes
|
|
||||||
apcHeader, err := makeFileHeader(p15File)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("create: failed to make p15 file header (%s)", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// combine header with file
|
|
||||||
apcFile := append(apcHeader, p15File...)
|
|
||||||
|
|
||||||
// determine file name (should already be done by flag parsing, but avoid nil just in case)
|
// determine file name (should already be done by flag parsing, but avoid nil just in case)
|
||||||
fileName := createDefaultOutFilePath
|
fileName := createDefaultOutFilePath
|
||||||
if app.config.create.outFilePath != nil && *app.config.create.outFilePath != "" {
|
if app.config.create.outFilePath != nil && *app.config.create.outFilePath != "" {
|
||||||
|
|
131
pkg/app/cmd_install.go
Normal file
131
pkg/app/cmd_install.go
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// key must be specified
|
||||||
|
if app.config.install.keyPemFilePath == nil || *app.config.install.keyPemFilePath == "" {
|
||||||
|
return errors.New("install: failed, key not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// cert must be specified
|
||||||
|
if app.config.install.certPemFilePath == nil || *app.config.install.certPemFilePath == "" {
|
||||||
|
return errors.New("install: failed, cert not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// host to install on must be specified
|
||||||
|
if app.config.install.hostAndPort == nil || *app.config.install.hostAndPort == "" {
|
||||||
|
return errors.New("install: failed, apc host not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// validation done
|
||||||
|
|
||||||
|
// make p15 file
|
||||||
|
apcFile, err := app.pemToAPCP15(*app.config.install.keyPemFilePath, *app.config.install.certPemFilePath, "install")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// make host key callback
|
||||||
|
hk := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||||
|
// calculate server's key's SHA256
|
||||||
|
hasher := sha256.New()
|
||||||
|
_, err := hasher.Write(key.Marshal())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
actualHash := hasher.Sum(nil)
|
||||||
|
|
||||||
|
// log fingerprint for debugging
|
||||||
|
actualHashB64 := base64.RawStdEncoding.EncodeToString(actualHash)
|
||||||
|
actualHashHex := hex.EncodeToString(actualHash)
|
||||||
|
app.logger.Debugf("ssh: remote server key fingerprint (b64): %s", actualHashB64)
|
||||||
|
app.logger.Debugf("ssh: remote server key fingerprint (hex): %s", actualHashHex)
|
||||||
|
|
||||||
|
// allow base64 format
|
||||||
|
if actualHashB64 == *app.config.install.fingerprint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow hex format
|
||||||
|
if actualHashHex == *app.config.install.fingerprint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("ssh: fingerprint didn't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// install file on UPS
|
||||||
|
// ssh config
|
||||||
|
config := &ssh.ClientConfig{
|
||||||
|
User: *app.config.install.username,
|
||||||
|
Auth: []ssh.AuthMethod{
|
||||||
|
ssh.Password(*app.config.install.password),
|
||||||
|
},
|
||||||
|
// APC seems to require `Client Version` string to start with "SSH-2" and must be at least
|
||||||
|
// 13 characters long
|
||||||
|
// e.g. working from Ubuntu ssh: ClientVersion: "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.6",
|
||||||
|
// ClientVersion: "SSH-2.0-PuTTY_Release_0.80",
|
||||||
|
ClientVersion: fmt.Sprintf("SSH-2.0-apc-p15-tool_v%s %s-%s", appVersion, runtime.GOOS, runtime.GOARCH),
|
||||||
|
Config: ssh.Config{
|
||||||
|
// KeyExchanges: []string{"ecdh-sha2-nistp256"},
|
||||||
|
// Ciphers: []string{"aes128-ctr"},
|
||||||
|
// MACs: []string{"hmac-sha2-256"},
|
||||||
|
},
|
||||||
|
// HostKeyAlgorithms: []string{"ssh-rsa"},
|
||||||
|
HostKeyCallback: hk,
|
||||||
|
|
||||||
|
// reasonable timeout for file copy
|
||||||
|
Timeout: scpTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to ups over SSH
|
||||||
|
client, err := ssh.Dial("tcp", *app.config.install.hostAndPort, config)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("install: failed to connect to host (%w)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send file to UPS
|
||||||
|
err = scpSendFileToUPS(client, apcFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("install: failed to send p15 file to ups over scp (%w)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// done
|
||||||
|
app.logger.Infof("install: apc p15 file installed on %s", *app.config.install.hostAndPort)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v4"
|
"github.com/peterbourgon/ff/v4"
|
||||||
)
|
)
|
||||||
|
@ -18,18 +19,26 @@ type config struct {
|
||||||
certPemFilePath *string
|
certPemFilePath *string
|
||||||
outFilePath *string
|
outFilePath *string
|
||||||
}
|
}
|
||||||
|
install struct {
|
||||||
|
keyPemFilePath *string
|
||||||
|
certPemFilePath *string
|
||||||
|
hostAndPort *string
|
||||||
|
fingerprint *string
|
||||||
|
username *string
|
||||||
|
password *string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getConfig returns the app's configuration from either command line args,
|
// getConfig returns the app's configuration from either command line args,
|
||||||
// or environment variables
|
// or environment variables
|
||||||
func (app *app) getConfig() {
|
func (app *app) getConfig() error {
|
||||||
// make config
|
// make config
|
||||||
cfg := &config{}
|
cfg := &config{}
|
||||||
|
|
||||||
// commands:
|
// commands:
|
||||||
// create
|
// create
|
||||||
|
// install
|
||||||
// TODO:
|
// TODO:
|
||||||
// upload
|
|
||||||
// unpack (both key & key+cert)
|
// unpack (both key & key+cert)
|
||||||
|
|
||||||
// apc-p15-tool -- root command
|
// apc-p15-tool -- root command
|
||||||
|
@ -61,7 +70,33 @@ func (app *app) getConfig() {
|
||||||
|
|
||||||
rootCmd.Subcommands = append(rootCmd.Subcommands, createCmd)
|
rootCmd.Subcommands = append(rootCmd.Subcommands, createCmd)
|
||||||
|
|
||||||
// set app cmd & cfg
|
// install -- subcommand
|
||||||
app.cmd = rootCmd
|
installFlags := ff.NewFlagSet("install").SetParent(rootFlags)
|
||||||
app.config = cfg
|
|
||||||
|
cfg.install.keyPemFilePath = installFlags.StringLong("keyfile", "", "path and filename of the rsa-2048 key in pem format")
|
||||||
|
cfg.install.certPemFilePath = installFlags.StringLong("certfile", "", "path and filename 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.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")
|
||||||
|
|
||||||
|
installCmd := &ff.Command{
|
||||||
|
Name: "install",
|
||||||
|
Usage: "apc-p15-tool upload --keyfile key.pem --certfile cert.pem --apchost example.com:22 --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,
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.Subcommands = append(rootCmd.Subcommands, installCmd)
|
||||||
|
|
||||||
|
// set cfg & parse
|
||||||
|
app.config = cfg
|
||||||
|
app.cmd = rootCmd
|
||||||
|
err := app.cmd.Parse(os.Args[1:], ff.WithEnvVarPrefix(environmentVarPrefix))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
50
pkg/app/pem_to_p15.go
Normal file
50
pkg/app/pem_to_p15.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apc-p15-tool/pkg/pkcs15"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// pemToAPCP15 reads the specified pem files and returns the apc p15 bytes
|
||||||
|
func (app *app) pemToAPCP15(keyFileName, certFileName, parentCmdName string) ([]byte, error) {
|
||||||
|
app.logger.Infof("%s: making apc p15 file from pem files", parentCmdName)
|
||||||
|
|
||||||
|
// Read in PEM files
|
||||||
|
keyPem, err := os.ReadFile(keyFileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to read key file (%w)", parentCmdName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certPem, err := os.ReadFile(certFileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to read cert file (%w)", parentCmdName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make p15 struct
|
||||||
|
p15, err := pkcs15.ParsePEMToPKCS15(keyPem, certPem)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to parse pem files (%w)", parentCmdName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.logger.Infof("%s: successfully loaded pem files", parentCmdName)
|
||||||
|
|
||||||
|
// make file bytes
|
||||||
|
p15File, err := p15.ToP15File()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to make p15 file (%w)", parentCmdName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make header for file bytes
|
||||||
|
apcHeader, err := makeFileHeader(p15File)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: failed to make p15 file header (%w)", parentCmdName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine header with file
|
||||||
|
apcFile := append(apcHeader, p15File...)
|
||||||
|
|
||||||
|
app.logger.Infof("%s: apc p15 file data succesfully generated", parentCmdName)
|
||||||
|
|
||||||
|
return apcFile, nil
|
||||||
|
}
|
105
pkg/app/scp.go
Normal file
105
pkg/app/scp.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APC UPS won't except Go's SSH "Run()" command as the format isn't quite
|
||||||
|
// the same. Therefore, write a custom implementation to send the desired
|
||||||
|
// command instead of relying on something like github.com/bramvdbogaerde/go-scp
|
||||||
|
|
||||||
|
const (
|
||||||
|
scpP15Destination = "/ssl/defaultcert.p15"
|
||||||
|
scpP15PermissionsStr = "0600"
|
||||||
|
|
||||||
|
scpTimeout = 90 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
// scpSendFileToUPS 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 {
|
||||||
|
// make session to use for SCP
|
||||||
|
session, err := client.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("scp: failed to create session (%w)", err)
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
|
||||||
|
// attach pipes
|
||||||
|
out, err := session.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w, err := session.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
// send execute cmd --
|
||||||
|
// build cmd to send as request
|
||||||
|
// 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 -v -t %s", scpP15Destination))
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return errors.New("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)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("scp: failed to send scp cmd (bad remote response) (%w)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// just file name (without path)
|
||||||
|
filename := path.Base(scpP15Destination)
|
||||||
|
|
||||||
|
// send file header
|
||||||
|
_, err = fmt.Fprintln(w, "C"+scpP15PermissionsStr, len(p15File), filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("scp: failed to send file info (%w)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scpCheckResponse(out)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send file end
|
||||||
|
_, err = fmt.Fprint(w, "\x00")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("scp: failed to send final 00 byte (%w)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scpCheckResponse(out)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("scp: failed to send file (bad remote response) (%w)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// done
|
||||||
|
return nil
|
||||||
|
}
|
34
pkg/app/scp_response.go
Normal file
34
pkg/app/scp_response.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// scpCheckResponse reads the output from the remote and returns an error
|
||||||
|
// if the remote output was not 0
|
||||||
|
func scpCheckResponse(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
responseType := buffer[0]
|
||||||
|
message := ""
|
||||||
|
if responseType > 0 {
|
||||||
|
bufferedReader := bufio.NewReader(remoteOutPipe)
|
||||||
|
message, err = bufferedReader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("scp: 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 nil
|
||||||
|
}
|
Loading…
Reference in a new issue