From 88d7c1824aad6cc89b9c0df6d09b5b3ae8b14191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Hagberg?= Date: Mon, 24 Oct 2022 15:39:34 +0200 Subject: [PATCH 1/8] Add a cmdline opt for server to the linux client Also: Remove hard-coded references to /var/nivlheim, and add a test. --- client/nivlheim_client | 21 ++++++++++++++------- tests/test_cert_handling.sh | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/client/nivlheim_client b/client/nivlheim_client index 73e16ae..67ff574 100755 --- a/client/nivlheim_client +++ b/client/nivlheim_client @@ -79,6 +79,7 @@ my $HELP = <<'END_HELP'; OPTIONS: -c, --config Specify configuration file + -s, --server Specify server hostname --ssl-ca SSL CA file --ssl-cert SSL CERT file --ssl-key SSL key file @@ -101,6 +102,7 @@ END_LICENSE # Get options my %opt; GetOptions('c|config=s' => \$opt{config}, + 's|server=s' => \$opt{server}, 'ssl-ca=s' => \$opt{ca_file}, 'ssl-cert=s' => \$opt{cert_file}, 'ssl-key=s' => \$opt{key_file}, @@ -210,14 +212,18 @@ foreach (keys %defaultopt) { } } -# Verify that we have a server url. It must have trailing slash. -if (!defined($config{'settings'}) || !defined($config{'settings'}{'server'})) { +# Verify that a "server" argument or config option was given +if (defined($opt{server})) { + $server_url = 'https://' . $opt{server} . '/cgi-bin/'; +} elsif (defined($config{'settings'}{'server'})) { + $server_url = 'https://' . $config{'settings'}{'server'} . '/cgi-bin/'; +} else { print "The config file $configfile must have a section [settings] " ."that contains a \"server\" option " ."with the hostname or ip address of the server.\n"; + print "Alternatively, you may pass it as an argument with -s / --server.\n"; exit 1; } -$server_url = 'https://' . $config{'settings'}{'server'} . '/cgi-bin/'; # Show options in effect if ($opt{debug}) { @@ -393,7 +399,7 @@ elsif ($have_cert && !$cert_works) { } } -createPKCS8() unless (-f "/var/nivlheim/pkcs8.key"); +createPKCS8() unless (-f dirname($opt{key_file})."/pkcs8.key"); # Determine which files to send my @filelist = @{$config{'files'}} if defined($config{'files'}); @@ -514,7 +520,7 @@ print "Signed the archive file\n" if ($opt{debug}); # nonce my $nonce = 0; -my $noncefile = "/var/nivlheim/nonce"; +my $noncefile = dirname($opt{key_file})."/nonce"; if (-r $noncefile) { open(my $F, $noncefile); $nonce = <$F>; @@ -913,8 +919,9 @@ sub parse_certificate_response($) { } sub createPKCS8() { - system("openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in /var/nivlheim/my.key -out /var/nivlheim/pkcs8.key"); - chmod(0600, "/var/nivlheim/pkcs8.key"); + my $dir = dirname($opt{key_file}); + system("openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in ".$opt{key_file}." -out $dir/pkcs8.key"); + chmod(0600, "$dir/pkcs8.key"); } sub sign_with_cfengine_key() { diff --git a/tests/test_cert_handling.sh b/tests/test_cert_handling.sh index 68ac918..466e608 100755 --- a/tests/test_cert_handling.sh +++ b/tests/test_cert_handling.sh @@ -205,6 +205,23 @@ if ! grep -qi "revoked" $tempdir/renewresult; then exit 1 fi +# Run with other paths for cert/key, verify that cert, key, pkcs8 and nonce files were created there. +echo "Running the client with another path for cert and key, empty config, and server given by argument" +mkdir -p $tempdir/foo; rm -f $tempdir/foo/* +touch $tempdir/foo/emptyfile +if ! docker run --rm --network host --mount type=bind,source=$tempdir/foo,target=/foo nivlheimclient \ + --ssl-cert /foo/a.crt --ssl-key /foo/a.key -c /foo/emptyfile --server localhost --debug >$tempdir/output2 2>&1; then + echo "The client failed to post data successfully when run with cert/key arguments:" + echo "--------------------------------------------" + cat $tempdir/output2 + exit 1 +fi +if [ ! -f $tempdir/foo/a.crt ] || [ ! -f $tempdir/foo/a.key ] || [ ! -f $tempdir/foo/pkcs8.key ] || [ ! -f $tempdir/foo/nonce ]; then + echo "Some output files are missing. These are present:" + ls $tempdir/foo + exit 1; +fi + # Check logs for errors if docker exec -t docker_nivlheimweb_1 grep -A1 "ERROR" /var/log/nivlheim/system.log; then exit 1 From 1487ce2206b54833f626406791aac4bdfffa9eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Hagberg?= Date: Mon, 24 Oct 2022 16:06:11 +0200 Subject: [PATCH 2/8] Fix a bug in parseFiles.go (missing space in sql) --- server/service/parseFiles.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/service/parseFiles.go b/server/service/parseFiles.go index c5dab96..dcb40fb 100644 --- a/server/service/parseFiles.go +++ b/server/service/parseFiles.go @@ -301,7 +301,7 @@ func parseFile(database *sql.DB, fileID int64) { serial.String = m[1] serial.Valid = len(serial.String) > 0 } - _, err = tx.Exec("UPDATE hostinfo SET manufacturer=$1,product=$2,serialno=$3"+ + _, err = tx.Exec("UPDATE hostinfo SET manufacturer=$1,product=$2,serialno=$3 "+ "WHERE certfp=$4", manufacturer, product, serial, certfp.String) return } @@ -319,7 +319,7 @@ func parseFile(database *sql.DB, fileID int64) { serial.String = m[1] serial.Valid = len(serial.String) > 0 } - _, err = tx.Exec("UPDATE hostinfo SET manufacturer='Apple',product=$1,serialno=$2"+ + _, err = tx.Exec("UPDATE hostinfo SET manufacturer='Apple',product=$1,serialno=$2 "+ "WHERE certfp=$3", product, serial, certfp.String) return } From cfcf62059a65a6327482d08cc87471e6bba0319f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Hagberg?= Date: Mon, 24 Oct 2022 16:29:14 +0200 Subject: [PATCH 3/8] Updated GitHub workflow actions to latest version --- .github/workflows/ci.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index da1294a..8cbccb2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,7 +22,7 @@ jobs: - 5432:5432 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Run Go tests run: | cd server/service @@ -44,7 +44,7 @@ jobs: - name: Docker save run: docker save nivlheim | gzip > nivlheim-image.tar.gz - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: nivlheim-image.tar.gz path: nivlheim-image.tar.gz @@ -54,7 +54,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Docker build run: docker build --file ci/docker/Dockerfile --tag nivlheim-www:latest . - name: Docker save @@ -63,7 +63,7 @@ jobs: docker inspect $IMAGE docker save nivlheim-www | gzip > nivlheim-www.tar.gz - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: nivlheim-www.tar.gz path: nivlheim-www.tar.gz @@ -73,7 +73,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Docker build run: | cp client/client.yaml tmp_client.yaml @@ -82,7 +82,7 @@ jobs: docker save nivlheimclient | gzip > nivlheim-client.tar.gz rm tmp_client.yaml - name: Upload artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: nivlheim-client.tar.gz path: nivlheim-client.tar.gz @@ -102,9 +102,9 @@ jobs: - powershell.sh steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 # the name input parameter is not provided, so all artifacts will be downloaded - name: Load images run: | @@ -146,7 +146,7 @@ jobs: contents: read steps: - name: Download artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: nivlheim-image.tar.gz - name: Load image @@ -176,7 +176,7 @@ jobs: contents: read steps: - name: Download artifact - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: nivlheim-www.tar.gz - name: Load image From ce780646e4c8de3d67b3ef58948baa88a39455ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Hagberg?= Date: Wed, 26 Oct 2022 16:28:28 +0200 Subject: [PATCH 4/8] Make the host ownership job fail if plugin fails --- server/service/host_owner.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/service/host_owner.go b/server/service/host_owner.go index 845b814..22e1fc6 100644 --- a/server/service/host_owner.go +++ b/server/service/host_owner.go @@ -59,6 +59,10 @@ func (job determineHostOwnershipJob) Run(db *sql.DB) { } pluginAccess.AllowOnlyLocalhost() tempKey := GenerateTemporaryAPIKey(pluginAccess) + defer func() { + // When the function exits, even if panic, make the key expire right away + pluginAccess.expires = time.Now() + }() // Loop through those hosts and call the plugin to determine ownership for each of them for _, host := range list { if devmode { @@ -83,7 +87,7 @@ func (job determineHostOwnershipJob) Run(db *sql.DB) { // Check the http status if resp.StatusCode > 299 { // oops, the statuscode indicates an error - continue + log.Panicf("http status %d from host owner plugin", resp.StatusCode) } // Parse the response from the plugin. Should be one line of text // that only contains the owner group name, nothing else. @@ -93,7 +97,4 @@ func (job determineHostOwnershipJob) Run(db *sql.DB) { "WHERE certfp=$2", ownerGroup, host.certfp) } } - // All done. Now make the key expire right away - pluginAccess.expires = time.Now() - } From 3042e1288cb0a0c9da69439552376ec67b9dce5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Hagberg?= Date: Wed, 26 Oct 2022 16:48:54 +0200 Subject: [PATCH 5/8] Fix a regexp and write a test for it --- server/service/parseFiles.go | 2 +- server/service/parseFiles_test.go | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/server/service/parseFiles.go b/server/service/parseFiles.go index dcb40fb..dfe936d 100644 --- a/server/service/parseFiles.go +++ b/server/service/parseFiles.go @@ -239,7 +239,7 @@ func parseFile(database *sql.DB, fileID int64) { } if strings.EqualFold(filename.String, "(Get-WmiObject Win32_OperatingSystem).Caption") { - reWinX := regexp.MustCompile(`Microsoft Windows (\d+) (.*)`) + reWinX := regexp.MustCompile(`Microsoft Windows (\d+) (\w*)`) reWinServer := regexp.MustCompile(`Microsoft®? Windows Server®? (\d+)( R2)?`) if m := reWinX.FindStringSubmatch(content.String); m != nil { _, err = tx.Exec("UPDATE hostinfo SET os=$1, os_edition=$2, os_family='Windows' "+ diff --git a/server/service/parseFiles_test.go b/server/service/parseFiles_test.go index 26950ad..261ea73 100644 --- a/server/service/parseFiles_test.go +++ b/server/service/parseFiles_test.go @@ -132,6 +132,10 @@ func TestParseFilesWindows(t *testing.T) { "VersionString": "Microsoft Windows NT 10.0.17763.0" }`, }, + { + filename: "(Get-WmiObject Win32_OperatingSystem).Caption", + content: "Microsoft Windows 10 Enterprise\r\n", + }, } for _, f := range testfiles { _, err := db.Exec("INSERT INTO files(certfp,filename,content,received) "+ @@ -146,10 +150,10 @@ func TestParseFilesWindows(t *testing.T) { job.Run(db) // verify the results - var kernel, manufacturer, product, serial sql.NullString - err := db.QueryRow("SELECT kernel,manufacturer,product,serialno "+ + var kernel, manufacturer, product, serial, edition sql.NullString + err := db.QueryRow("SELECT kernel,manufacturer,product,serialno,os_edition "+ "FROM hostinfo WHERE certfp='1234'"). - Scan(&kernel, &manufacturer, &product, &serial) + Scan(&kernel, &manufacturer, &product, &serial, &edition) if err == sql.ErrNoRows { t.Fatal("No hostinfo row found") } @@ -176,6 +180,11 @@ func TestParseFilesWindows(t *testing.T) { if kernel.String != expectedKernel { t.Errorf("Kernel = %s, expected %s", kernel.String, expectedKernel) } + + expectedEdition := "Enterprise" + if edition.String != expectedEdition { + t.Errorf("OS Edition = %s, expected %s", edition.String, expectedEdition) + } } func TestParseFilesMacOS(t *testing.T) { From 3a3be5ffb964d6d58576f726c588962190536dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Hagberg?= Date: Fri, 28 Oct 2022 10:57:16 +0200 Subject: [PATCH 6/8] Prevent unnecessary error message from Database.pm --- server/cgi/Database.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/cgi/Database.pm b/server/cgi/Database.pm index d24a189..72b2146 100644 --- a/server/cgi/Database.pm +++ b/server/cgi/Database.pm @@ -7,8 +7,7 @@ sub connect_to_db() { $dbparams{'PGPORT'} = 5432; # default # Try to read connection parameters from /etc/nivlheim/server.conf - open(my $F, "/etc/nivlheim/server.conf"); - if ($F) { + if (open(my $F, "/etc/nivlheim/server.conf")) { while (<$F>) { if (/^([pP[gG][a-zA-Z]+)=(.*)/) { $dbparams{uc $1} = $2; From ff5a02608f3bf71984c21ed9adba8d9a18087c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Hagberg?= Date: Fri, 28 Oct 2022 10:58:01 +0200 Subject: [PATCH 7/8] Bump version --- VERSION | 2 +- client/nivlheim_client | 2 +- client/windows/nivlheim_client.ps1 | 2 +- debian/changelog | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/VERSION b/VERSION index f24054f..35b46ae 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.7.15 +2.7.16 diff --git a/client/nivlheim_client b/client/nivlheim_client index 67ff574..8328df4 100755 --- a/client/nivlheim_client +++ b/client/nivlheim_client @@ -67,7 +67,7 @@ my $NAME = 'nivlheim_client'; my $AUTHOR = 'Øyvind Hagberg'; my $CONTACT = 'oyvind.hagberg@usit.uio.no'; my $RIGHTS = 'USIT/IT-DRIFT/GD/GID, University of Oslo, Norway'; -my $VERSION = '2.7.15'; +my $VERSION = '2.7.16'; # Usage text my $USAGE = <<"END_USAGE"; diff --git a/client/windows/nivlheim_client.ps1 b/client/windows/nivlheim_client.ps1 index 8f1c4b0..16712e1 100644 --- a/client/windows/nivlheim_client.ps1 +++ b/client/windows/nivlheim_client.ps1 @@ -31,7 +31,7 @@ param( [bool]$nosleep = $false ) -Set-Variable version -option Constant -value "2.7.15" +Set-Variable version -option Constant -value "2.7.16" Set-Variable useragent -option Constant -value "NivlheimPowershellClient/$version" Set-PSDebug -strict Set-StrictMode -version "Latest" # http://technet.microsoft.com/en-us/library/hh849692.aspx diff --git a/debian/changelog b/debian/changelog index 1be0bcc..3576d88 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,8 @@ -nivlheim (2.7.15-1) buster; urgency=low +nivlheim (2.7.16-1) buster; urgency=low * Upstream changes, no changes in the client - -- Øyvind Hagberg Mon, 10 Oct 2022 13:55:00 +0200 + -- Øyvind Hagberg Web, 26 Oct 2022 17:03:00 +0200 nivlheim (2.7.13-1) buster; urgency=low From c23d31474fa813d092dd2b25171c02b7c97d88a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20Hagberg?= Date: Fri, 28 Oct 2022 15:42:35 +0200 Subject: [PATCH 8/8] Correct the latest entry in the Debian changelog --- debian/changelog | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/debian/changelog b/debian/changelog index 3576d88..1634ac1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,9 @@ nivlheim (2.7.16-1) buster; urgency=low - * Upstream changes, no changes in the client + * Added a --server option to the client + * Removed hard-coded references to /var/nivlheim - -- Øyvind Hagberg Web, 26 Oct 2022 17:03:00 +0200 + -- Øyvind Hagberg Wed, 26 Oct 2022 17:03:00 +0200 nivlheim (2.7.13-1) buster; urgency=low