Compare commits

..

23 commits

Author SHA1 Message Date
Greg T. Wallace
c9ab6ae050
Update README.md 2025-05-31 22:59:50 -04:00
Greg T. Wallace
86feabd939 v1.2.2 2025-04-22 18:27:55 -04:00
Greg T. Wallace
124c06d8be build: compile linux/arm64 in native runner 2025-04-22 18:27:55 -04:00
Greg T. Wallace
72f3f42baa build: add darwin arm64 & amd64 2025-04-22 18:27:54 -04:00
Greg T. Wallace
3bb6b2a3c1 dep: update all 2025-04-22 18:27:54 -04:00
Greg T. Wallace
1392529a3f dep: go 1.24.2 2025-04-22 18:27:54 -04:00
Greg T. Wallace
e87a3100d2 v1.2.1 2025-03-17 22:06:52 -04:00
Greg T. Wallace
c67001f0e4 dep: update all 2025-03-17 22:06:46 -04:00
Greg T. Wallace
ad8c4e88a9 dep: go 1.24.1 2025-03-17 22:05:29 -04:00
FingerlessGloves
096b50187a
Fix GetTime for GMT users ()
UPS failes to use the required `+` when in the GMT timezone. Account for that.

---------

Co-authored-by: Greg T. Wallace <greg@gregtwallace.com>
2025-03-17 22:01:37 -04:00
Greg T. Wallace
c5bb8edbec update screenshot 2025-01-28 21:04:06 -05:00
Greg T. Wallace
2e082a30cf v1.2.0 2025-01-27 19:54:04 -05:00
Greg T. Wallace
06b76700c4 dep: update all 2025-01-27 19:47:13 -05:00
Greg T. Wallace
7f377fc5da dep: build with ubuntu-24.04 2025-01-27 19:23:18 -05:00
Greg T. Wallace
eedbdfcc2a dep: go 1.23.5 2025-01-27 19:22:45 -05:00
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
Greg T. Wallace
1cfd35c4e2 readme: escape asterisks 2024-09-17 18:50:06 -04:00
Greg T. Wallace
94a76b93de v1.1.0 2024-09-17 18:44:35 -04:00
Greg T. Wallace
1cd9916a17 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
2024-09-17 18:44:34 -04:00
Greg T. Wallace
c22447b0c2 readme: update info re: modern key support 2024-09-17 18:44:33 -04:00
Greg T. Wallace
cbb831e009 add ecdsa key support and enable 4,092 RSA
* apcssh: add descriptive error when required file(s) not passed
* create: dont create key+cert file when key isn't supported by NMC2
* config: fix usage messages re: key types
* p15 files: dont generate key+cert when it isn't needed (aka NMC2 doesn't support key)
* pkcs15: pre-calculate envelope when making the p15 struct
* pkcs15: omit key ID 8 & 9 from EC keys
* pkcs15: update key decode logic
* pkcs15: add key type value for easy determination of compatibility
* pkcs15: add ec key support
* pkcs15: separate functions for key and key+cert p15 files
* update README
see: https://github.com/gregtwallace/apc-p15-tool/issues/6
2024-09-17 18:44:33 -04:00
Greg T. Wallace
51e5847409 go: update to 1.23.1 2024-09-17 18:44:33 -04:00
Greg T. Wallace
e2b5abc624 readme: add beta notice 2024-07-09 19:16:59 -04:00
11 changed files with 405 additions and 42 deletions

View file

@ -8,11 +8,11 @@ on:
env:
GITHUB_REF: ${{ github.ref }}
GO_VERSION: '1.22.4'
GO_VERSION: '1.24.2'
jobs:
build-common:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Checkout Main Repo
@ -40,8 +40,10 @@ jobs:
name: CHANGELOG.md
path: ./CHANGELOG.md
###
build-linux-arm64:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
steps:
- name: Checkout Repo
uses: actions/checkout@v4
@ -50,12 +52,6 @@ jobs:
ref: ${{ env.GITHUB_REF }}
fetch-depth: 0
- name: Update apt
run: sudo apt update
- name: Install cross-compiler for linux/arm64
run: sudo apt-get -y install gcc-aarch64-linux-gnu
- name: Set up Go
uses: actions/setup-go@v5
with:
@ -66,7 +62,6 @@ jobs:
env:
GOOS: linux
GOARCH: arm64
CC: aarch64-linux-gnu-gcc
CGO_ENABLED: 0
- name: Save Compiled Binary
@ -90,7 +85,7 @@ jobs:
path: ./apc-p15-install
build-linux-amd64:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Checkout Backend Repo
uses: actions/checkout@v4
@ -171,9 +166,93 @@ jobs:
name: apc-p15-install-windows-amd64
path: ./apc-p15-install.exe
build-darwin-arm64:
runs-on: macos-15
steps:
- name: Checkout Backend Repo
uses: actions/checkout@v4
with:
repository: gregtwallace/apc-p15-tool
ref: ${{ env.GITHUB_REF }}
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '${{ env.GO_VERSION }}'
- name: Build Tool
run: go build -o ./apc-p15-tool -v ./cmd/tool
env:
GOOS: darwin
GOARCH: arm64
CGO_ENABLED: 0
- name: Save Compiled Binary
uses: actions/upload-artifact@v4
with:
name: apc-p15-tool-darwin-arm64
path: ./apc-p15-tool
- name: Build Install Only
run: go build -o ./apc-p15-install -v ./cmd/install_only
env:
GOOS: darwin
GOARCH: arm64
CGO_ENABLED: 0
- name: Save Compiled Binary
uses: actions/upload-artifact@v4
with:
name: apc-p15-install-darwin-arm64
path: ./apc-p15-install
build-darwin-amd64:
runs-on: macos-13
steps:
- name: Checkout Backend Repo
uses: actions/checkout@v4
with:
repository: gregtwallace/apc-p15-tool
ref: ${{ env.GITHUB_REF }}
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '${{ env.GO_VERSION }}'
- name: Build Tool
run: go build -o ./apc-p15-tool -v ./cmd/tool
env:
GOOS: darwin
GOARCH: amd64
CGO_ENABLED: 0
- name: Save Compiled Binary
uses: actions/upload-artifact@v4
with:
name: apc-p15-tool-darwin-amd64
path: ./apc-p15-tool
- name: Build Install Only
run: go build -o ./apc-p15-install -v ./cmd/install_only
env:
GOOS: darwin
GOARCH: amd64
CGO_ENABLED: 0
- name: Save Compiled Binary
uses: actions/upload-artifact@v4
with:
name: apc-p15-install-darwin-amd64
path: ./apc-p15-install
###
release-file-linux-arm64:
needs: [build-common, build-linux-arm64]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Make release directory
@ -217,7 +296,7 @@ jobs:
release-file-linux-amd64:
needs: [build-common, build-linux-amd64]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Make release directory
@ -261,7 +340,7 @@ jobs:
release-file-windows-amd64:
needs: [build-common, build-windows-amd64]
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Make release directory
@ -302,3 +381,91 @@ jobs:
with:
name: apc-p15-tool_windows_amd64
path: ./release
release-file-darwin-arm64:
needs: [build-common, build-darwin-arm64]
runs-on: ubuntu-24.04
steps:
- name: Make release directory
run: mkdir ./release
- name: Download Tool Binary
uses: actions/download-artifact@v4
with:
name: apc-p15-tool-darwin-arm64
path: ./release
- name: Download Install Binary
uses: actions/download-artifact@v4
with:
name: apc-p15-install-darwin-arm64
path: ./release
- name: Download README
uses: actions/download-artifact@v4
with:
name: README.md
path: ./release
- name: Download LICENSE
uses: actions/download-artifact@v4
with:
name: LICENSE.md
path: ./release
- name: Download CHANGELOG
uses: actions/download-artifact@v4
with:
name: CHANGELOG.md
path: ./release
- name: Save Release
uses: actions/upload-artifact@v4
with:
name: apc-p15-tool_darwin_arm64
path: ./release
release-file-darwin-amd64:
needs: [build-common, build-darwin-amd64]
runs-on: ubuntu-24.04
steps:
- name: Make release directory
run: mkdir ./release
- name: Download Tool Binary
uses: actions/download-artifact@v4
with:
name: apc-p15-tool-darwin-amd64
path: ./release
- name: Download Install Binary
uses: actions/download-artifact@v4
with:
name: apc-p15-install-darwin-amd64
path: ./release
- name: Download README
uses: actions/download-artifact@v4
with:
name: README.md
path: ./release
- name: Download LICENSE
uses: actions/download-artifact@v4
with:
name: LICENSE.md
path: ./release
- name: Download CHANGELOG
uses: actions/download-artifact@v4
with:
name: CHANGELOG.md
path: ./release
- name: Save Release
uses: actions/upload-artifact@v4
with:
name: apc-p15-tool_darwin_amd64
path: ./release

View file

@ -1,13 +1,46 @@
# APC P15 Tool Changelog
## [v1.1.0-b1] - 2024-07-09
## [v1.2.2] - 2025-04-22
BETA: First effort to add support for EC key support for NMC3.
All dependencies updated.
This version also enables RSA 4,092 bit length. Code was updated
so the NMC2 key+cert file is not generated when NMC2 is not
supported. Log messages were also updated to signal user when
a compatibility issue is present.
Add darwin arm64 and amd64 builds.
## [v1.2.1] - 2025-03-17
Fix time check for UPS when it is set to GMT timezone.
All dependencies updated.
## [v1.2.0] - 2025-01-27
Add a new feature to `install` that checks the time of the UPS to confirm
it is accurate. A log message is added that advises either way. Even if
the check fails, the install still proceeds with attempting to install
the new certificate.
Dependencies were also all updated.
## [v1.1.0] - 2024-09-17
> [!IMPORTANT]
> The flag `apchost` on the `install` command has been renamed to
> `hostname`. This flag should contain the hostname only. If a non-
> default SSH port is needed, specify it in the `sshport` flag.
This version brings support for for RSA 4,092 bit and EC keys. These
keys are only compatible with NMC3 running newer firmwares. To know
if your firmware is new enough, SSH into your UPS and type `ssh` and enter.
If the UPS responds `Command Not Found` the firmware is too old or
otherwise incompatible.
This version also adds a post `install` check that connects to the web
ui and verifies the certificate served is the expected one. You can
specify a non standard ssl port with the `sslport` flag or skip the check
entirely with the `skipverify` flag.
## [v1.0.0] - 2024-07-01

View file

@ -63,13 +63,19 @@ NMC2 device in a home lab and have no way to guarantee success in all cases.
NMC2:
- RSA 1,024, 2,048, 3,072* bit lengths.
NMC3:
NMC3*:
- RSA 1,024, 2,048, 3,072, and 4,092 bit lengths.
- ECDSA curves P-256, P-384, and P-521.
* 3,072 bit length is not officially supported by my NMC2, but appears to work
\* 3,072 bit length is not officially supported by my NMC2, but appears to work
fine.
\* The additional key types supported by NMC3 require newer firmware on the
device. I am unsure what the version cutoff is, but you can check support
by connecting to the UPS via SSH and typing `ssl`. If `Command Not Found`
is returned, the firmware is too old and only the key types listed under
NMC2 will work.
1,024 bit RSA is no longer considered completely secure; avoid keys of
this size if possible. Most (all?) public ACME services won't accept keys
of this size anyway.
@ -140,7 +146,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
@ -165,6 +171,11 @@ separate script.
![Cert Warden with APC P15 Tool](https://raw.githubusercontent.com/gregtwallace/apc-p15-tool/main/img/apc-p15-tool.png)
## Links
@Owl-Tec's write up using this tool with ACDS:
https://owltec.ca/Windows+Server/Deploying+An+Internal+HTTPS+Certificate+for+a+UPS+APC+with+ADCS+(Active+Directory+Certificate+Services)+with+APC+P15+Tool
## Thanks
Special thanks to the following people and resources which helped me

View file

@ -34,3 +34,25 @@ $env:GOARCH = "arm64"
$env:GOOS = "linux"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-install-arm64 ./cmd/install_only
# Darwin (MacOS) amd64
$env:GOARCH = "amd64"
$env:GOOS = "darwin"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-tool-darwin-amd64 ./cmd/tool
$env:GOARCH = "amd64"
$env:GOOS = "darwin"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-install-darwin-amd64 ./cmd/install_only
# Darwin (MacOS) arm64
$env:GOARCH = "arm64"
$env:GOOS = "darwin"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-tool-darwin-arm64 ./cmd/tool
$env:GOARCH = "arm64"
$env:GOOS = "darwin"
$env:CGO_ENABLED = 0
go build -o $outDir/apc-p15-install-darwin-arm64 ./cmd/install_only

8
go.mod
View file

@ -1,14 +1,14 @@
module apc-p15-tool
go 1.22.4
go 1.24.2
require (
github.com/peterbourgon/ff/v4 v4.0.0-alpha.4
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
golang.org/x/crypto v0.18.0
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1
golang.org/x/crypto v0.37.0
)
require golang.org/x/sys v0.16.0 // indirect
require golang.org/x/sys v0.32.0 // indirect
replace apc-p15-tool/cmd/install_only => /cmd/install_only

16
go.sum
View file

@ -2,13 +2,13 @@ github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 h1:aiqS8aBlF9PsAKeMddMSfbwp3smONCn3UO8QfUg0Z7Y=
github.com/peterbourgon/ff/v4 v4.0.0-alpha.4/go.mod h1:H/13DK46DKXy7EaIxPhk2Y0EC8aubKm35nBjBe8AAGc=
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
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/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=
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I=
github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

Binary file not shown.

Before

(image error) Size: 102 KiB

After

(image error) Size: 93 KiB

Before After
Before After

62
pkg/apcssh/cmd_gettime.go Normal file
View file

@ -0,0 +1,62 @@
package apcssh
import (
"fmt"
"regexp"
"strings"
"time"
)
// GetTime sends the APC `system` command and then attempts to parse the
// response to determine the UPS current date/time.
func (cli *Client) GetTime() (time.Time, error) {
result, err := cli.cmd("date")
if err != nil {
return time.Time{}, fmt.Errorf("apcssh: failed to get time (%s)", err)
} else if !strings.EqualFold(result.code, "e000") {
return time.Time{}, fmt.Errorf("apcssh: failed to get time (%s: %s)", result.code, result.codeText)
}
// capture each portion of the date information
regex := regexp.MustCompile(`Date:\s*(\S*)\s*[\r\n]Time:\s*(\S*)\s*[\r\n]Format:\s*(\S*)\s*[\r\n]Time Zone:\s*(\S*)\s*[\r\n]?`)
datePieces := regex.FindStringSubmatch(result.resultText)
if len(datePieces) != 5 {
return time.Time{}, fmt.Errorf("apcssh: failed to get time (length of datetime value pieces was %d (expected: 5))", len(datePieces))
}
dateVal := datePieces[1]
timeVal := datePieces[2]
formatUPSVal := datePieces[3]
timeZoneVal := datePieces[4]
// GMT time requires + prefix
// APC UPS fails to use the required +, so add it
if timeZoneVal == "00:00" {
timeZoneVal = "+" + timeZoneVal
}
// known APC UPS format strings
dateFormatVal := ""
switch formatUPSVal {
case "mm/dd/yyyy":
dateFormatVal = "01/02/2006"
case "dd.mm.yyyy":
dateFormatVal = "02.01.2006"
case "mmm-dd-yy":
dateFormatVal = "Jan-02-06"
case "dd-mmm-yy":
dateFormatVal = "02-Jan-06"
case "yyyy-mm-dd":
dateFormatVal = "2006-01-02"
default:
return time.Time{}, fmt.Errorf("apcssh: failed to get time (ups returned unknown format string (%s)", formatUPSVal)
}
// convert to time.Time
t, err := time.Parse(dateFormatVal+" 15:04:05 -07:00", dateVal+" "+timeVal+" "+timeZoneVal)
if err != nil {
return time.Time{}, fmt.Errorf("apcssh: failed to get time (time parse failed: %s)", err)
}
return t, nil
}

View file

@ -12,7 +12,7 @@ import (
)
const (
appVersion = "1.1.0-b1"
appVersion = "1.2.2"
)
// struct for receivers to use common app pieces

View file

@ -2,11 +2,18 @@ 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 {
@ -36,7 +43,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 +64,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,
@ -68,6 +77,16 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error {
}
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 {
@ -75,7 +94,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 +108,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
}

View file

@ -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,