diff --git a/.github/workflows/build_releases.yml b/.github/workflows/build_releases.yml index c054287..5114c97 100644 --- a/.github/workflows/build_releases.yml +++ b/.github/workflows/build_releases.yml @@ -8,10 +8,89 @@ on: env: GITHUB_REF: ${{ github.ref }} + GO_VERSION: '1.22.4' jobs: - build-all: - runs-on: ubuntu-24.04 + build-common: + runs-on: ubuntu-latest + + steps: + - name: Checkout Main Repo + uses: actions/checkout@v4 + with: + repository: gregtwallace/apc-p15-tool + ref: ${{ env.GITHUB_REF }} + fetch-depth: 0 + + - name: Save README + uses: actions/upload-artifact@v4 + with: + name: README.md + path: ./README.md + + - name: Save LICENSE + uses: actions/upload-artifact@v4 + with: + name: LICENSE.md + path: ./LICENSE.md + + - name: Save CHANGELOG + uses: actions/upload-artifact@v4 + with: + name: CHANGELOG.md + path: ./CHANGELOG.md + + build-linux-arm64: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + repository: gregtwallace/apc-p15-tool + 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: + go-version: '${{ env.GO_VERSION }}' + + - name: Build Tool + run: go build -o ./apc-p15-tool -v ./cmd/tool + env: + GOOS: linux + GOARCH: arm64 + CC: aarch64-linux-gnu-gcc + CGO_ENABLED: 0 + + - name: Save Compiled Binary + uses: actions/upload-artifact@v4 + with: + name: apc-p15-tool-linux-arm64 + path: ./apc-p15-tool + + - name: Build Install Only + run: go build -o ./apc-p15-install -v ./cmd/install_only + env: + GOOS: linux + GOARCH: arm64 + CC: aarch64-linux-gnu-gcc + CGO_ENABLED: 0 + + - name: Save Compiled Binary + uses: actions/upload-artifact@v4 + with: + name: apc-p15-install-linux-arm64 + path: ./apc-p15-install + + build-linux-amd64: + runs-on: ubuntu-latest steps: - name: Checkout Backend Repo uses: actions/checkout@v4 @@ -23,14 +102,203 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version-file: 'go.mod' + go-version: '${{ env.GO_VERSION }}' - - name: Build All - run: | - python ./build_release.py + - name: Build Tool + run: go build -o ./apc-p15-tool -v ./cmd/tool + env: + GOOS: linux + GOARCH: amd64 + CGO_ENABLED: 0 - - name: Save Zip of all targets + - name: Save Compiled Binary uses: actions/upload-artifact@v4 with: - name: apc-p15-tool-release - path: ./_out/_release + name: apc-p15-tool-linux-amd64 + path: ./apc-p15-tool + + - name: Build Install Only + run: go build -o ./apc-p15-install -v ./cmd/install_only + env: + GOOS: linux + GOARCH: amd64 + CGO_ENABLED: 0 + + - name: Save Compiled Binary + uses: actions/upload-artifact@v4 + with: + name: apc-p15-install-linux-amd64 + path: ./apc-p15-install + + build-windows-amd64: + runs-on: windows-latest + 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.exe -v ./cmd/tool + env: + GOOS: windows + GOARCH: amd64 + CGO_ENABLED: 0 + + - name: Save Compiled Binary + uses: actions/upload-artifact@v4 + with: + name: apc-p15-tool-windows-amd64 + path: ./apc-p15-tool.exe + + - name: Build Install Only + run: go build -o ./apc-p15-install.exe -v ./cmd/install_only + env: + GOOS: windows + GOARCH: amd64 + CGO_ENABLED: 0 + + - name: Save Compiled Binary + uses: actions/upload-artifact@v4 + with: + name: apc-p15-install-windows-amd64 + path: ./apc-p15-install.exe + + release-file-linux-arm64: + needs: [build-common, build-linux-arm64] + runs-on: ubuntu-latest + + steps: + - name: Make release directory + run: mkdir ./release + + - name: Download Tool Binary + uses: actions/download-artifact@v4 + with: + name: apc-p15-tool-linux-arm64 + path: ./release + + - name: Download Install Binary + uses: actions/download-artifact@v4 + with: + name: apc-p15-install-linux-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_linux_arm64 + path: ./release + + release-file-linux-amd64: + needs: [build-common, build-linux-amd64] + runs-on: ubuntu-latest + + steps: + - name: Make release directory + run: mkdir ./release + + - name: Download Tool Binary + uses: actions/download-artifact@v4 + with: + name: apc-p15-tool-linux-amd64 + path: ./release + + - name: Download Install Binary + uses: actions/download-artifact@v4 + with: + name: apc-p15-install-linux-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_linux_amd64 + path: ./release + + release-file-windows-amd64: + needs: [build-common, build-windows-amd64] + runs-on: ubuntu-latest + + steps: + - name: Make release directory + run: mkdir ./release + + - name: Download Tool Binary + uses: actions/download-artifact@v4 + with: + name: apc-p15-tool-windows-amd64 + path: ./release + + - name: Download Install Binary + uses: actions/download-artifact@v4 + with: + name: apc-p15-install-windows-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_windows_amd64 + path: ./release diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f89be..f83e590 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,61 +1,13 @@ # APC P15 Tool Changelog -## [v1.2.3] - 2025-06-19 +## [v1.1.0-b1] - 2024-07-09 -Minor updates to the application. Large updates to the build process to -improve building, releasing, and maintainability. +BETA: First effort to add support for EC key support for NMC3. -- Go updated to 1.24.4 and all dependencies updated. -- Added FreeBSD arm64 and amd64 builds. -- Build process overhauled for simplicity. Build is now OS agnostic. PowerShell - script was removed and replaced with a python script. -- Build instructions added to README. -- GitHub build action now only runs in one Ubuntu container and cross-compiles. -- Release windows and macos as zip files and all others as gztar. -- Add file permissions for non-windows and non-macos releases. - - -## [v1.2.2] - 2025-04-22 - -All dependencies updated. - -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. +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. ## [v1.0.0] - 2024-07-01 diff --git a/README.md b/README.md index d673c1f..976d5fc 100644 --- a/README.md +++ b/README.md @@ -63,19 +63,13 @@ 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. @@ -146,7 +140,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 --hostname myapc.example.com --username apc --password someSecret --fingerprint 123abc` +e.g. `./apc-p15-tool install --keyfile ./apckey.pem --certfile ./apccert.pem --apchost myapc.example.com:22 --username apc --password someSecret --fingerprint 123abc` ## Note About Install Automation @@ -171,20 +165,6 @@ separate script.  -## Building - -Python3, Go, and git all must be installed to run the build script. - -Once the dependencies are installed, clone this repo and run -`python build_release.py`. If you only want to build for certain OS or -ARCH targets, edit the `targets` array in the `build_release.py` file -before running it. - -## 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 diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..7147a33 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,36 @@ +# Parent dir is root +$scriptDir = Get-Location +$outDir = Join-Path -Path $scriptDir -ChildPath "/_out" + +# Windows x64 +$env:GOARCH = "amd64" +$env:GOOS = "windows" +$env:CGO_ENABLED = 0 +go build -o $outDir/apc-p15-tool-amd64.exe ./cmd/tool + +$env:GOARCH = "amd64" +$env:GOOS = "windows" +$env:CGO_ENABLED = 0 +go build -o $outDir/apc-p15-install-amd64.exe ./cmd/install_only + +# Linux x64 +$env:GOARCH = "amd64" +$env:GOOS = "linux" +$env:CGO_ENABLED = 0 +go build -o $outDir/apc-p15-tool-amd64 ./cmd/tool + +$env:GOARCH = "amd64" +$env:GOOS = "linux" +$env:CGO_ENABLED = 0 +go build -o $outDir/apc-p15-install-amd64 ./cmd/install_only + +# Linux arm64 +$env:GOARCH = "arm64" +$env:GOOS = "linux" +$env:CGO_ENABLED = 0 +go build -o $outDir/apc-p15-tool-arm64 ./cmd/tool + +$env:GOARCH = "arm64" +$env:GOOS = "linux" +$env:CGO_ENABLED = 0 +go build -o $outDir/apc-p15-install-arm64 ./cmd/install_only diff --git a/build_release.py b/build_release.py deleted file mode 100644 index d3892e7..0000000 --- a/build_release.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -import os.path -import shutil -import subprocess -import tarfile - -# Configuration -# output path (relative to this script) -outRelativeDir = "_out" - -# target strings must be in the format: -# `GOOS_GOARCH` -# see: https://github.com/golang/go/blob/master/src/internal/syslist/syslist.go -# or unofficially: https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63 -targets = [ - "windows_amd64", - "linux_amd64", - "linux_arm64", - "darwin_amd64", - "darwin_arm64", - "freebsd_amd64", - "freebsd_arm64", -] - -### - -# Script -# relative dir is root -scriptDir = dirname = os.path.dirname(__file__) -outBaseDir = os.path.join(scriptDir, outRelativeDir) -releaseDir = os.path.join(outBaseDir, "_release") - -# recreate paths -if os.path.exists(outBaseDir): - shutil.rmtree(outBaseDir) -os.makedirs(outBaseDir) -os.makedirs(releaseDir) - -# get version number / tag -gitTag = subprocess.check_output(["git", "describe", "--tags", "--abbrev=0"]).decode('utf-8').strip() - -# loop through and build all targets -for target in targets: - # environment vars - split = target.split("_") - GOOS = split[0] - GOARCH = split[1] - os.environ["GOOS"] = GOOS - os.environ["GOARCH"] = GOARCH - os.environ["CGO_ENABLED"] = "0" - - # send build product to GOOS_GOARCH subfolders - targetOutDir = os.path.join(outBaseDir, target) - if not os.path.exists(targetOutDir): - os.makedirs(targetOutDir) - - # special case for windows to add file extensions - extension = "" - if GOOS.lower() == "windows": - extension = ".exe" - - # build binary and install only binary - subprocess.run(["go", "build", "-o", f"{targetOutDir}/apc-p15-tool{extension}", "./cmd/tool"]) - subprocess.run(["go", "build", "-o", f"{targetOutDir}/apc-p15-install{extension}", "./cmd/install_only"]) - - # copy other important files for release - shutil.copy("README.md", targetOutDir) - shutil.copy("CHANGELOG.md", targetOutDir) - shutil.copy("LICENSE.md", targetOutDir) - - # compress release file - # special case for windows & mac to use zip format - if GOOS.lower() == "windows" or GOOS.lower() == "darwin": - shutil.make_archive(f"{releaseDir}/apc-p15-tool-{gitTag}_{target}", "zip", targetOutDir) - else: - # for others, use gztar and set permissions on the files - - # filter for setting permissions - def set_permissions(tarinfo): - if tarinfo.name == "apc-p15-tool" or tarinfo.name == "apc-p15-install": - tarinfo.mode = 0o0755 - else: - tarinfo.mode = 0o0644 - return tarinfo - - # make tar - with tarfile.open(f"{releaseDir}/apc-p15-tool-{gitTag}_{target}.tar.gz", "w:gz") as tar: - for file in os.listdir(targetOutDir): - tar.add(os.path.join(targetOutDir, file), arcname=file, recursive=False, filter=set_permissions) diff --git a/go.mod b/go.mod index 4848582..ad1bc05 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,14 @@ module apc-p15-tool -go 1.24.4 +go 1.22.4 require ( github.com/peterbourgon/ff/v4 v4.0.0-alpha.4 - github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1 - golang.org/x/crypto v0.39.0 + github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 + golang.org/x/crypto v0.18.0 ) -require golang.org/x/sys v0.33.0 // indirect +require golang.org/x/sys v0.16.0 // indirect replace apc-p15-tool/cmd/install_only => /cmd/install_only diff --git a/go.sum b/go.sum index cd5913b..96676f0 100644 --- a/go.sum +++ b/go.sum @@ -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-20240131213347-83fcde1e29d1 h1:NVK+OqnavpyFmUiKfUMHrpvbCi2VFoWTrcpI7aDaJ2I= -github.com/sigurn/crc16 v0.0.0-20240131213347-83fcde1e29d1/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +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= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/img/apc-p15-tool.png b/img/apc-p15-tool.png index c537585..807fb84 100644 Binary files a/img/apc-p15-tool.png and b/img/apc-p15-tool.png differ diff --git a/pkg/apcssh/cmd_gettime.go b/pkg/apcssh/cmd_gettime.go deleted file mode 100644 index 139b0ba..0000000 --- a/pkg/apcssh/cmd_gettime.go +++ /dev/null @@ -1,62 +0,0 @@ -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 -} diff --git a/pkg/app/app.go b/pkg/app/app.go index 53c734a..d262678 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -12,7 +12,7 @@ import ( ) const ( - appVersion = "1.2.3" + appVersion = "1.1.0-b1" ) // struct for receivers to use common app pieces diff --git a/pkg/app/cmd_install.go b/pkg/app/cmd_install.go index eacda53..d3270a4 100644 --- a/pkg/app/cmd_install.go +++ b/pkg/app/cmd_install.go @@ -2,18 +2,11 @@ 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 { @@ -43,9 +36,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { } // 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 { - + if app.config.install.hostAndPort == nil || *app.config.install.hostAndPort == "" { return errors.New("install: failed, apc host not specified") } @@ -64,7 +55,7 @@ func (app *app) cmdInstall(cmdCtx context.Context, args []string) error { // make APC SSH client cfg := &apcssh.Config{ - Hostname: *app.config.install.hostname + ":" + strconv.Itoa(*app.config.install.sshport), + Hostname: *app.config.install.hostAndPort, Username: *app.config.install.username, Password: *app.config.install.password, ServerFingerprint: *app.config.install.fingerprint, @@ -77,16 +68,6 @@ 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 { @@ -94,7 +75,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.hostname) + 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 { @@ -108,48 +89,5 @@ 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 7edf216..6ef840b 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -33,14 +33,11 @@ type config struct { } install struct { keyCertPemCfg - hostname *string - sshport *int + hostAndPort *string fingerprint *string username *string password *string restartWebUI *bool - webUISSLPort *int - skipVerify *bool insecureCipher *bool } } @@ -95,19 +92,16 @@ 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.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.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") 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 --hostname example.com --fingerprint 123abc --username apc --password test", + Usage: "apc-p15-tool install --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,