Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfixes 2022-06 #177

Merged
merged 6 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 3 additions & 12 deletions server/httpd_ssl.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,6 @@ Listen 443 https
# Pass along database options from the environment to CGI scripts
PassEnv NIVLHEIM_PGHOST NIVLHEIM_PGUSER NIVLHEIM_PGPASSWORD NIVLHEIM_PGDATABASE

# SSL protocol settings
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
SSLHonorCipherOrder on
SSLCompression off

# OCSP Stapling
SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache shmcb:/var/run/ocsp(128000)

# HTTP headers
Header set Strict-Transport-Security "max-age=3600"
Header set Content-Security-Policy "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; font-src 'self'; frame-ancestors 'none'; upgrade-insecure-requests"
Expand Down Expand Up @@ -57,6 +45,9 @@ SSLVerifyDepth 10
ProxyPass "http://nivlheimapi:4040/api/"
</Location>

CustomLog "|/usr/sbin/rotatelogs -n 7 /var/log/httpd/access_log 86400" combined
ErrorLog "|/usr/sbin/rotatelogs -n 7 /var/log/httpd/error_log 86400"

</VirtualHost>

<VirtualHost nivlheimweb:80>
Expand Down
6 changes: 6 additions & 0 deletions server/service/database/patch007.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SET client_min_messages TO WARNING;

-- Create an index because the certid field is referenced in the files table and slows down delete operations otherwise
CREATE INDEX IF NOT EXISTS files_originalcertid ON files(originalcertid);

UPDATE db SET patchlevel = 7;
51 changes: 51 additions & 0 deletions server/service/deleteOldCertificates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package main

import (
"database/sql"
"log"
"time"

"github.com/unioslo/nivlheim/server/service/utility"
)

type deleteOldCertificatesJob struct{}

func init() {
RegisterJob(deleteOldCertificatesJob{})
}

func (p deleteOldCertificatesJob) HowOften() time.Duration {
return time.Hour * 24
}

func (p deleteOldCertificatesJob) Run(db *sql.DB) {
// Delete old certificates from the database table
// Criteria:
// - expired (past the "not after" date)
// - not referenced from any files/hostinfo rows
// - not referenced by any other certificate in the table (through the "previous" column) that was in turn referenced by files/hostinfo
err := utility.RunStatementsInTransaction(db, []string{
// First, find certificates in use, create a temporary table
`SELECT * INTO TEMP TABLE certs_in_use FROM (
WITH RECURSIVE previouscerts AS (
SELECT certid,previous FROM certificates WHERE fingerprint IN (SELECT distinct certfp FROM files UNION SELECT distinct certfp FROM hostinfo)
UNION
SELECT certid,previous FROM certificates c, files f WHERE c.certid=f.originalcertid
UNION
SELECT DISTINCT c.certid,c.previous FROM certificates c,previouscerts pc WHERE c.certid=pc.previous
) SELECT * FROM previouscerts
) AS foo`,
// Then, find certificates that fit the criteria to be deleted
`SELECT c.certid INTO TEMP TABLE certs_to_be_deleted
FROM certificates c
LEFT JOIN certs_in_use use ON c.certid=use.certid
WHERE to_timestamp( (regexp_match(cert,'Not After : (.* GMT)'))[1], 'Mon DD HH24:MI:SS YYYY') < now() AND use.certid IS NULL`,
// Delete the certificates
`DELETE FROM certificates WHERE certid IN (SELECT certid FROM certs_to_be_deleted)`,
// Last, drop the temp tables and index
`DROP TABLE certs_in_use, certs_to_be_deleted`,
})
if err != nil {
log.Panic(err)
}
}
58 changes: 58 additions & 0 deletions server/service/deleteOldCertificates_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"os"
"testing"

"github.com/unioslo/nivlheim/server/service/utility"
)

func TestDeleteOldCertificates(t *testing.T) {
if os.Getenv("NOPOSTGRES") != "" {
t.Log("No Postgres, skipping test")
return
}

// Create a database connection
db := getDBconnForTesting(t)
defer db.Close()

// Add some data for testing
err := utility.RunStatementsInTransaction(db, []string{
// Expired certificate, should be deleted
"INSERT INTO certificates(certid, first, previous, fingerprint, cert, issued, commonname) VALUES(1,1,NULL,'abcd','Not After : Jan 01 13:14:15 2020 GMT',now(),'foo')",
// Expired certificate, but referenced by another certificate, should be kept
"INSERT INTO certificates(certid, first, previous, fingerprint, cert, issued, commonname) VALUES(2,2,NULL,'bcde','Not After : Jan 01 13:14:15 2020 GMT',now(),'foo')",
// Expired certificate, but still referenced by a file, should be kept
"INSERT INTO certificates(certid, first, previous, fingerprint, cert, issued, commonname) VALUES(3,2,2,'cdef','Not After : Jan 01 13:14:15 2021 GMT', now(), 'foo')",
"INSERT INTO files(originalcertid,certfp) VALUES(2,'cdef')",
// Certificate that has a valid date, should be kept
"INSERT INTO certificates(certid, first, previous, fingerprint, cert, issued, commonname) VALUES(4,4,NULL,'efefef','Not After : Jan 01 13:14:15 2070 GMT',now(),'bar')",
// Certificate that is referenced by a hostinfo row and no files, should never happen but if it does we've covered that too
"INSERT INTO certificates(certid, first, previous, fingerprint, cert, issued, commonname) VALUES(5,5,NULL,'dedede','Not After : Jan 01 13:14:15 2021 GMT', now(), 'baz')",
"INSERT INTO hostinfo(hostname,certfp) VALUES('baz','dedede')",
})
if err != nil {
t.Fatal(err)
}

// Run the function
job := deleteOldCertificatesJob{}
job.Run(db)

// Look at the result
list, err := QueryColumn(db, "SELECT certid FROM certificates ORDER BY certid")
if err != nil {
t.Fatal(err)
}
correctResult := []int64{2, 3, 4, 5}
if len(list) != len(correctResult) {
t.Fatalf("Expected %v, got %v", correctResult, list)
}
for i, v := range list {
n := v.(int64)
if n != correctResult[i] {
t.Fatalf("Expected %v, got %v", correctResult, list)
}
}
}
14 changes: 7 additions & 7 deletions server/service/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const sessionCookieName = "nivlheimSession"
// Returns nil otherwise.
// This function does not create a new session.
func getSessionFromRequest(req *http.Request) *Session {
if isLocal(req) {
if isLocal(req) && devmode {
// Browsers have non-standard behavior with cookies toward localhost,
// so session mgmt with cookies doesn't work when developing locally.
// For development, let's just return the one and only session anyway.
Expand Down Expand Up @@ -98,9 +98,9 @@ func newSession(w http.ResponseWriter, req *http.Request) *Session {
defer sessionMutex.Unlock()
sPtr := new(Session)
sPtr.lastUsed = time.Now()
if isLocal(req) {
// If local connection, assume development environment.
// Make sure there's only one active session
if isLocal(req) && devmode {
// development environment
// make sure there's only one active session
sessions = make(map[string]*Session, 0)
}
sessions[newID] = sPtr
Expand All @@ -110,9 +110,9 @@ func newSession(w http.ResponseWriter, req *http.Request) *Session {
func deleteSession(req *http.Request) {
sessionMutex.Lock()
defer sessionMutex.Unlock()
if isLocal(req) {
// If local connection, assume development environment.
// There's only supposed to be one session, so delete all.
if isLocal(req) && devmode {
// If local connection and development environment,
// there's only supposed to be one session, so delete all.
sessions = make(map[string]*Session, 0)
} else {
cookie, err := req.Cookie(sessionCookieName)
Expand Down
29 changes: 21 additions & 8 deletions server/service/testingUtilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,36 @@ import (
"fmt"
"io/ioutil"
"os"
"os/user"
"regexp"
"runtime"
"testing"
)

// GetDBconnForTesting returns a database handle that points to a
// temporary tablespace that cleans up after the connection is closed.
// The function runs all the SQL scripts to create tables etc.
func getDBconnForTesting(t *testing.T) *sql.DB {
// Create a database connection
var dataSource string
if runtime.GOOS == "windows" {
dataSource = "sslmode=disable host=127.0.0.1 port=5432"
} else {
dataSource = "sslmode=disable host=/var/run/postgresql"
// username
user, err := user.Current()
if err != nil {
t.Fatalf(err.Error())
}

// defaults for testing
var config = &Config{
PGdatabase: user.Username,
PGuser: user.Username,
PGpassword: "",
}
db, err := sql.Open("postgres", dataSource)

// Look for configuration overrides in the environment.
UpdateConfigFromEnvironment(config)

// Create a database connection
dbConnectionString := fmt.Sprintf(
"host=127.0.0.1 port=5432 dbname=%s user=%s password='%s' sslmode=disable",
config.PGdatabase, config.PGuser, config.PGpassword)
db, err := sql.Open("postgres", dbConnectionString)
if err != nil {
t.Fatal(err)
}
Expand Down
2 changes: 2 additions & 0 deletions server/service/utility/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"log"
"math/rand"
"reflect"
"strconv"
Expand Down Expand Up @@ -115,6 +116,7 @@ func RunStatementsInTransaction(db *sql.DB, statements []string, args ...interfa
return RunInTransaction(db, func(tx *sql.Tx) error {
for _, st := range statements {
if _, err := tx.Exec(st, args...); err != nil {
log.Printf("Statement that failed: %s", st)
return err
}
}
Expand Down